Просмотр исходного кода

Merge pull request #7749 from weseek/support/jest-to-vitest

support: Convert unit tests by Jest to Vitest
Yuki Takei 2 лет назад
Родитель
Сommit
0c68096cc7
41 измененных файлов с 388 добавлено и 515 удалено
  1. 0 11
      .eslintrc.js
  2. 0 11
      apps/app/.eslintrc.js
  3. 1 4
      apps/app/config/migrate-mongo-config.js
  4. 71 0
      apps/app/config/migrate-mongo-config.spec.ts
  5. 0 20
      apps/app/jest.config.js
  6. 1 1
      apps/app/package.json
  7. 1 3
      apps/app/src/server/console.js
  8. 3 1
      apps/app/src/server/crowi/express-init.js
  9. 1 3
      apps/app/src/server/crowi/index.js
  10. 9 3
      apps/app/src/server/middlewares/exclude-read-only-user.spec.ts
  11. 23 30
      apps/app/src/server/middlewares/safe-redirect.spec.ts
  12. 6 4
      apps/app/src/server/middlewares/safe-redirect.ts
  13. 1 5
      apps/app/src/server/util/mongoose-utils.ts
  14. 18 1
      apps/app/src/services/renderer/rehype-plugins/relative-links.spec.ts
  15. 4 3
      apps/app/src/services/renderer/rehype-plugins/relative-links.ts
  16. 6 3
      apps/app/src/utils/page-delete-config.test.ts
  17. 3 1
      apps/app/src/utils/to-array-from-csv.spec.ts
  18. 16 0
      apps/app/test/integration/.eslintrc.js
  19. 1 10
      apps/app/test/integration/global-setup.js
  20. 2 2
      apps/app/test/integration/migrations/20210913153942-migrate-slack-app-integration-schema.test.ts
  21. 2 2
      apps/app/test/integration/service/questionnaire-cron.test.ts
  22. 1 2
      apps/app/test/integration/setup.js
  23. 0 71
      apps/app/test/unit/migrate-mongo-config.test.js
  24. 13 0
      apps/app/vitest.config.ts
  25. 6 1
      package.json
  26. 5 0
      packages/core/.eslintrc.js
  27. 0 63
      packages/core/jest.config.js
  28. 1 1
      packages/core/package.json
  29. 0 1
      packages/core/src/index.ts
  30. 6 10
      packages/core/src/plugin/util/option-parser.spec.ts
  31. 0 54
      packages/core/src/service/localstorage-manager.js
  32. 23 0
      packages/core/src/utils/env-utils.spec.ts
  33. 4 2
      packages/core/src/utils/objectid-utils.spec.ts
  34. 16 51
      packages/core/src/utils/page-path-utils/index.spec.ts
  35. 14 0
      packages/core/src/utils/page-path-utils/is-top-page.spec.ts
  36. 8 6
      packages/core/src/utils/path-utils.spec.ts
  37. 0 86
      packages/core/test/service/localstorage-manager.test.js
  38. 0 21
      packages/core/test/util/env-utils.test.js
  39. 5 0
      packages/core/vitest.config.ts
  40. 0 4
      vitest.workspace.ts
  41. 117 24
      yarn.lock

+ 0 - 11
.eslintrc.js

@@ -3,15 +3,8 @@ module.exports = {
   extends: [
     'weseek',
     'weseek/typescript',
-    'plugin:jest/recommended',
   ],
-  env: {
-    'jest/globals': true,
-  },
-  globals: {
-  },
   plugins: [
-    'jest',
     'regex',
   ],
   rules: {
@@ -72,10 +65,6 @@ module.exports = {
         FunctionExpression: { body: 1, parameters: 2 },
       },
     ],
-    'jest/no-standalone-expect': [
-      'error',
-      { additionalTestBlockFunctions: ['each.test'] },
-    ],
     'regex/invalid': ['error', [
       {
         regex: '\\?\\<\\!',

+ 0 - 11
apps/app/.eslintrc.js

@@ -5,16 +5,6 @@ module.exports = {
   plugins: [
     'regex',
   ],
-  env: {
-    jquery: true,
-  },
-  globals: {
-    $: true,
-    jquery: true,
-    hljs: true,
-    ScrollPosStyler: true,
-    window: true,
-  },
   settings: {
     // resolve path aliases by eslint-import-resolver-typescript
     'import/resolver': {
@@ -40,7 +30,6 @@ module.exports = {
     // set 'warn' temporarily -- 2021.08.02 Yuki Takei
     '@typescript-eslint/no-use-before-define': ['warn'],
     '@typescript-eslint/no-this-alias': ['warn'],
-    'jest/no-done-callback': ['warn'],
   },
   overrides: [
     {

+ 1 - 4
apps/app/config/migrate-mongo-config.js

@@ -8,7 +8,7 @@ const isProduction = process.env.NODE_ENV === 'production';
 
 const { URL } = require('url');
 
-const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = isProduction
+const { getMongoUri, mongoOptions } = isProduction
   // eslint-disable-next-line import/extensions, import/no-unresolved
   ? require('../dist/server/util/mongoose-utils')
   : require('../src/server/util/mongoose-utils');
@@ -19,9 +19,6 @@ if (migrationsDir == null) {
   throw new Error('An env var MIGRATIONS_DIR must be set.');
 }
 
-
-initMongooseGlobalSettings();
-
 const mongoUri = getMongoUri();
 
 // parse url

+ 71 - 0
apps/app/config/migrate-mongo-config.spec.ts

@@ -0,0 +1,71 @@
+import {
+  vi,
+  beforeEach,
+  describe, test, expect,
+} from 'vitest';
+
+import mockRequire from 'mock-require';
+
+const { reRequire } = mockRequire;
+
+
+describe('config/migrate-mongo-config.js', () => {
+
+  test.concurrent('throws an error when MIGRATIONS_DIR is not set', () => {
+
+    const getMongoUriMock = vi.fn();
+    const mongoOptionsMock = vi.fn();
+
+    // mock for mongoose-utils
+    mockRequire('../src/server/util/mongoose-utils', {
+      getMongoUri: getMongoUriMock,
+      mongoOptions: mongoOptionsMock,
+    });
+
+    // use reRequire to avoid using module cache
+    const caller = () => reRequire('./migrate-mongo-config');
+
+    expect(caller).toThrow('An env var MIGRATIONS_DIR must be set.');
+
+    mockRequire.stop('../src/server/util/mongoose-utils');
+
+    expect(getMongoUriMock).not.toHaveBeenCalled();
+  });
+
+  describe.concurrent.each`
+    MONGO_URI                                         | expectedDbName
+    ${'mongodb://example.com/growi'}                  | ${'growi'}
+    ${'mongodb://user:pass@example.com/growi'}        | ${'growi'}
+    ${'mongodb://example.com/growi?replicaSet=mySet'} | ${'growi'}
+  `('returns', ({ MONGO_URI, expectedDbName }) => {
+
+    beforeEach(async() => {
+      process.env.MIGRATIONS_DIR = 'testdir/migrations';
+    });
+
+    test(`when 'MONGO_URI' is '${MONGO_URI}`, () => {
+
+      const getMongoUriMock = vi.fn(() => MONGO_URI);
+      const mongoOptionsMock = vi.fn();
+
+      // mock for mongoose-utils
+      mockRequire('../src/server/util/mongoose-utils', {
+        getMongoUri: getMongoUriMock,
+        mongoOptions: mongoOptionsMock,
+      });
+
+      // use reRequire to avoid using module cache
+      const { mongodb, migrationsDir, changelogCollectionName } = reRequire('./migrate-mongo-config');
+
+      mockRequire.stop('../src/server/util/mongoose-utils');
+
+      // expect(getMongoUriMock).toHaveBeenCalledOnce();
+      expect(mongodb.url).toBe(MONGO_URI);
+      expect(mongodb.databaseName).toBe(expectedDbName);
+      expect(mongodb.options).toBe(mongoOptionsMock);
+      expect(migrationsDir).toBe('testdir/migrations');
+      expect(changelogCollectionName).toBe('migrations');
+    });
+  });
+
+});

+ 0 - 20
apps/app/jest.config.js

@@ -13,26 +13,6 @@ module.exports = {
   moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
 
   projects: [
-    {
-      displayName: 'unit',
-
-      transform: {
-        '^.+\\.(t|j)sx?$': '@swc/jest',
-      },
-      // transform ESM to CJS (includes all packages in node_modules)
-      transformIgnorePatterns: [],
-
-      rootDir: '.',
-      roots: ['<rootDir>'],
-      testMatch: ['<rootDir>/test/unit/**/*.test.ts', '<rootDir>/test/unit/**/*.test.js'],
-
-      testEnvironment: 'node',
-
-      // Automatically clear mock calls and instances between every test
-      clearMocks: true,
-      moduleNameMapper: MODULE_NAME_MAPPING,
-
-    },
     {
       displayName: 'server',
 

+ 1 - 1
apps/app/package.json

@@ -35,7 +35,7 @@
     "prelint:swagger2openapi": "yarn openapi:v3",
     "test": "run-p test:*",
     "test:jest": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=4096\" jest --logHeapUsage",
-    "test:vitest": "cross-env NODE_ENV=test vitest run src",
+    "test:vitest": "cross-env NODE_ENV=test vitest run config src --coverage",
     "jest:run": "cross-env NODE_ENV=test jest --passWithNoTests -- ",
     "reg:run": "reg-suit run",
     "//// misc": "",

+ 1 - 3
apps/app/src/server/console.js

@@ -4,7 +4,7 @@ const repl = require('repl');
 
 const mongoose = require('mongoose');
 
-const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = require('~/server/util/mongoose-utils');
+const { getMongoUri, mongoOptions } = require('~/server/util/mongoose-utils');
 
 const models = require('./models');
 
@@ -34,8 +34,6 @@ fs.readFile(replHistoryPath, 'utf8', (err, data) => {
 replServer.context.mongoose = mongoose;
 replServer.context.models = models;
 
-initMongooseGlobalSettings();
-
 mongoose.connect(getMongoUri(), mongoOptions)
   .then(() => {
     replServer.context.db = mongoose.connection.db;

+ 3 - 1
apps/app/src/server/crowi/express-init.js

@@ -5,6 +5,8 @@ import qs from 'qs';
 import loggerFactory from '~/utils/logger';
 import { resolveFromRoot } from '~/utils/project-dir-utils';
 
+import registerSafeRedirectFactory from '../middlewares/safe-redirect';
+
 const logger = loggerFactory('growi:crowi:express-init');
 
 module.exports = function(crowi, app) {
@@ -22,7 +24,7 @@ module.exports = function(crowi, app) {
   const mongoSanitize = require('express-mongo-sanitize');
 
   const promster = require('../middlewares/promster')(crowi, app);
-  const registerSafeRedirect = require('../middlewares/safe-redirect')();
+  const registerSafeRedirect = registerSafeRedirectFactory();
   const injectCurrentuserToLocalvars = require('../middlewares/inject-currentuser-to-localvars')();
   const autoReconnectToS2sMsgServer = require('../middlewares/auto-reconnect-to-s2s-msg-server')(crowi);
 

+ 1 - 3
apps/app/src/server/crowi/index.js

@@ -37,7 +37,7 @@ import { PluginService } from '../service/plugin';
 import SearchService from '../service/search';
 import { SlackIntegrationService } from '../service/slack-integration';
 import { UserNotificationService } from '../service/user-notification';
-import { initMongooseGlobalSettings, getMongoUri, mongoOptions } from '../util/mongoose-utils';
+import { getMongoUri, mongoOptions } from '../util/mongoose-utils';
 
 
 const logger = loggerFactory('growi:crowi');
@@ -224,8 +224,6 @@ Crowi.prototype.setupDatabase = function() {
   // mongoUri = mongodb://user:password@host/dbname
   const mongoUri = getMongoUri();
 
-  initMongooseGlobalSettings();
-
   return mongoose.connect(mongoUri, mongoOptions);
 };
 

+ 9 - 3
apps/app/test/unit/middlewares/exclude-read-only-user.test.ts → apps/app/src/server/middlewares/exclude-read-only-user.spec.ts

@@ -1,6 +1,12 @@
+import {
+  vi,
+  beforeEach,
+  describe, test, expect,
+} from 'vitest';
+
 import { ErrorV3 } from '@growi/core';
 
-import { excludeReadOnlyUser } from '../../../src/server/middlewares/exclude-read-only-user';
+import { excludeReadOnlyUser } from './exclude-read-only-user';
 
 describe('excludeReadOnlyUser', () => {
   let req;
@@ -12,9 +18,9 @@ describe('excludeReadOnlyUser', () => {
       user: {},
     };
     res = {
-      apiv3Err: jest.fn(),
+      apiv3Err: vi.fn(),
     };
-    next = jest.fn();
+    next = vi.fn();
   });
 
   test('should call next if user is not found', () => {

+ 23 - 30
apps/app/test/unit/middlewares/safe-redirect.test.js → apps/app/src/server/middlewares/safe-redirect.spec.ts

@@ -1,105 +1,98 @@
-/* eslint-disable arrow-body-style */
+import {
+  vi,
+  describe, test, expect,
+} from 'vitest';
 
-describe('safeRedirect', () => {
-  let registerSafeRedirect;
+import type { Request } from 'express';
+
+import registerSafeRedirectFactory, { type ResWithSafeRedirect } from './safe-redirect';
 
+describe('safeRedirect', () => {
   const whitelistOfHosts = [
     'white1.example.com:8080',
     'white2.example.com',
   ];
-
-  beforeEach(async() => {
-    registerSafeRedirect = require('~/server/middlewares/safe-redirect')(whitelistOfHosts);
-  });
+  const registerSafeRedirect = registerSafeRedirectFactory(whitelistOfHosts);
 
   describe('res.safeRedirect', () => {
     // setup req/res/next
+    const getFunc = vi.fn().mockReturnValue('example.com');
     const req = {
       protocol: 'http',
       hostname: 'example.com',
-      get: jest.fn().mockReturnValue('example.com'),
-    };
+      get: getFunc,
+    } as any as Request;
+
+    const redirect = vi.fn();
     const res = {
-      redirect: jest.fn().mockReturnValue('redirect'),
-    };
-    const next = jest.fn();
+      redirect,
+    } as any as ResWithSafeRedirect;
+    const next = vi.fn();
 
     test('redirects to \'/\' because specified url causes open redirect vulnerability', () => {
       registerSafeRedirect(req, res, next);
 
-      const result = res.safeRedirect('//evil.example.com');
+      res.safeRedirect('//evil.example.com');
 
       expect(next).toHaveBeenCalledTimes(1);
-      expect(req.get).toHaveBeenCalledTimes(1);
       expect(req.get).toHaveBeenCalledWith('host');
       expect(res.redirect).toHaveBeenCalledTimes(1);
       expect(res.redirect).toHaveBeenCalledWith('/');
-      expect(result).toBe('redirect');
     });
 
     test('redirects to \'/\' because specified host without port is not in whitelist', () => {
       registerSafeRedirect(req, res, next);
 
-      const result = res.safeRedirect('http://white1.example.com/path/to/page');
+      res.safeRedirect('http://white1.example.com/path/to/page');
 
       expect(next).toHaveBeenCalledTimes(1);
-      expect(req.get).toHaveBeenCalledTimes(1);
       expect(req.get).toHaveBeenCalledWith('host');
       expect(res.redirect).toHaveBeenCalledTimes(1);
       expect(res.redirect).toHaveBeenCalledWith('/');
-      expect(result).toBe('redirect');
     });
 
     test('redirects to the specified local url', () => {
       registerSafeRedirect(req, res, next);
 
-      const result = res.safeRedirect('/path/to/page');
+      res.safeRedirect('/path/to/page');
 
       expect(next).toHaveBeenCalledTimes(1);
-      expect(req.get).toHaveBeenCalledTimes(1);
       expect(req.get).toHaveBeenCalledWith('host');
       expect(res.redirect).toHaveBeenCalledTimes(1);
       expect(res.redirect).toHaveBeenCalledWith('http://example.com/path/to/page');
-      expect(result).toBe('redirect');
     });
 
     test('redirects to the specified local url (fqdn)', () => {
       registerSafeRedirect(req, res, next);
 
-      const result = res.safeRedirect('http://example.com/path/to/page');
+      res.safeRedirect('http://example.com/path/to/page');
 
       expect(next).toHaveBeenCalledTimes(1);
-      expect(req.get).toHaveBeenCalledTimes(1);
       expect(req.get).toHaveBeenCalledWith('host');
       expect(res.redirect).toHaveBeenCalledTimes(1);
       expect(res.redirect).toHaveBeenCalledWith('http://example.com/path/to/page');
-      expect(result).toBe('redirect');
     });
 
     test('redirects to the specified whitelisted url (white1.example.com:8080)', () => {
       registerSafeRedirect(req, res, next);
 
-      const result = res.safeRedirect('http://white1.example.com:8080/path/to/page');
+      res.safeRedirect('http://white1.example.com:8080/path/to/page');
 
       expect(next).toHaveBeenCalledTimes(1);
-      expect(req.get).toHaveBeenCalledTimes(1);
       expect(req.get).toHaveBeenCalledWith('host');
       expect(res.redirect).toHaveBeenCalledTimes(1);
       expect(res.redirect).toHaveBeenCalledWith('http://white1.example.com:8080/path/to/page');
-      expect(result).toBe('redirect');
     });
 
     test('redirects to the specified whitelisted url (white2.example.com:8080)', () => {
       registerSafeRedirect(req, res, next);
 
-      const result = res.safeRedirect('http://white2.example.com:8080/path/to/page');
+      res.safeRedirect('http://white2.example.com:8080/path/to/page');
 
       expect(next).toHaveBeenCalledTimes(1);
-      expect(req.get).toHaveBeenCalledTimes(1);
       expect(req.get).toHaveBeenCalledWith('host');
       expect(res.redirect).toHaveBeenCalledTimes(1);
       expect(res.redirect).toHaveBeenCalledWith('http://white2.example.com:8080/path/to/page');
-      expect(result).toBe('redirect');
     });
 
   });

+ 6 - 4
apps/app/src/server/middlewares/safe-redirect.ts

@@ -4,7 +4,7 @@
  * Usage: app.use(require('middlewares/safe-redirect')(['example.com', 'some.example.com:8080']))
  */
 
-import {
+import type {
   Request, Response, NextFunction,
 } from 'express';
 
@@ -31,13 +31,13 @@ function isInWhitelist(whitelistOfHosts: string[], redirectToFqdn: string): bool
 }
 
 
-type ResWithSafeRedirect = Response & {
+export type ResWithSafeRedirect = Response & {
   safeRedirect: (redirectTo?: string) => void,
 }
 
-module.exports = (whitelistOfHosts: string[]) => {
+const factory = (whitelistOfHosts: string[]) => {
 
-  return function(req: Request, res: ResWithSafeRedirect, next: NextFunction) {
+  return (req: Request, res: ResWithSafeRedirect, next: NextFunction): void => {
 
     // extend res object
     res.safeRedirect = function(redirectTo?: string) {
@@ -75,3 +75,5 @@ module.exports = (whitelistOfHosts: string[]) => {
   };
 
 };
+
+export default factory;

+ 1 - 5
apps/app/src/server/util/mongoose-utils.ts

@@ -1,16 +1,12 @@
 import mongoose from 'mongoose';
 import type {
-  Model, Document, Schema, ConnectOptions,
+  Model, Document, ConnectOptions,
 } from 'mongoose';
 
 // suppress DeprecationWarning: current Server Discovery and Monitoring engine is deprecated, and will be removed in a future version
 type ConnectionOptionsExtend = {
   useUnifiedTopology: boolean
 }
-// No More Deprecation Warning Options
-// Removed useFindAndModify and useCreateIndex option
-// see: https://mongoosejs.com/docs/migrating_to_6.html#no-more-deprecation-warning-options
-export const initMongooseGlobalSettings = (): void => {};
 
 export const getMongoUri = (): string => {
   const { env } = process;

+ 18 - 1
apps/app/src/services/renderer/rehype-plugins/relative-links.spec.ts

@@ -10,6 +10,22 @@ import { relativeLinks } from './relative-links';
 
 describe('relativeLinks', () => {
 
+  test('do nothing when the options does not have pagePath', () => {
+    // setup
+    const processor = unified()
+      .use(parse)
+      .use(remarkRehype)
+      .use(relativeLinks, {});
+
+    // when
+    const mdastTree = processor.parse('[link](/Sandbox)');
+    const hastTree = processor.runSync(mdastTree) as HastNode;
+
+    // then
+    const anchorElement = select('a', hastTree);
+    expect(anchorElement?.properties?.href).toBe('/Sandbox');
+  });
+
   test.concurrent.each`
     originalHref
       ${'http://example.com/Sandbox'}
@@ -17,10 +33,11 @@ describe('relativeLinks', () => {
     `('leaves the original href \'$originalHref\' as-is', ({ originalHref }) => {
 
     // setup
+    const pagePath = '/foo/bar/baz';
     const processor = unified()
       .use(parse)
       .use(remarkRehype)
-      .use(relativeLinks, {});
+      .use(relativeLinks, { pagePath });
 
     // when
     const mdastTree = processor.parse(`[link](${originalHref})`);

+ 4 - 3
apps/app/src/services/renderer/rehype-plugins/relative-links.ts

@@ -1,7 +1,10 @@
+import assert from 'assert';
+
 import { selectAll, type HastNode, type Element } from 'hast-util-select';
 import isAbsolute from 'is-absolute-url';
 import type { Plugin } from 'unified';
 
+
 export type IAnchorsSelector = (node: HastNode) => Element[];
 export type IUrlResolver = (relativeHref: string, basePath: string) => URL;
 
@@ -43,9 +46,7 @@ export const relativeLinks: Plugin<[RelativeLinksPluginParams]> = (options = {})
     const anchors = anchorsSelector(tree as HastNode);
 
     anchors.forEach((anchor) => {
-      if (anchor.properties == null) {
-        return;
-      }
+      assert(anchor.properties != null);
 
       const href = anchor.properties.href;
       if (href == null || typeof href !== 'string' || isAbsolute(href) || isAnchorLink(href)) {

+ 6 - 3
apps/app/test/unit/utils/page-delete-config.test.ts → apps/app/src/utils/page-delete-config.test.ts

@@ -1,8 +1,11 @@
-import { PageDeleteConfigValue } from '../../../src/interfaces/page-delete-config';
-import { validateDeleteConfigs } from '../../../src/utils/page-delete-config';
+import { describe, test } from 'vitest';
+
+import { PageDeleteConfigValue } from '../interfaces/page-delete-config';
+
+import { validateDeleteConfigs } from './page-delete-config';
 
 describe('validateDeleteConfigs utility function', () => {
-  test('Should validate delete configs', () => {
+  test('Should validate delete configs', ({ expect }) => {
     const Anyone = PageDeleteConfigValue.Anyone;
     const AdminAndAuthor = PageDeleteConfigValue.AdminAndAuthor;
     const AdminOnly = PageDeleteConfigValue.AdminOnly;

+ 3 - 1
apps/app/test/unit/utils/to-array-from-csv.test.js → apps/app/src/utils/to-array-from-csv.spec.ts

@@ -1,4 +1,6 @@
-import { toArrayFromCsv } from '~/utils/to-array-from-csv';
+import { describe, test, expect } from 'vitest';
+
+import { toArrayFromCsv } from './to-array-from-csv';
 
 describe('To array from csv', () => {
 

+ 16 - 0
apps/app/test/integration/.eslintrc.js

@@ -0,0 +1,16 @@
+module.exports = {
+  extends: [
+    'plugin:jest/recommended',
+  ],
+  env: {
+    'jest/globals': true,
+  },
+  plugins: ['jest'],
+  rules: {
+    'jest/no-done-callback': ['warn'],
+    'jest/no-standalone-expect': [
+      'error',
+      { additionalTestBlockFunctions: ['each.test'] },
+    ],
+  },
+};

+ 1 - 10
apps/app/test/integration/global-setup.js

@@ -1,13 +1,6 @@
-/** **********************************************************
- *                           Caution
- *
- * Module aliases by compilerOptions.paths in tsconfig.json
- * are NOT available in setup scripts
- *********************************************************** */
-
 import mongoose from 'mongoose';
 
-import { initMongooseGlobalSettings, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 
 // check env
 if (process.env.NODE_ENV !== 'test') {
@@ -15,8 +8,6 @@ if (process.env.NODE_ENV !== 'test') {
 }
 
 module.exports = async() => {
-  initMongooseGlobalSettings();
-
   mongoose.connect(getMongoUri(), mongoOptions);
 
   // drop database

+ 2 - 2
apps/app/test/integration/migrations/20210913153942-migrate-slack-app-integration-schema.test.ts

@@ -1,7 +1,7 @@
-import mongoose from 'mongoose';
 import { Collection } from 'mongodb';
+import mongoose from 'mongoose';
 
-const migrate = require('~/migrations/20210913153942-migrate-slack-app-integration-schema');
+import migrate from '~/migrations/20210913153942-migrate-slack-app-integration-schema';
 
 describe('migrate-slack-app-integration-schema', () => {
 

+ 2 - 2
apps/app/test/integration/service/questionnaire-cron.test.ts

@@ -1,3 +1,5 @@
+// eslint-disable-next-line no-restricted-imports
+import axios from 'axios';
 import mongoose from 'mongoose';
 
 import { IProactiveQuestionnaireAnswer } from '../../../src/features/questionnaire/interfaces/proactive-questionnaire-answer';
@@ -9,8 +11,6 @@ import QuestionnaireAnswerStatus from '../../../src/features/questionnaire/serve
 import QuestionnaireOrder from '../../../src/features/questionnaire/server/models/questionnaire-order';
 import { getInstance } from '../setup-crowi';
 
-const axios = require('axios').default;
-
 const spyAxiosGet = jest.spyOn<typeof axios, 'get'>(
   axios,
   'get',

+ 1 - 2
apps/app/test/integration/setup.js

@@ -8,14 +8,13 @@
 const gc = require('expose-gc/function');
 const mongoose = require('mongoose');
 
-const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = require('~/server/util/mongoose-utils');
+const { getMongoUri, mongoOptions } = require('~/server/util/mongoose-utils');
 
 mongoose.Promise = global.Promise;
 
 jest.setTimeout(30000); // default 5000
 
 beforeAll(async() => {
-  initMongooseGlobalSettings();
   await mongoose.connect(getMongoUri(), mongoOptions);
 });
 

+ 0 - 71
apps/app/test/unit/migrate-mongo-config.test.js

@@ -1,71 +0,0 @@
-describe('config/migrate-mongo-config.js', () => {
-
-  beforeEach(async() => {
-    jest.resetModules();
-  });
-
-  test('throws an error when MIGRATIONS_DIR is not set', () => {
-
-    const initMongooseGlobalSettingsMock = jest.fn();
-
-    // mock for mongoose-utils
-    jest.doMock('../../src/server/util/mongoose-utils', () => {
-      return {
-        initMongooseGlobalSettings: initMongooseGlobalSettingsMock,
-      };
-    });
-
-    const requireConfig = () => {
-      require('../../config/migrate-mongo-config');
-    };
-
-    expect(requireConfig).toThrow('An env var MIGRATIONS_DIR must be set.');
-
-    jest.dontMock('../../src/server/util/mongoose-utils');
-
-    expect(initMongooseGlobalSettingsMock).not.toHaveBeenCalled();
-  });
-
-  /* eslint-disable indent */
-  describe.each`
-    MONGO_URI                                         | expectedDbName
-    ${'mongodb://example.com/growi'}                  | ${'growi'}
-    ${'mongodb://user:pass@example.com/growi'}        | ${'growi'}
-    ${'mongodb://example.com/growi?replicaSet=mySet'} | ${'growi'}
-  `('returns', ({ MONGO_URI, expectedDbName }) => {
-
-    beforeEach(async() => {
-      process.env.MIGRATIONS_DIR = 'testdir/migrations';
-    });
-
-    test(`when 'MONGO_URI' is '${MONGO_URI}`, () => {
-
-      const initMongooseGlobalSettingsMock = jest.fn();
-      const mongoOptionsMock = jest.fn();
-
-      // mock for mongoose-utils
-      jest.doMock('../../src/server/util/mongoose-utils', () => {
-        return {
-          initMongooseGlobalSettings: initMongooseGlobalSettingsMock,
-          getMongoUri: () => {
-            return MONGO_URI;
-          },
-          mongoOptions: mongoOptionsMock,
-        };
-      });
-
-      const { mongodb, migrationsDir, changelogCollectionName } = require('../../config/migrate-mongo-config');
-
-      jest.dontMock('../../src/server/util/mongoose-utils');
-
-      expect(initMongooseGlobalSettingsMock).toHaveBeenCalledTimes(1);
-      expect(mongodb.url).toBe(MONGO_URI);
-      expect(mongodb.databaseName).toBe(expectedDbName);
-      expect(mongodb.options).toBe(mongoOptionsMock);
-      expect(migrationsDir).toBe('testdir/migrations');
-      expect(changelogCollectionName).toBe('migrations');
-    });
-  });
-  /* eslint-enable indent */
-
-});

+ 13 - 0
apps/app/vitest.config.ts

@@ -0,0 +1,13 @@
+import tsconfigPaths from 'vite-tsconfig-paths';
+import { defineProject } from 'vitest/config';
+
+export default defineProject({
+  plugins: [
+    tsconfigPaths(),
+  ],
+  test: {
+    environment: 'node',
+    exclude: ['**/test/**'],
+    clearMocks: true,
+  },
+});

+ 6 - 1
package.json

@@ -68,6 +68,8 @@
     "@typescript-eslint/eslint-plugin": "^5.59.7",
     "@typescript-eslint/parser": "^5.59.7",
     "@vitejs/plugin-react": "^3.1.0",
+    "@vitest/coverage-c8": "^0.31.1",
+    "@vitest/ui": "^0.31.1",
     "cypress": "^12.0.1",
     "cypress-wait-until": "^1.7.2",
     "eslint": "^8.41.0",
@@ -79,10 +81,12 @@
     "eslint-plugin-react": "^7.30.1",
     "eslint-plugin-react-hooks": "^4.6.0",
     "eslint-plugin-rulesdir": "^0.2.2",
+    "eslint-plugin-vitest": "^0.2.3",
     "glob": "^8.1.0",
     "jest": "^28.1.3",
     "jest-date-mock": "^1.0.8",
     "jest-localstorage-mock": "^2.4.14",
+    "mock-require": "^3.0.3",
     "postcss": "^8.4.5",
     "postcss-scss": "^4.0.3",
     "reg-keygen-git-hash-plugin": "^0.11.1",
@@ -97,8 +101,9 @@
     "tsconfig-paths": "^3.9.0",
     "typescript": "~4.9",
     "unplugin-swc": "^1.3.2",
-    "vite": "^4.2.2",
+    "vite": "^4.3.8",
     "vite-plugin-dts": "^2.0.0-beta.0",
+    "vite-tsconfig-paths": "^4.2.0",
     "vitest": "^0.31.1"
   },
   "engines": {

+ 5 - 0
packages/core/.eslintrc.js

@@ -0,0 +1,5 @@
+module.exports = {
+  extends: [
+    'plugin:vitest/recommended',
+  ],
+};

+ 0 - 63
packages/core/jest.config.js

@@ -1,63 +0,0 @@
-// For a detailed explanation regarding each configuration property, visit:
-// https://jestjs.io/docs/en/configuration.html
-
-const MODULE_NAME_MAPPING = {
-  '^~/(.+)$': '<rootDir>/src/$1',
-};
-
-module.exports = {
-
-  transform: {
-    '^.+\\.(js|jsx|ts|tsx)$': '@swc/jest',
-  },
-  transformIgnorePatterns: [],
-
-  moduleNameMapper: MODULE_NAME_MAPPING,
-
-  // Automatically clear mock calls and instances between every test
-  clearMocks: true,
-
-  // Indicates whether the coverage information should be collected while executing the test
-  collectCoverage: true,
-
-  // An array of glob patterns indicating a set of files for which coverage information should be collected
-  // collectCoverageFrom: undefined,
-
-  // The directory where Jest should output its coverage files
-  coverageDirectory: 'coverage',
-
-  // An array of regexp pattern strings used to skip coverage collection
-  coveragePathIgnorePatterns: [
-    '/node_modules/',
-  ],
-
-  // An object that configures minimum threshold enforcement for coverage results
-  // TODO: activate -- 2020.03.24 Yuki Takei
-  // coverageThreshold: {
-  //   global: {
-  //     branches: 70,
-  //     functions: 70,
-  //     lines: 70,
-  //     statements: 70,
-  //   },
-  // },
-
-  // An array of file extensions your modules use
-  moduleFileExtensions: [
-    'js',
-    'json',
-    'jsx',
-    'ts',
-    'tsx',
-    'node',
-  ],
-
-  // The test environment that will be used for testing
-  testEnvironment: 'node',
-
-  // The glob patterns Jest uses to detect test files
-  testMatch: [
-    '**/test/**/__tests__/**/*.[jt]s?(x)',
-    '**/test/**/?(*.)+(spec|test).[jt]s?(x)',
-  ],
-};

+ 1 - 1
packages/core/package.json

@@ -20,7 +20,7 @@
     "lint:js": "yarn eslint **/*.{js,ts}",
     "lint:typecheck": "tsc",
     "lint": "npm-run-all -p lint:*",
-    "test": "jest --verbose"
+    "test": "vitest run --coverage"
   },
   "// comments for dependencies": {
     "escape-string-regexp": "5.0.0 or above exports only ESM"

+ 0 - 1
packages/core/src/index.ts

@@ -15,5 +15,4 @@ export * from './interfaces/vite';
 export * from './models/devided-page-path';
 export * from './models/vo/error-apiv3';
 export * from './plugin';
-export * from './service/localstorage-manager';
 export * from './utils';

+ 6 - 10
packages/core/test/plugin/util/option-parser.test.js → packages/core/src/plugin/util/option-parser.spec.ts

@@ -1,23 +1,19 @@
-import each from 'jest-each';
+import { describe, test, expect } from 'vitest';
 
-import { OptionParser } from '~/plugin/util/option-parser';
+import { OptionParser } from './option-parser';
 
 describe('option-parser', () => {
 
-  test('.parseRange(null) returns null', () => {
-    expect(OptionParser.parseRange(null)).toBeNull();
-  });
-
-  each`
+  test.concurrent.each`
     arg
     ${'aaa'}
     ${'5++2'}
     ${'5:+2'}
-  `.test('.parseRange(\'$arg\') returns null', ({ arg }) => {
+  `('.parseRange(\'$arg\') returns null', ({ arg }) => {
     expect(OptionParser.parseRange(arg)).toBeNull();
   });
 
-  each`
+  test.concurrent.each`
     arg       | start | end
     ${'1'}    | ${1} | ${1}
     ${'2:1'}  | ${2} | ${1}
@@ -25,7 +21,7 @@ describe('option-parser', () => {
     ${'10:-3'}   | ${10} | ${-3}
     ${'5+2'}   | ${5} | ${7}
     ${'5+'}   | ${5} | ${5}
-  `.test('.parseRange(\'$arg\') returns { start: $start, end : $end }', ({ arg, start, end }) => {
+  `('.parseRange(\'$arg\') returns { start: $start, end : $end }', ({ arg, start, end }) => {
     expect(OptionParser.parseRange(arg)).toEqual({ start, end });
   });
 

+ 0 - 54
packages/core/src/service/localstorage-manager.js

@@ -1,54 +0,0 @@
-let _instance = null;
-export class LocalStorageManager {
-
-  static getInstance() {
-    if (_instance == null) {
-      _instance = new LocalStorageManager();
-    }
-
-    return _instance;
-  }
-
-  /**
-   * retrieve and return parsed JSON object
-   * @param {string} namespace
-   * @param {string} key
-   * @returns {object}
-   */
-  retrieveFromSessionStorage(namespace, key) {
-    const item = JSON.parse(sessionStorage.getItem(namespace)) || {};
-    if (key != null) {
-      return item[key];
-    }
-    return item;
-  }
-
-  /**
-   * save JavaScript object as stringified JSON object
-   *
-   * @param {string} namespace
-   * @param {string | object} cacheObjOrKey cache object or key (if third param is undefined)
-   * @param {object} cacheObj
-   */
-  saveToSessionStorage(namespace, cacheObjOrKey, cacheObj) {
-    let item = JSON.parse(sessionStorage.getItem(namespace)) || {};
-    if (cacheObj !== undefined) {
-      const key = cacheObjOrKey;
-      item[key] = cacheObj;
-    }
-    else {
-      item = cacheObjOrKey;
-    }
-    sessionStorage.setItem(namespace, JSON.stringify(item));
-  }
-
-  /**
-   * clear all state caches
-   *
-   * @param {string} namespace
-   */
-  clearAllStateCaches(namespace) {
-    sessionStorage.removeItem(namespace);
-  }
-
-}

+ 23 - 0
packages/core/src/utils/env-utils.spec.ts

@@ -0,0 +1,23 @@
+import { describe, it, expect } from 'vitest';
+
+import { toBoolean } from './env-utils';
+
+
+describe('env-utils', () => {
+  describe('.toBoolean', () => {
+
+    it('should convert to true', () => {
+      expect(toBoolean('true')).toBe(true);
+      expect(toBoolean('True')).toBe(true);
+      expect(toBoolean('1')).toBe(true);
+    });
+
+    it('should convert to false', () => {
+      // expect(toBoolean(undefined)).toBe(false);
+      // expect(toBoolean(null)).toBe(false);
+      expect(toBoolean('false')).toBe(false);
+      expect(toBoolean('0')).toBe(false);
+    });
+
+  });
+});

+ 4 - 2
packages/core/test/util/objectid-utils.test.ts → packages/core/src/utils/objectid-utils.spec.ts

@@ -1,11 +1,13 @@
+import { describe, test, expect } from 'vitest';
+
 import ObjectId from 'bson-objectid';
 
-import { isValidObjectId } from '~/utils/objectid-utils';
+import { isValidObjectId } from './objectid-utils';
 
 describe('isValidObjectId', () => {
 
   /* eslint-disable indent */
-  describe.each`
+  describe.concurrent.each`
     arg                                           | expected
     ${undefined}                                  | ${false}
     ${null}                                       | ${false}

+ 16 - 51
packages/core/test/util/page-path-utils.test.js → packages/core/src/utils/page-path-utils/index.spec.ts

@@ -1,27 +1,10 @@
-import {
-  isTopPage, isMovablePage, convertToNewAffiliationPath, isCreatablePage, omitDuplicateAreaPathFromPaths,
-} from '~/utils/page-path-utils';
+import { describe, test, expect } from 'vitest';
 
-describe('TopPage Path test', () => {
-  test('Path is only "/"', () => {
-    const result = isTopPage('/');
-    expect(result).toBe(true);
-  });
-  test('Path is not match string', () => {
-    const result = isTopPage('/test');
-    expect(result).toBe(false);
-  });
-  test('Path is integer', () => {
-    const result = isTopPage(1);
-    expect(result).toBe(false);
-  });
-  test('Path is null', () => {
-    const result = isTopPage(null);
-    expect(result).toBe(false);
-  });
-});
+import {
+  isMovablePage, convertToNewAffiliationPath, isCreatablePage, omitDuplicateAreaPathFromPaths,
+} from './index';
 
-describe('isMovablePage test', () => {
+describe.concurrent('isMovablePage test', () => {
   test('should decide deletable or not', () => {
     expect(isMovablePage('/')).toBeFalsy();
     expect(isMovablePage('/hoge')).toBeTruthy();
@@ -32,42 +15,24 @@ describe('isMovablePage test', () => {
   });
 });
 
-describe('convertToNewAffiliationPath test', () => {
-  test('Child path is not converted normally', () => {
+describe.concurrent('convertToNewAffiliationPath test', () => {
+  test.concurrent('Child path is not converted normally', () => {
     const result = convertToNewAffiliationPath('parent/', 'parent2/', 'parent/child');
     expect(result).toBe('parent2/child');
   });
 
-  test('Parent path is not converted normally', () => {
+  test.concurrent('Parent path is not converted normally', () => {
     const result = convertToNewAffiliationPath('parent/', 'parent3/', 'parent/child');
     expect(result === 'parent/child').toBe(false);
   });
 
-  test('Parent and Child path names are switched unexpectedly', () => {
+  test.concurrent('Parent and Child path names are switched unexpectedly', () => {
     const result = convertToNewAffiliationPath('parent/', 'parent4/', 'parent/child');
     expect(result === 'child/parent4').toBe(false);
   });
-
-  test('Child path is null', () => {
-    expect(() => {
-      convertToNewAffiliationPath('parent/', 'parent5/', null);
-    }).toThrow();
-  });
-
-  test('Old parent path is null', () => {
-    expect(() => {
-      convertToNewAffiliationPath(null, 'parent5/', 'child');
-    }).toThrow();
-  });
-
-  test('New parent path is null', () => {
-    expect(() => {
-      convertToNewAffiliationPath('parent/', null, 'child');
-    }).toThrow();
-  });
 });
 
-describe('isCreatablePage test', () => {
+describe.concurrent('isCreatablePage test', () => {
   test('should decide creatable or not', () => {
     expect(isCreatablePage('/hoge')).toBeTruthy();
 
@@ -125,29 +90,29 @@ describe('isCreatablePage test', () => {
     }
   });
 
-  describe('Test omitDuplicateAreaPathFromPaths', () => {
-    test('Should not omit when all paths are at unique area', () => {
+  describe.concurrent('Test omitDuplicateAreaPathFromPaths', () => {
+    test.concurrent('Should not omit when all paths are at unique area', () => {
       const paths = ['/A', '/B/A', '/C/B/A', '/D'];
       const expectedPaths = paths;
 
-      expect(omitDuplicateAreaPathFromPaths(paths)).toStrictEqual(paths);
+      expect(omitDuplicateAreaPathFromPaths(paths)).toStrictEqual(expectedPaths);
     });
 
-    test('Should omit when some paths are at duplicated area', () => {
+    test.concurrent('Should omit when some paths are at duplicated area', () => {
       const paths = ['/A', '/A/A', '/A/B/A', '/B', '/B/A', '/AA'];
       const expectedPaths = ['/A', '/B', '/AA'];
 
       expect(omitDuplicateAreaPathFromPaths(paths)).toStrictEqual(expectedPaths);
     });
 
-    test('Should omit when some long paths are at duplicated area', () => {
+    test.concurrent('Should omit when some long paths are at duplicated area', () => {
       const paths = ['/A/B/C', '/A/B/C/D', '/A/B/C/D/E'];
       const expectedPaths = ['/A/B/C'];
 
       expect(omitDuplicateAreaPathFromPaths(paths)).toStrictEqual(expectedPaths);
     });
 
-    test('Should omit when some long paths are at duplicated area [case insensitivity]', () => {
+    test.concurrent('Should omit when some long paths are at duplicated area [case insensitivity]', () => {
       const paths = ['/a/B/C', '/A/b/C/D', '/A/B/c/D/E'];
       const expectedPaths = ['/a/B/C'];
 

+ 14 - 0
packages/core/src/utils/page-path-utils/is-top-page.spec.ts

@@ -0,0 +1,14 @@
+import { describe, test, expect } from 'vitest';
+
+import { isTopPage } from './is-top-page';
+
+describe('TopPage Path test', () => {
+  test.concurrent('Path is only "/"', () => {
+    const result = isTopPage('/');
+    expect(result).toBe(true);
+  });
+  test.concurrent('Path is not match string', () => {
+    const result = isTopPage('/test');
+    expect(result).toBe(false);
+  });
+});

+ 8 - 6
packages/core/test/util/path-utils.test.js → packages/core/src/utils/path-utils.spec.ts

@@ -1,25 +1,27 @@
-import * as pathUtils from '~/utils/path-utils';
+import { describe, test, expect } from 'vitest';
+
+import * as pathUtils from './path-utils';
 
 
 describe('page-utils', () => {
   describe('.normalizePath', () => {
-    test('should return the root path with empty string', () => {
+    test.concurrent('should return the root path with empty string', () => {
       expect(pathUtils.normalizePath('')).toBe('/');
     });
 
-    test('should return the root path as is', () => {
+    test.concurrent('should return the root path as is', () => {
       expect(pathUtils.normalizePath('/')).toBe('/');
     });
 
-    test('should add heading slash', () => {
+    test.concurrent('should add heading slash', () => {
       expect(pathUtils.normalizePath('hoge/fuga')).toBe('/hoge/fuga');
     });
 
-    test('should remove trailing slash', () => {
+    test.concurrent('should remove trailing slash', () => {
       expect(pathUtils.normalizePath('/hoge/fuga/')).toBe('/hoge/fuga');
     });
 
-    test('should remove unnecessary slashes', () => {
+    test.concurrent('should remove unnecessary slashes', () => {
       expect(pathUtils.normalizePath('//hoge/fuga//')).toBe('/hoge/fuga');
     });
   });

+ 0 - 86
packages/core/test/service/localstorage-manager.test.js

@@ -1,86 +0,0 @@
-// eslint-disable-next-line import/no-unresolved
-import 'jest-localstorage-mock';
-
-import { LocalStorageManager } from '~/service/localstorage-manager';
-
-let localStorageManager = null;
-
-beforeEach(() => {
-  localStorageManager = LocalStorageManager.getInstance();
-
-  // == init jest-localstorage-mock
-  // reset the storage
-  localStorage.clear();
-  sessionStorage.clear();
-  // set preset data
-  sessionStorage.setItem('localstorage-manager-test', JSON.stringify({ foo: 'bar' }));
-  // reset mocks
-  localStorage.setItem.mockClear();
-  sessionStorage.setItem.mockClear();
-});
-
-describe('LocalStorageManager', () => {
-  test('.getInstance() returns the same instance', () => {
-    expect(LocalStorageManager.getInstance()).toBe(localStorageManager);
-  });
-
-  test('.retrieveFromSessionStorage() with unknown namespace returns the empty object', () => {
-    const item = localStorageManager.retrieveFromSessionStorage('unknown namespace');
-    expect(item).toEqual({});
-  });
-
-  test('.retrieveFromSessionStorage() without key returns the preset data', () => {
-    const item = localStorageManager.retrieveFromSessionStorage('localstorage-manager-test');
-    expect(item).toEqual({ foo: 'bar' });
-  });
-
-  test('.retrieveFromSessionStorage() with key returns the preset data', () => {
-    const item = localStorageManager.retrieveFromSessionStorage('localstorage-manager-test', 'foo');
-    expect(item).toBe('bar');
-  });
-
-  test('.saveToSessionStorage() without key works fine', () => {
-    localStorageManager.saveToSessionStorage(
-      'localstorage-manager-test',
-      { foo: { qux: 'quux' } },
-    );
-
-    expect(sessionStorage.__STORE__.length).toBe(1);
-    expect(sessionStorage.setItem)
-      .toHaveBeenLastCalledWith(
-        'localstorage-manager-test',
-        JSON.stringify({ foo: { qux: 'quux' } }),
-      );
-  });
-
-  test('.saveToSessionStorage() with key works fine', () => {
-    localStorageManager.saveToSessionStorage(
-      'localstorage-manager-test',
-      'baz',
-      { qux: 'quux' },
-    );
-
-    expect(sessionStorage.__STORE__.length).toBe(1);
-    expect(sessionStorage.setItem)
-      .toHaveBeenLastCalledWith(
-        'localstorage-manager-test',
-        JSON.stringify({ foo: 'bar', baz: { qux: 'quux' } }),
-      );
-  });
-
-  test('.saveToSessionStorage() with unknown key works fine', () => {
-    localStorageManager.saveToSessionStorage(
-      'localstorage-manager-test-unknown-key',
-      'baz',
-      { qux: 'quux' },
-    );
-
-    expect(sessionStorage.__STORE__.length).toBe(2);
-    expect(sessionStorage.setItem)
-      .toHaveBeenLastCalledWith(
-        'localstorage-manager-test-unknown-key',
-        JSON.stringify({ baz: { qux: 'quux' } }),
-      );
-  });
-
-});

+ 0 - 21
packages/core/test/util/env-utils.test.js

@@ -1,21 +0,0 @@
-import { toBoolean } from '~/utils/env-utils';
-
-
-describe('env-utils', () => {
-  describe('.toBoolean', () => {
-
-    test('should convert to true', () => {
-      expect(toBoolean('true')).toBe(true);
-      expect(toBoolean('True')).toBe(true);
-      expect(toBoolean(1)).toBe(true);
-    });
-
-    test('should convert to false', () => {
-      expect(toBoolean(undefined)).toBe(false);
-      expect(toBoolean(null)).toBe(false);
-      expect(toBoolean('false')).toBe(false);
-      expect(toBoolean(0)).toBe(false);
-    });
-
-  });
-});

+ 5 - 0
apps/app/vitest.config.unit.ts → packages/core/vitest.config.ts

@@ -1,7 +1,12 @@
+import tsconfigPaths from 'vite-tsconfig-paths';
 import { defineProject } from 'vitest/config';
 
 export default defineProject({
+  plugins: [
+    tsconfigPaths(),
+  ],
   test: {
     environment: 'node',
+    clearMocks: true,
   },
 });

+ 0 - 4
vitest.workspace.ts

@@ -1,4 +0,0 @@
-export default [
-  'apps/*/vitest.config.{e2e,unit}.ts',
-  'packages/*/vitest.config.{e2e,unit}.ts',
-];

+ 117 - 24
yarn.lock

@@ -18,6 +18,14 @@
     "@jridgewell/gen-mapping" "^0.1.0"
     "@jridgewell/trace-mapping" "^0.3.9"
 
+"@ampproject/remapping@^2.2.1":
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
+  integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
+  dependencies:
+    "@jridgewell/gen-mapping" "^0.3.0"
+    "@jridgewell/trace-mapping" "^0.3.9"
+
 "@apidevtools/json-schema-ref-parser@^9.0.6":
   version "9.0.9"
   resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b"
@@ -2594,6 +2602,15 @@
     "@jridgewell/set-array" "^1.0.0"
     "@jridgewell/sourcemap-codec" "^1.4.10"
 
+"@jridgewell/gen-mapping@^0.3.0":
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
+  integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
+  dependencies:
+    "@jridgewell/set-array" "^1.0.1"
+    "@jridgewell/sourcemap-codec" "^1.4.10"
+    "@jridgewell/trace-mapping" "^0.3.9"
+
 "@jridgewell/gen-mapping@^0.3.2":
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
@@ -4227,7 +4244,7 @@
     semver "^7.3.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/utils@5.59.7":
+"@typescript-eslint/utils@5.59.7", "@typescript-eslint/utils@^5.59.2":
   version "5.59.7"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.7.tgz#7adf068b136deae54abd9a66ba5a8780d2d0f898"
   integrity sha512-yCX9WpdQKaLufz5luG4aJbOpdXf/fjwGMcLFXZVPUz3QqLirG5QcwwnIHNf8cjLjxK4qtzTO8udUtMQSAToQnQ==
@@ -4282,6 +4299,17 @@
     magic-string "^0.27.0"
     react-refresh "^0.14.0"
 
+"@vitest/coverage-c8@^0.31.1":
+  version "0.31.1"
+  resolved "https://registry.yarnpkg.com/@vitest/coverage-c8/-/coverage-c8-0.31.1.tgz#bc242d85404e02723e89e4126bec9c727146638a"
+  integrity sha512-6TkjQpmgYez7e3dbAUoYdRXxWN81BojCmUILJwgCy39uZFG33DsQ0rSRSZC9beAEdCZTpxR63nOvd9hxDQcJ0g==
+  dependencies:
+    "@ampproject/remapping" "^2.2.1"
+    c8 "^7.13.0"
+    magic-string "^0.30.0"
+    picocolors "^1.0.0"
+    std-env "^3.3.2"
+
 "@vitest/expect@0.31.1":
   version "0.31.1"
   resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.31.1.tgz#db8cb5a14a91167b948f377b9d29442229c73747"
@@ -4317,6 +4345,19 @@
   dependencies:
     tinyspy "^2.1.0"
 
+"@vitest/ui@^0.31.1":
+  version "0.31.1"
+  resolved "https://registry.yarnpkg.com/@vitest/ui/-/ui-0.31.1.tgz#5c4245aa94f5d16f5c8831dd66ee8eedb79414cc"
+  integrity sha512-+JJ2+rvRPAVxFLNE+WJOMzOjxqYPn7V2hl00uNwid6kquD+UHTa716Z7szfNeZMLnHOHv+fxq1UgLCymvVpE5w==
+  dependencies:
+    "@vitest/utils" "0.31.1"
+    fast-glob "^3.2.12"
+    fflate "^0.7.4"
+    flatted "^3.2.7"
+    pathe "^1.1.0"
+    picocolors "^1.0.0"
+    sirv "^2.0.3"
+
 "@vitest/utils@0.31.1":
   version "0.31.1"
   resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.31.1.tgz#b810a458b37ef16931ab0d384ce79a9500f34e07"
@@ -5412,6 +5453,24 @@ c8@^7.0.0:
     yargs "^16.2.0"
     yargs-parser "^20.2.9"
 
+c8@^7.13.0:
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/c8/-/c8-7.13.0.tgz#a2a70a851278709df5a9247d62d7f3d4bcb5f2e4"
+  integrity sha512-/NL4hQTv1gBL6J6ei80zu3IiTrmePDKXKXOTLpHvcIWZTVYQlDhVWjjWvkhICylE8EwwnMVzDZugCvdx0/DIIA==
+  dependencies:
+    "@bcoe/v8-coverage" "^0.2.3"
+    "@istanbuljs/schema" "^0.1.3"
+    find-up "^5.0.0"
+    foreground-child "^2.0.0"
+    istanbul-lib-coverage "^3.2.0"
+    istanbul-lib-report "^3.0.0"
+    istanbul-reports "^3.1.4"
+    rimraf "^3.0.2"
+    test-exclude "^6.0.0"
+    v8-to-istanbul "^9.0.0"
+    yargs "^16.2.0"
+    yargs-parser "^20.2.9"
+
 cac@^6.7.14:
   version "6.7.14"
   resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959"
@@ -7798,6 +7857,13 @@ eslint-plugin-rulesdir@^0.2.2:
   resolved "https://registry.yarnpkg.com/eslint-plugin-rulesdir/-/eslint-plugin-rulesdir-0.2.2.tgz#84756ec39cd8503b1fe8af6a02a5da361e2bd076"
   integrity sha512-qhBtmrWgehAIQeMDJ+Q+PnOz1DWUZMPeVrI0wE9NZtnpIMFUfh3aPKFYt2saeMSemZRrvUtjWfYwepsC8X+mjQ==
 
+eslint-plugin-vitest@^0.2.3:
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-vitest/-/eslint-plugin-vitest-0.2.3.tgz#3524e72630231c68dc85651adf26422f8d116ceb"
+  integrity sha512-eqyiIY0Hhyp/2+AgNDDFrVhVT0hSXMbAyjFLB87E5CCHxelH9eNrJZe9qVElPPVpuJ201nQ/wmUupaO5EguxKQ==
+  dependencies:
+    "@typescript-eslint/utils" "^5.59.2"
+
 eslint-restricted-globals@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7"
@@ -8309,6 +8375,11 @@ fd-slicer@~1.1.0:
   dependencies:
     pend "~1.2.0"
 
+fflate@^0.7.4:
+  version "0.7.4"
+  resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50"
+  integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==
+
 figlet@^1.1.1:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.5.0.tgz#2db4d00a584e5155a96080632db919213c3e003c"
@@ -8478,6 +8549,11 @@ flatted@^3.1.0:
   resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561"
   integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==
 
+flatted@^3.2.7:
+  version "3.2.7"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
+  integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
+
 fn-args@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/fn-args/-/fn-args-5.0.0.tgz#7a18e105c8fb3bf0a51c30389bf16c9ebe740bb3"
@@ -8731,7 +8807,7 @@ gensync@^1.0.0-beta.2:
   resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
   integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
 
-get-caller-file@^1.0.1:
+get-caller-file@^1.0.1, get-caller-file@^1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
   integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
@@ -12574,6 +12650,14 @@ mlly@^1.2.0:
     pkg-types "^1.0.3"
     ufo "^1.1.2"
 
+mock-require@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/mock-require/-/mock-require-3.0.3.tgz#ccd544d9eae81dd576b3f219f69ec867318a1946"
+  integrity sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg==
+  dependencies:
+    get-caller-file "^1.0.2"
+    normalize-path "^2.1.1"
+
 modify-values@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
@@ -13040,7 +13124,7 @@ normalize-path@3, normalize-path@3.0.0, normalize-path@^3.0.0, normalize-path@~3
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
 
-normalize-path@^2.0.0, normalize-path@^2.0.1:
+normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
   dependencies:
@@ -15193,7 +15277,7 @@ resolve.exports@^1.1.0:
   resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9"
   integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==
 
-resolve@^1.0.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1, resolve@~1.22.1:
+resolve@^1.0.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.20.0, resolve@^1.22.0, resolve@~1.22.1:
   version "1.22.1"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
   integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
@@ -15299,13 +15383,6 @@ robust-predicates@^3.0.0:
   resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a"
   integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==
 
-rollup@^3.18.0:
-  version "3.20.6"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.20.6.tgz#53c0fd73e397269d2ce5f0ec12851457dd53cacd"
-  integrity sha512-2yEB3nQXp/tBQDN0hJScJQheXdvU2wFhh6ld7K/aiZ1vYcak6N/BKjY1QrU6BvO2JWYS8bEs14FRaxXosxy2zw==
-  optionalDependencies:
-    fsevents "~2.3.2"
-
 rollup@^3.21.0:
   version "3.22.1"
   resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.22.1.tgz#cf876bd933000aeda2f8807371448b788e094481"
@@ -15738,6 +15815,15 @@ sirv@^1.0.7:
     mrmime "^1.0.0"
     totalist "^1.0.0"
 
+sirv@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446"
+  integrity sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==
+  dependencies:
+    "@polka/url" "^1.0.0-next.20"
+    mrmime "^1.0.0"
+    totalist "^3.0.0"
+
 sisteransi@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.0.tgz#77d9622ff909080f1c19e5f4a1df0c1b0a27b88c"
@@ -16856,6 +16942,11 @@ totalist@^1.0.0:
   resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
   integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==
 
+totalist@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8"
+  integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
+
 tough-cookie@^2.3.3, tough-cookie@~2.5.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
@@ -16977,6 +17068,11 @@ tsc-alias@^1.2.9:
     globby "^11.0.2"
     normalize-path "^3.0.0"
 
+tsconfck@^2.1.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-2.1.1.tgz#9b51603d2712d1f4740fa14748ca886a2e1893e5"
+  integrity sha512-ZPCkJBKASZBmBUNqGHmRhdhM8pJYDdOXp4nRgj/O0JwUwsMq50lCDRQP/M5GBNAA0elPrq4gAeu4dkaVCuKWww==
+
 tsconfig-paths@^3.14.1, tsconfig-paths@^3.9.0:
   version "3.14.1"
   resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a"
@@ -17665,7 +17761,16 @@ vite-plugin-dts@^2.0.0-beta.0:
     magic-string "^0.29.0"
     ts-morph "17.0.1"
 
-"vite@^3.0.0 || ^4.0.0":
+vite-tsconfig-paths@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.2.0.tgz#bd2647d3eadafb65a10fc98a2ca565211f2eaf63"
+  integrity sha512-jGpus0eUy5qbbMVGiTxCL1iB9ZGN6Bd37VGLJU39kTDD6ZfULTTb1bcc5IeTWqWJKiWV5YihCaibeASPiGi8kw==
+  dependencies:
+    debug "^4.1.1"
+    globrex "^0.1.2"
+    tsconfck "^2.1.0"
+
+"vite@^3.0.0 || ^4.0.0", vite@^4.3.8:
   version "4.3.8"
   resolved "https://registry.yarnpkg.com/vite/-/vite-4.3.8.tgz#70cd6a294ab52d7fb8f37f5bc63d117dd19e9918"
   integrity sha512-uYB8PwN7hbMrf4j1xzGDk/lqjsZvCDbt/JC5dyfxc19Pg8kRm14LinK/uq+HSLNswZEoKmweGdtpbnxRtrAXiQ==
@@ -17676,18 +17781,6 @@ vite-plugin-dts@^2.0.0-beta.0:
   optionalDependencies:
     fsevents "~2.3.2"
 
-vite@^4.2.2:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/vite/-/vite-4.2.2.tgz#014c30e5163844f6e96d7fe18fbb702236516dc6"
-  integrity sha512-PcNtT5HeDxb3QaSqFYkEum8f5sCVe0R3WK20qxgIvNBZPXU/Obxs/+ubBMeE7nLWeCo2LDzv+8hRYSlcaSehig==
-  dependencies:
-    esbuild "^0.17.5"
-    postcss "^8.4.21"
-    resolve "^1.22.1"
-    rollup "^3.18.0"
-  optionalDependencies:
-    fsevents "~2.3.2"
-
 vitest@^0.31.1:
   version "0.31.1"
   resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.31.1.tgz#e3d1b68a44e76e24f142c1156fe9772ef603e52c"