2
0
Эх сурвалжийг харах

migration to vite/vitest (#111)

* to vite

* fx customSnackBar test

* fix emptyCard test

* clean debug

* fix status test

* fix customDialogTitle test

* fix CustomButton test

* fix customDialog test

* fix some of customInput tests

* add missing dependency

* fix missing dependency

* testing-library is dropped support for react 17, so we need to stick
with 12.1.2

* fix tablehead test

* fix toobar test

* remove unused test

* fix table tests

* fix simple menu test

* fix layout test

* fix grid test

* fix custom input test

* remove unused file

* add missing coverage dependency

* upgrade electron

* remove unused file
ryjiang 2 жил өмнө
parent
commit
c9ce9db33f
59 өөрчлөгдсөн 3025 нэмэгдсэн , 10181 устгасан
  1. 1 0
      client/.env.development
  2. 1 0
      client/.env.production
  3. 0 24
      client/README.md
  4. 0 7
      client/config-overrides.js
  5. 29 0
      client/index.html
  6. 30 34
      client/package.json
  7. BIN
      client/public/favicon.ico
  8. 0 47
      client/public/index.html
  9. BIN
      client/public/logo.png
  10. BIN
      client/public/logo192.png
  11. BIN
      client/public/logo512.png
  12. 0 25
      client/public/manifest.json
  13. 7 10
      client/src/components/__test__/cards/EmptyCard.spec.tsx
  14. 5 33
      client/src/components/__test__/customButton/CustomButton.spec.tsx
  15. 41 53
      client/src/components/__test__/customDialog/CustomDialog.spec.tsx
  16. 2 2
      client/src/components/__test__/customDialog/CustomDialogTitle.spec.tsx
  17. 3 2
      client/src/components/__test__/customDialog/DeleteDialogTemplate.spec.tsx
  18. 3 2
      client/src/components/__test__/customDialog/DialogTemplate.spec.tsx
  19. 49 76
      client/src/components/__test__/customInput/CustomInput.spec.tsx
  20. 10 10
      client/src/components/__test__/customInput/SearchInput.spec.tsx
  21. 0 121
      client/src/components/__test__/customSelector/CustomGroupedSelect.spec.tsx
  22. 6 30
      client/src/components/__test__/customSnackBar/CustomSnackBar.spec.tsx
  23. 12 9
      client/src/components/__test__/customToolTip/CustomToolTip.spec.tsx
  24. 40 75
      client/src/components/__test__/grid/Grid.spec.tsx
  25. 3 2
      client/src/components/__test__/grid/IconBtnCell.spec.tsx
  26. 9 0
      client/src/components/__test__/grid/LoadingTable.spec.tsx
  27. 43 121
      client/src/components/__test__/grid/Table.spec.tsx
  28. 49 87
      client/src/components/__test__/grid/TableHead.spec.tsx
  29. 30 76
      client/src/components/__test__/grid/Toolbar.spec.tsx
  30. 1 1
      client/src/components/__test__/grid/Utils.spec.ts
  31. 2 1
      client/src/components/__test__/layout/GlobalEffect.spec.tsx
  32. 33 44
      client/src/components/__test__/layout/Layout.spec.tsx
  33. 3 29
      client/src/components/__test__/menu/SimpleMenu.spec.tsx
  34. 9 36
      client/src/components/__test__/status/Status.spec.tsx
  35. 2 2
      client/src/components/customDialog/CustomDialogTitle.tsx
  36. 22 20
      client/src/components/customInput/CustomInput.tsx
  37. 1 1
      client/src/components/grid/Grid.tsx
  38. 1 1
      client/src/components/grid/LoadingTable.tsx
  39. 4 2
      client/src/components/grid/Table.tsx
  40. 3 2
      client/src/components/grid/TableHead.tsx
  41. 3 1
      client/src/components/grid/ToolBar.tsx
  42. 29 28
      client/src/components/insert/ImportSample.tsx
  43. 15 22
      client/src/components/layout/Layout.tsx
  44. 2 2
      client/src/http/Axios.ts
  45. 1 1
      client/src/http/User.ts
  46. 9 9
      client/src/pages/user/User.tsx
  47. 1 1
      client/src/plugins/search/config.json
  48. 1 1
      client/src/plugins/system/config.json
  49. 11 12
      client/src/router/Config.ts
  50. 2 2
      client/src/utils/__test__/Validation.spec.ts
  51. 9 11
      client/tsconfig.json
  52. 0 9
      client/tsconfig.paths.json
  53. 25 0
      client/vite.config.ts
  54. 19 0
      client/vitest.config.ts
  55. 2178 8873
      client/yarn.lock
  56. 0 35
      server/generate-csv.ts
  57. 2 2
      server/package.json
  58. 2 2
      server/src/utils/Helper.ts
  59. 262 185
      server/yarn.lock

+ 1 - 0
client/.env.development

@@ -0,0 +1 @@
+VITE_BASE_URL=http://localhost:3000/

+ 1 - 0
client/.env.production

@@ -0,0 +1 @@
+VITE_BASE_URL=https://API_BASE_URL/

+ 0 - 24
client/README.md

@@ -60,27 +60,3 @@ We use class getter to define our client fields like \_field, because of our ser
 Like utils / consts / utils / hooks , we dont want put all functions or data in one file like index.ts because of maintainability.
 
 So when we need to create new file , treat the file like Class then name it.
-
-### Plugins Folder
-
-You can deploy any plugin developed by [template](https://github.com/zilliztech/insight-plugin-template). All client plugins should be placed at `src/plugins` folder. We have transferred `System View` and `Vector Search` to plugins. For more plugins development details please refer to [template repo](https://github.com/zilliztech/insight-plugin-template).
-
-### Alias Map
-
-As `react-app-rewire-alias` in `config-overrides.js`, we can use alias import. `insight_src/` is equal to `client/src` .
-
-### Icon
-
-We put all icons in components/icons file. Normally we use material icon.
-
-If we use custom svg, like: import { ReactComponent as CustomIcon } from xxx/xxx.svg'.
-
-It's react component because of svgr/webpack in webpack config.
-
-### Build
-
-We use react-app-rewired to change webpack config.
-
-If we want to change the webpack config, we can edit config-overrides.js file.
-
-Our build path is `./build`. And we use Attu server to host our client site.

+ 0 - 7
client/config-overrides.js

@@ -1,7 +0,0 @@
-// const path = require('path');
-const { configPaths } = require('react-app-rewire-alias');
-const { aliasDangerous } = require('react-app-rewire-alias/lib/aliasDangerous');
-
-const aliasMap = configPaths('./tsconfig.paths.json');
-
-module.exports = aliasDangerous(aliasMap);

+ 29 - 0
client/index.html

@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8" />
+  <link rel="icon" href="attu.png" />
+  <meta name="description" content="Attu, best milvus management tool" />
+  <meta name="viewport" content="width=device-width, initial-scale=1" />
+  <meta name="theme-color" content="#000000" />
+  <script src="./env-config.js"></script>
+  <script type="module" src="/src/index.tsx"></script>
+  <title>Attu</title>
+</head>
+
+<body>
+  <noscript>You need to enable JavaScript to run this app.</noscript>
+  <div id="root"></div>
+  <!--
+      This HTML file is a template.
+      If you open it directly in the browser, you will see an empty page.
+
+      You can add webfonts, meta tags, or analytics to this file.
+      The build step will place the bundled scripts into the <body> tag.
+
+      To begin the development, run `npm start` or `yarn start`.
+      To create a production bundle, use `npm run build` or `yarn build`.
+    -->
+</body>
+
+</html>

+ 30 - 34
client/package.json

@@ -13,55 +13,58 @@
     "@material-ui/lab": "4.0.0-alpha.58",
     "@material-ui/pickers": "^3.3.10",
     "@mui/x-data-grid": "^4.0.0",
-    "@testing-library/jest-dom": "^5.11.4",
-    "@testing-library/react": "^11.1.0",
-    "@testing-library/user-event": "^12.1.10",
-    "@types/jest": "^26.0.15",
-    "@types/node": "^12.0.0",
-    "@types/papaparse": "^5.2.6",
-    "@types/react": "^17.0.0",
-    "@types/react-dom": "^17.0.0",
-    "@types/react-highlight-words": "^0.16.2",
-    "@types/react-router-dom": "^5.1.7",
-    "@types/react-syntax-highlighter": "^13.5.2",
+    "@vitejs/plugin-react": "^2.2.0",
+    "@vitejs/plugin-react-refresh": "^1.3.6",
     "axios": "^0.21.3",
     "dayjs": "^1.10.5",
     "file-saver": "^2.0.5",
     "i18next": "^20.3.1",
     "papaparse": "^5.3.1",
     "react": "^17.0.2",
-    "react-app-rewire-alias": "^1.1.4",
-    "react-app-rewired": "^2.1.8",
     "react-dom": "^17.0.2",
     "react-highlight-words": "^0.17.0",
-    "react-i18next": "11.10.0",
+    "react-i18next": "^12.0.0",
     "react-router-dom": "^5.2.0",
-    "react-scripts": "4.0.3",
     "react-syntax-highlighter": "^15.4.4",
     "set-value": "^4.1.0",
     "socket.io-client": "^4.1.3",
     "typescript": "^4.1.2",
+    "vite": "^3.2.2",
+    "vite-plugin-svgr": "^0.3.0",
     "web-vitals": "^1.0.1"
   },
-  "jest": {
-    "coverageDirectory": "<rootDir>/coverage/"
+  "devDependencies": {
+    "@testing-library/jest-dom": "^5.16.5",
+    "@testing-library/react": "12.1.2",
+    "@testing-library/react-hooks": "^7.0.1",
+    "@testing-library/user-event": "^12.1.10",
+    "@types/file-saver": "^2.0.4",
+    "@types/loadable__component": "^5.13.4",
+    "@types/node": "^12.0.0",
+    "@types/papaparse": "^5.2.6",
+    "@types/react": "^17.0.0",
+    "@types/react-dom": "^17.0.0",
+    "@types/react-highlight-words": "^0.16.2",
+    "@types/react-router-dom": "^5.1.7",
+    "@types/react-syntax-highlighter": "^13.5.2",
+    "@types/webpack-env": "^1.16.3",
+    "@vitest/coverage-c8": "^0.25.0",
+    "jsdom": "^20.0.2",
+    "prettier": "2.3.2",
+    "vitest": "^0.24.5"
   },
   "homepage": "./",
   "scripts": {
-    "start": "react-app-rewired start -FAST_REFRESH=true",
-    "start:plugin": "REACT_APP_PLUGIN_DEV=true react-app-rewired start -FAST_REFRESH=true",
-    "build": "react-app-rewired build",
-    "test": "react-app-rewired test",
-    "test:watch": "react-app-rewired test --watch",
-    "test:cov": "react-app-rewired test --watchAll=false --coverage",
-    "test:report": "react-app-rewired test --watchAll=false --coverage --coverageReporters='text-summary'",
-    "eject": "react-app-rewired eject",
+    "start": "vite",
+    "build": "vite build",
+    "test": "vitest",
+    "test:coverage": "vitest --coverage",
+    "test:watch": "vitest --watchAll",
     "format": "prettier --write '**/*.{ts,js,tsx,jsx,css}'"
   },
   "eslintConfig": {
     "extends": [
-      "react-app",
-      "react-app/jest"
+      "react-app"
     ]
   },
   "browserslist": {
@@ -75,12 +78,5 @@
       "last 1 firefox version",
       "last 1 safari version"
     ]
-  },
-  "devDependencies": {
-    "@testing-library/react-hooks": "^7.0.1",
-    "@types/file-saver": "^2.0.4",
-    "@types/loadable__component": "^5.13.4",
-    "@types/webpack-env": "^1.16.3",
-    "prettier": "2.3.2"
   }
 }

BIN
client/public/favicon.ico


+ 0 - 47
client/public/index.html

@@ -1,47 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-  <meta charset="utf-8" />
-  <link rel="icon" href="%PUBLIC_URL%/attu.png" />
-  <meta name="viewport" content="width=device-width, initial-scale=1" />
-  <meta name="theme-color" content="#000000" />
-  <meta name="description" content="Web site created using create-react-app" />
-  <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
-  <!--
-      manifest.json provides metadata used when your web app is installed on a
-      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-    -->
-  <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
-  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap" rel="stylesheet" />
-  <link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@300;400&display=swap" rel="stylesheet" />
-  <script src="%PUBLIC_URL%/env-config.js"></script>
-
-  <!--
-      Notice the use of %PUBLIC_URL% in the tags above.
-      It will be replaced with the URL of the `public` folder during the build.
-      Only files inside the `public` folder can be referenced from the HTML.
-
-      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
-      work correctly both with client-side routing and a non-root public URL.
-      Learn how to configure a non-root public URL by running `npm run build`.
-    -->
-  <title>Attu</title>
-</head>
-
-<body>
-  <noscript>You need to enable JavaScript to run this app.</noscript>
-  <div id="root"></div>
-  <!--
-      This HTML file is a template.
-      If you open it directly in the browser, you will see an empty page.
-
-      You can add webfonts, meta tags, or analytics to this file.
-      The build step will place the bundled scripts into the <body> tag.
-
-      To begin the development, run `npm start` or `yarn start`.
-      To create a production bundle, use `npm run build` or `yarn build`.
-    -->
-</body>
-
-</html>

BIN
client/public/logo.png


BIN
client/public/logo192.png


BIN
client/public/logo512.png


+ 0 - 25
client/public/manifest.json

@@ -1,25 +0,0 @@
-{
-  "short_name": "React App",
-  "name": "Create React App Sample",
-  "icons": [
-    {
-      "src": "favicon.ico",
-      "sizes": "64x64 32x32 24x24 16x16",
-      "type": "image/x-icon"
-    },
-    {
-      "src": "logo192.png",
-      "type": "image/png",
-      "sizes": "192x192"
-    },
-    {
-      "src": "logo512.png",
-      "type": "image/png",
-      "sizes": "512x512"
-    }
-  ],
-  "start_url": ".",
-  "display": "standalone",
-  "theme_color": "#000000",
-  "background_color": "#ffffff"
-}

+ 7 - 10
client/src/components/__test__/cards/EmptyCard.spec.tsx

@@ -1,20 +1,17 @@
-import { render, screen, RenderResult } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
 import EmptyCard from '../../cards/EmptyCard';
 import provideTheme from '../utils/provideTheme';
 
-let body: RenderResult;
-
 describe('test empty card component', () => {
-  beforeEach(() => {
-    body = render(
+  it('renders default state', () => {
+    const emptyText = Math.random().toString();
+    render(
       provideTheme(
-        <EmptyCard icon={<span className="icon">icon</span>} text="empty" />
+        <EmptyCard icon={<span className="icon">icon</span>} text={emptyText} />
       )
     );
-  });
 
-  it('renders default state', () => {
-    expect(screen.getByText('icon')).toHaveClass('icon');
-    expect(screen.getByText('empty')).toBeInTheDocument();
+    expect(screen.queryByText('icon')!.className).toEqual('icon');
+    expect(screen.queryByText(emptyText)).not.toBeNull();
   });
 });

+ 5 - 33
client/src/components/__test__/customButton/CustomButton.spec.tsx

@@ -1,39 +1,11 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
 import CustomButton from '../../customButton/CustomButton';
-
-let container: any = null;
-
-jest.mock('@material-ui/core/Button', () => {
-  return props => {
-    const { variant, children } = props;
-    return (
-      <>
-        <div className="variant">{variant}</div>
-        <button className="button">{children}</button>;
-      </>
-    );
-  };
-});
+import { render } from '@testing-library/react';
 
 describe('Test CustomButton', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   test('test button props', () => {
-    act(() => {
-      render(<CustomButton variant="contained">test</CustomButton>, container);
-    });
-
-    expect(container.querySelector('.button').textContent).toBe('test');
-    expect(container.querySelector('.variant').textContent).toBe('contained');
+    const result = render(
+      <CustomButton variant="contained">test</CustomButton>
+    );
+    expect(result.getByText('test').textContent).toBe('test');
   });
 });

+ 41 - 53
client/src/components/__test__/customDialog/CustomDialog.spec.tsx

@@ -1,56 +1,50 @@
-import { fireEvent } from '@testing-library/react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { screen, fireEvent, render } from '@testing-library/react';
 import { DialogType } from '../../../context/Types';
 import CustomDialog from '../../customDialog/CustomDialog';
+import { vi } from 'vitest';
 
-let container: any = null;
-
-jest.mock('react-i18next', () => ({
+vi.mock('react-i18next', () => ({
   useTranslation: () => ({
     t: (key: any) => key,
   }),
 }));
 
-jest.mock('@material-ui/core/Dialog', () => {
-  return props => {
-    return <div id="dialog-wrapper">{props.children}</div>;
+vi.mock('@material-ui/core/Dialog', () => {
+  return {
+    default: (props: any) => {
+      return <div id="dialog-wrapper">{props.children}</div>;
+    },
   };
 });
 
-jest.mock('@material-ui/core/DialogTitle', () => {
-  return props => {
-    return <div id="dialog-title">{props.children}</div>;
+vi.mock('@material-ui/core/DialogTitle', () => {
+  return {
+    default: (props: any) => {
+      return <div id="dialog-title">{props.children}</div>;
+    },
   };
 });
 
-jest.mock('@material-ui/core/DialogContent', () => {
-  return props => {
-    return <div id="dialog-content">{props.children}</div>;
+vi.mock('@material-ui/core/DialogContent', () => {
+  return {
+    default: (props: any) => {
+      return <div id="dialog-content">{props.children}</div>;
+    },
   };
 });
 
-jest.mock('@material-ui/core/DialogActions', () => {
-  return props => {
-    return <div id="dialog-actions">{props.children}</div>;
+vi.mock('@material-ui/core/DialogActions', () => {
+  return {
+    default: (props: any) => {
+      return <div id="dialog-actions">{props.children}</div>;
+    },
   };
 });
 
 describe('Test Custom Dialog', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Test notice dialog ', () => {
-    const handleClose = jest.fn();
-    const handleConfirm = jest.fn();
+    const handleClose = vi.fn();
+    const handleConfirm = vi.fn();
 
     const params: DialogType = {
       open: true,
@@ -61,28 +55,26 @@ describe('Test Custom Dialog', () => {
         component: <div>123</div>,
       },
     };
-    act(() => {
-      render(
-        <CustomDialog {...params} onClose={handleClose}></CustomDialog>,
-        container
-      );
-    });
-
-    expect(container.querySelector('#dialog-title').textContent).toEqual(
-      params.params.title
+
+    const res = render(
+      <CustomDialog {...params} onClose={handleClose}></CustomDialog>
     );
 
-    expect(container.querySelector('#dialog-content').textContent).toEqual(
-      '123'
+    expect(res.getByText(params.params.title!).textContent).toEqual(
+      params.params.title
     );
 
-    container.querySelectorAll('button').forEach(v => fireEvent.click(v));
+    expect(res.getByText('123').textContent).toEqual('123');
+
+    fireEvent.click(screen.getByText('cancel'));
+    fireEvent.click(screen.getByText('confirm'));
+
     expect(handleClose).toBeCalledTimes(1);
     expect(handleConfirm).toBeCalledTimes(1);
   });
 
   it('Test Custom dialog ', () => {
-    const handleClose = jest.fn();
+    const handleClose = vi.fn();
 
     const params: DialogType = {
       open: true,
@@ -91,15 +83,11 @@ describe('Test Custom Dialog', () => {
         component: <div>custom</div>,
       },
     };
-    act(() => {
-      render(
-        <CustomDialog {...params} onClose={handleClose}></CustomDialog>,
-        container
-      );
-    });
-
-    expect(container.querySelector('#dialog-wrapper').textContent).toEqual(
-      'custom'
+
+    const res = render(
+      <CustomDialog {...params} onClose={handleClose}></CustomDialog>
     );
+
+    expect(res.getByText('custom').textContent).toEqual('custom');
   });
 });

+ 2 - 2
client/src/components/__test__/customDialog/CustomDialogTitle.spec.tsx

@@ -1,16 +1,16 @@
 import { fireEvent, render } from '@testing-library/react';
 import CustomDialogTitle from '../../customDialog/CustomDialogTitle';
+import { vi } from 'vitest';
 
 describe('test custom dialog title component', () => {
   it('renders default state', () => {
     const container = render(<CustomDialogTitle>title</CustomDialogTitle>);
-
     expect(container.getByText('title')).toBeInTheDocument();
     expect(container.queryByTestId('clear-icon')).toBeNull();
   });
 
   it('checks clear event', () => {
-    const mockClearFn = jest.fn();
+    const mockClearFn = vi.fn();
     const container = render(
       <CustomDialogTitle onClose={mockClearFn}>title</CustomDialogTitle>
     );

+ 3 - 2
client/src/components/__test__/customDialog/DeleteDialogTemplate.spec.tsx

@@ -3,10 +3,11 @@ import DeleteTemplate from '../../customDialog/DeleteDialogTemplate';
 import provideTheme from '../utils/provideTheme';
 import { I18nextProvider } from 'react-i18next';
 import i18n from '../../../i18n';
+import { vi } from 'vitest';
 
 describe('test delete dialog template component', () => {
-  const mockDeleteFn = jest.fn();
-  const mockCancelFn = jest.fn();
+  const mockDeleteFn = vi.fn();
+  const mockCancelFn = vi.fn();
 
   beforeEach(() => {
     render(

+ 3 - 2
client/src/components/__test__/customDialog/DialogTemplate.spec.tsx

@@ -3,10 +3,11 @@ import DialogTemplate from '../../customDialog/DialogTemplate';
 import provideTheme from '../utils/provideTheme';
 import { I18nextProvider } from 'react-i18next';
 import i18n from '../../../i18n';
+import { vi } from 'vitest';
 
 describe('test dialog template component', () => {
-  const mockCancelFn = jest.fn();
-  const mockConfirmFn = jest.fn();
+  const mockCancelFn = vi.fn();
+  const mockConfirmFn = vi.fn();
 
   it('renders default state and callbacks', () => {
     render(

+ 49 - 76
client/src/components/__test__/customInput/CustomInput.spec.tsx

@@ -1,34 +1,28 @@
-import { fireEvent } from '@testing-library/react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { fireEvent, render } from '@testing-library/react';
 import CustomInput from '../../customInput/CustomInput';
+import provideTheme from '../utils/provideTheme';
 import {
   IAdornmentConfig,
   IIconConfig,
   ITextfieldConfig,
 } from '../../customInput/Types';
+import { vi } from 'vitest';
 
-let container: any = null;
-
-jest.mock('@material-ui/core/styles/makeStyles', () => {
-  return () => () => ({});
-});
-
-jest.mock('@material-ui/core/FormControl', () => {
-  return props => {
+vi.mock('@material-ui/core/FormControl', () => {
+  return (props: any) => {
     const { children } = props;
     return <div className="form-control">{children}</div>;
   };
 });
 
-jest.mock('@material-ui/core/InputLabel', () => {
-  return props => {
+vi.mock('@material-ui/core/InputLabel', () => {
+  return (props: any) => {
     return <div className="label">{props.children}</div>;
   };
 });
 
-jest.mock('@material-ui/core/Input', () => {
-  return props => {
+vi.mock('@material-ui/core/Input', () => {
+  return (props: any) => {
     const { type, onBlur, endAdornment } = props;
     return (
       <>
@@ -40,8 +34,8 @@ jest.mock('@material-ui/core/Input', () => {
   };
 });
 
-jest.mock('@material-ui/core/TextField', () => {
-  return props => {
+vi.mock('@material-ui/core/TextField', () => {
+  return (props: any) => {
     const { helperText, onBlur, onChange, label, className } = props;
     return (
       <div className="text-field">
@@ -59,27 +53,16 @@ jest.mock('@material-ui/core/TextField', () => {
   };
 });
 
-jest.mock('@material-ui/core/Grid', () => {
-  return props => {
+vi.mock('@material-ui/core/Grid', () => {
+  return (props: any) => {
     const { children } = props;
     return <div className="grid">{children}</div>;
   };
 });
 
 describe('Test CustomInput', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   test('test text type input', () => {
-    const handleBlur = jest.fn();
+    const handleBlur = vi.fn();
 
     const mockTextConfig: ITextfieldConfig = {
       variant: 'standard',
@@ -89,36 +72,31 @@ describe('Test CustomInput', () => {
       onBlur: handleBlur,
     };
 
-    act(() => {
-      render(
-        <CustomInput
-          type="text"
-          textConfig={mockTextConfig}
-          checkValid={() => true}
-        />,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('.text-field').length).toBe(1);
-    expect(container.querySelector('.text-class').textContent).toBe(
-      'classname'
-    );
-    expect(container.querySelector('.text-label').textContent).toBe(
-      'test text'
+    const res = render(
+      <CustomInput
+        type="text"
+        textConfig={mockTextConfig}
+        checkValid={() => true}
+      />
     );
 
-    const input = container.querySelector('.text-input');
+    expect(res.getAllByText('test text').length).toBe(1);
+    expect(res.getByText('test text').textContent).toBe('test text');
+    expect(
+      res.getByText('test text').parentElement!.classList.contains('classname')
+    ).toBeTruthy();
+
+    const input = res.getByRole('textbox');
     input.focus();
     input.blur();
     expect(handleBlur).toHaveBeenCalledTimes(1);
   });
 
   test('test icon type input', () => {
-    const handleChange = jest.fn();
+    const handleChange = vi.fn();
 
     const mockIconConfig: IIconConfig = {
-      icon: <div className="icon"></div>,
+      icon: <div className="icon" role="img"></div>,
       inputType: 'icon',
       inputConfig: {
         label: 'icon text',
@@ -128,28 +106,25 @@ describe('Test CustomInput', () => {
       },
     };
 
-    render(
+    const res = render(
       <CustomInput
         type="icon"
         iconConfig={mockIconConfig}
         checkValid={() => true}
-      />,
-      container
+      />
     );
 
-    expect(container.querySelectorAll('.grid').length).toBe(3);
-    expect(container.querySelectorAll('.icon').length).toBe(1);
-    expect(container.querySelector('.text-label').textContent).toBe(
-      'icon text'
-    );
+    // expect(res.getAllByText('.grid').length).toBe(3);
+    expect(res.getAllByRole('img').length).toBe(1);
+    expect(res.getByText('icon text').textContent).toBe('icon text');
 
-    const input = container.querySelector('.text-input');
+    const input = res.getByRole('textbox');
     fireEvent.change(input, { target: { value: 'trigger change' } });
     expect(handleChange).toHaveBeenCalledTimes(1);
   });
 
   test('test adornmentConfig type input', () => {
-    const mockBlurFunc = jest.fn();
+    const mockBlurFunc = vi.fn();
 
     const mockAdornmentConfig: IAdornmentConfig = {
       label: 'adornment',
@@ -159,20 +134,20 @@ describe('Test CustomInput', () => {
       onInputBlur: mockBlurFunc,
     };
 
-    render(
-      <CustomInput
-        type="adornment"
-        adornmentConfig={mockAdornmentConfig}
-        checkValid={() => true}
-      />,
-      container
+    const res = render(
+      provideTheme(
+        <CustomInput
+          type="adornment"
+          adornmentConfig={mockAdornmentConfig}
+          checkValid={() => true}
+        />
+      )
     );
 
-    expect(container.querySelector('.label').textContent).toBe('adornment');
-    expect(container.querySelector('.type').textContent).toBe('text');
-    expect(container.querySelectorAll('.adornment-icon').length).toBe(1);
+    expect(res.getByText('adornment').textContent).toBe('adornment');
+    expect(res.getAllByRole('icon-button').length).toBe(1);
 
-    const input = container.querySelector('.input');
+    const input = res.getByRole('textbox');
     input.focus();
     input.blur();
     expect(mockBlurFunc).toHaveBeenCalledTimes(1);
@@ -185,10 +160,8 @@ describe('Test CustomInput', () => {
       variant: 'standard',
     };
 
-    act(() => {
-      render(<CustomInput textConfig={mockTextConfig} />, container);
-    });
+    const res = render(<CustomInput textConfig={mockTextConfig} />);
 
-    expect(container.querySelector('.text-label').textContent).toBe('default');
+    expect(res.getByText('default').textContent).toBe('default');
   });
 });

+ 10 - 10
client/src/components/__test__/customInput/SearchInput.spec.tsx

@@ -2,11 +2,11 @@ import { fireEvent, render } from '@testing-library/react';
 import SearchInput from '../../customInput/SearchInput';
 import { I18nextProvider } from 'react-i18next';
 import i18n from '../../../i18n';
-import { Router } from 'react-router-dom';
+import { vi } from 'vitest';
 
-const mockHistoryPushFn = jest.fn();
+const mockHistoryPushFn = vi.fn();
 
-jest.mock('react-router-dom', () => ({
+vi.mock('react-router-dom', () => ({
   useHistory: () => ({
     push: mockHistoryPushFn,
     location: {
@@ -15,14 +15,14 @@ jest.mock('react-router-dom', () => ({
   }),
 }));
 
-// clear the influence of jest.useFakeTimers
+// clear the influence of vi.useFakeTimers
 afterEach(() => {
-  jest.useRealTimers();
+  vi.useRealTimers();
 });
 
 describe('test search input component', () => {
   it('renders default state', () => {
-    const mockSearchFn = jest.fn();
+    const mockSearchFn = vi.fn();
     const container = render(
       <I18nextProvider i18n={i18n}>
         <SearchInput searchText="search text" onSearch={mockSearchFn} />
@@ -35,7 +35,7 @@ describe('test search input component', () => {
   });
 
   it('checks input value change event', () => {
-    const mockSearchFn = jest.fn();
+    const mockSearchFn = vi.fn();
     const container = render(
       <I18nextProvider i18n={i18n}>
         <SearchInput onSearch={mockSearchFn} />
@@ -55,9 +55,9 @@ describe('test search input component', () => {
   });
 
   it('checks location change according to search value', () => {
-    const mockSearchFn = jest.fn();
+    const mockSearchFn = vi.fn();
     // mock setTimeout
-    jest.useFakeTimers();
+    vi.useFakeTimers();
 
     const container = render(
       <I18nextProvider i18n={i18n}>
@@ -69,7 +69,7 @@ describe('test search input component', () => {
     fireEvent.change(input, { target: { value: 'route' } });
     expect(mockHistoryPushFn).not.toBeCalled();
     // fast-forward until all timers have been executed
-    jest.runAllTimers();
+    vi.runAllTimers();
     expect(mockHistoryPushFn).toBeCalled();
     expect(mockHistoryPushFn).toBeCalledWith({ search: 'search=route' });
   });

+ 0 - 121
client/src/components/__test__/customSelector/CustomGroupedSelect.spec.tsx

@@ -1,121 +0,0 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
-import { fireEvent } from '@testing-library/react';
-import CustomGroupedSelect from '../../customSelector/CustomGroupedSelect';
-import { GroupOption } from '../../customSelector/Types';
-
-let container: any = null;
-
-jest.mock('@material-ui/core/FormControl', () => {
-  return props => {
-    const { children } = props;
-    return <div className="form-control">{children}</div>;
-  };
-});
-
-jest.mock('@material-ui/core/Select', () => {
-  return props => {
-    const { children, onChange } = props;
-    return (
-      <select className="group-select" onChange={onChange}>
-        {children}
-      </select>
-    );
-  };
-});
-
-jest.mock('@material-ui/core/ListSubheader', () => {
-  return props => {
-    const { children } = props;
-    return <option className="group-header">{children}</option>;
-  };
-});
-
-jest.mock('@material-ui/core/MenuItem', () => {
-  return props => {
-    const { children, value } = props;
-    return (
-      <option className="group-item" value={value}>
-        {children}
-      </option>
-    );
-  };
-});
-
-describe('Test CustomGroupedSelect', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
-  test('test group select props', () => {
-    const mockOptions: GroupOption[] = [
-      {
-        label: 'Group 1',
-        children: [
-          {
-            label: 'group text 1',
-            value: 'group text 1',
-          },
-          {
-            label: 'group text 2',
-            value: 'group text 2',
-          },
-          {
-            label: 'group text 3',
-            value: 'group text 3',
-          },
-        ],
-      },
-      {
-        label: 'Group 2',
-        children: [
-          {
-            label: 'group text 11',
-            value: 'group text 11',
-          },
-          {
-            label: 'group text 22',
-            value: 'group text 22',
-          },
-          {
-            label: 'group text 33',
-            value: 'group text 33',
-          },
-        ],
-      },
-    ];
-    const handleChange = jest.fn();
-
-    act(() => {
-      render(
-        <CustomGroupedSelect
-          options={mockOptions}
-          value={''}
-          onChange={handleChange}
-        />,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('.form-control').length).toBe(1);
-    expect(container.querySelectorAll('.group-header').length).toBe(2);
-    expect(container.querySelectorAll('.group-item').length).toBe(6);
-
-    const select = container.querySelector('.group-select');
-
-    fireEvent.change(select, {
-      target: {
-        value: 'group text 2',
-      },
-    });
-    expect(handleChange).toHaveBeenCalledTimes(1);
-    expect(select.value).toBe('group text 2');
-  });
-});

+ 6 - 30
client/src/components/__test__/customSnackBar/CustomSnackBar.spec.tsx

@@ -1,44 +1,20 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { render, screen } from '@testing-library/react';
 import { SnackBarType } from '../../../context/Types';
 import CustomSnackBar from '../../customSnackBar/CustomSnackBar';
-
-let container: any = null;
-
-jest.mock('@material-ui/core/Snackbar', () => {
-  return props => {
-    return <div id="snackbar">{props.children}</div>;
-  };
-});
+import { vi } from 'vitest';
 
 describe('Test Custom Dialog', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Test Custom dialog ', () => {
     const params: SnackBarType = {
-      open: false,
+      open: true,
       type: 'success',
       message: 'test',
       vertical: 'top',
       horizontal: 'center',
       autoHideDuration: 2000,
     };
-    const handleClose = jest.fn();
-    act(() => {
-      render(<CustomSnackBar {...params} onClose={handleClose} />, container);
-    });
-
-    expect(container.querySelector('#snackbar').textContent).toEqual(
-      params.message
-    );
+    const handleClose = vi.fn();
+    render(<CustomSnackBar {...params} onClose={handleClose} />);
+    expect(screen.queryByText('test')?.textContent).toEqual(params.message);
   });
 });

+ 12 - 9
client/src/components/__test__/customToolTip/CustomToolTip.spec.tsx

@@ -1,18 +1,21 @@
 import { render, unmountComponentAtNode } from 'react-dom';
 import { act } from 'react-dom/test-utils';
 import CustomToolTip from '../../customToolTip/CustomToolTip';
+import { vi } from 'vitest';
 
 let container: any = null;
 
-jest.mock('@material-ui/core/Tooltip', () => {
-  return props => {
-    return (
-      <div id="tooltip">
-        <div id="title">{props.title}</div>
-        <div id="placement">{props.placement}</div>
-        {props.children}
-      </div>
-    );
+vi.mock('@material-ui/core/Tooltip', () => {
+  return {
+    default: (props: any) => {
+      return (
+        <div id="tooltip">
+          <div id="title">{props.title}</div>
+          <div id="placement">{props.placement}</div>
+          {props.children}
+        </div>
+      );
+    },
   };
 });
 

+ 40 - 75
client/src/components/__test__/grid/Grid.spec.tsx

@@ -1,11 +1,9 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { render } from '@testing-library/react';
 import AttuGrid from '../../grid/Grid';
 import { ToolBarConfig } from '../../grid/Types';
+import { vi } from 'vitest';
 
-let container: any = null;
-
-jest.mock('react-i18next', () => {
+vi.mock('react-i18next', () => {
   return {
     useTranslation: () => ({
       t: () => ({
@@ -15,23 +13,11 @@ jest.mock('react-i18next', () => {
   };
 });
 
-jest.mock('../../grid/Table', () => {
-  return () => {
-    return <div id="table">{ }</div>;
-  };
-});
-
-jest.mock('../../grid/ToolBar', () => {
-  return () => {
-    return <div id="tool-bar"></div>;
-  };
-});
-
-jest.mock('react-router-dom', () => {
+vi.mock('react-router-dom', () => {
   return {
     useHistory: () => {
       return {
-        listen: () => () => { },
+        listen: () => () => {},
         location: {
           name: '',
         },
@@ -41,54 +27,36 @@ jest.mock('react-router-dom', () => {
 });
 
 describe('Test Grid index', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Has Table Data', () => {
-    act(() => {
-      render(
-        <AttuGrid
-          primaryKey="id"
-          rows={[{}]}
-          colDefinitions={[]}
-          rowCount={10}
-          toolbarConfigs={[]}
-        />,
-        container
-      );
-    });
+    const res = render(
+      <AttuGrid
+        primaryKey="id"
+        rows={[{}]}
+        colDefinitions={[]}
+        rowCount={10}
+        toolbarConfigs={[]}
+      />
+    );
 
-    expect(container.querySelectorAll('#table').length).toEqual(1);
+    expect(res.getAllByRole('table').length).toEqual(1);
   });
 
   it('Test title', () => {
     const title = ['collections', 'vectors'];
-    act(() => {
-      render(
-        <AttuGrid
-          primaryKey="id"
-          rows={[]}
-          colDefinitions={[]}
-          rowCount={0}
-          toolbarConfigs={[]}
-          title={title}
-        />,
-        container
-      );
-    });
+    const res = render(
+      <AttuGrid
+        primaryKey="id"
+        rows={[]}
+        colDefinitions={[]}
+        rowCount={0}
+        toolbarConfigs={[]}
+        title={title}
+      />
+    );
 
-    const titleNodes = container.querySelectorAll('h6');
-    expect(titleNodes.length).toEqual(title.length);
-    expect(titleNodes[0].textContent).toEqual(title[0]);
-    expect(titleNodes[1].textContent).toEqual(title[1]);
+    const breadCrum = res.getAllByRole('breadcrumb');
+    expect(breadCrum.length).toEqual(1);
+    expect(breadCrum[0].textContent).toEqual(`collections›vectors`);
   });
 
   it('Test Toolbar ', () => {
@@ -96,23 +64,20 @@ describe('Test Grid index', () => {
       {
         label: 'collection',
         icon: 'search',
-        onClick: () => { },
-        onSearch: () => { },
+        onClick: () => {},
+        onSearch: () => {},
       },
     ];
-    act(() => {
-      render(
-        <AttuGrid
-          primaryKey="id"
-          rows={[]}
-          colDefinitions={[]}
-          rowCount={0}
-          toolbarConfigs={ToolbarConfig}
-        />,
-        container
-      );
-    });
+    const res = render(
+      <AttuGrid
+        primaryKey="id"
+        rows={[]}
+        colDefinitions={[]}
+        rowCount={0}
+        toolbarConfigs={ToolbarConfig}
+      />
+    );
 
-    expect(container.querySelectorAll('#tool-bar').length).toEqual(1);
+    expect(res.getAllByRole('toolbar').length).toEqual(1);
   });
 });

+ 3 - 2
client/src/components/__test__/grid/IconBtnCell.spec.tsx

@@ -2,6 +2,7 @@ import { render, unmountComponentAtNode } from 'react-dom';
 import { act } from 'react-dom/test-utils';
 import ActionBar from '../../grid/ActionBar';
 import { fireEvent } from '@testing-library/react';
+import { vi } from 'vitest';
 
 let container: any = null;
 
@@ -26,8 +27,8 @@ describe('Test Table Head', () => {
   });
 
   it('Test Delete Icon Button', () => {
-    const deleteSpy = jest.fn();
-    const showDialogSpy = jest.fn();
+    const deleteSpy = vi.fn();
+    const showDialogSpy = vi.fn();
 
     act(() => {
       render(

+ 9 - 0
client/src/components/__test__/grid/LoadingTable.spec.tsx

@@ -0,0 +1,9 @@
+import { render } from '@testing-library/react';
+import LoadingTable from '../../grid/LoadingTable';
+
+describe('Test Table', () => {
+  it('Test Table Loading status', () => {
+    const res = render(<LoadingTable count={2} />);
+    expect(res.getAllByRole('skeleton').length).toBe(2);
+  });
+});

+ 43 - 121
client/src/components/__test__/grid/Table.spec.tsx

@@ -1,9 +1,7 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { render, screen, fireEvent } from '@testing-library/react';
 import Table from '../../grid/Table';
 import { ColDefinitionsType } from '../../grid/Types';
-
-let container: any = null;
+import { vi } from 'vitest';
 
 const colDefinitions: ColDefinitionsType[] = [
   {
@@ -31,133 +29,57 @@ const colDefinitions: ColDefinitionsType[] = [
   },
 ];
 
-jest.mock('@material-ui/core/styles/makeStyles', () => {
-  return () => () => ({});
-});
-
-jest.mock('../../grid/LoadingTable.tsx', () => {
-  return () => {
-    return <div className="loading"></div>;
-  };
-});
-
 describe('Test Table', () => {
-  let data: any[] = [];
-  let onSelected: any;
-  let isSelected: any;
-  let onSelectedAll: any;
-
-  beforeEach(() => {
-    data = [
-      {
-        id: 1,
-        name: 'czz',
-      },
-    ];
-    onSelected = jest.fn();
-    isSelected = jest.fn().mockImplementation(() => true);
-    onSelectedAll = jest.fn();
-
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
+  let data: any[] = [
+    { id: 1, name: 'foo' },
+    { id: 2, name: 'bar' },
+    { id: 3, name: 'dede' },
+  ];
+  let onSelected: any = vi.fn();
+  let isSelected: any = vi.fn();
+  let onSelectedAll: any = vi.fn();
 
   it('Test Basic Table', () => {
-    act(() => {
-      render(
-        <Table
-          selected={[]}
-          onSelected={onSelected}
-          isSelected={isSelected}
-          onSelectedAll={onSelectedAll}
-          rows={data}
-          primaryKey="id"
-          colDefinitions={colDefinitions}
-        ></Table>,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('input').length).toEqual(2); // check box
-    expect(container.querySelector('tr').children.length).toEqual(
-      colDefinitions.length + 1
+    const res = render(
+      <Table
+        editHeads={[]}
+        selected={[]}
+        onSelected={onSelected}
+        isSelected={isSelected}
+        onSelectedAll={onSelectedAll}
+        rows={data}
+        primaryKey="id"
+        colDefinitions={colDefinitions}
+      ></Table>
     );
-    expect(container.querySelectorAll('th')[1].textContent).toEqual(
-      colDefinitions[0].label
+    expect(res.getAllByRole('checkbox').length).toEqual(4); // check box
+    expect(res.getAllByRole('row').length).toEqual(colDefinitions.length + 1);
+    expect(res.getAllByRole('cell').length).toEqual(
+      (colDefinitions.length + 1) * (data.length + 1)
     );
-    expect(container.querySelectorAll('th')[2].textContent).toEqual(
+    expect(res.getAllByRole('button').length).toEqual(3);
+    expect(res.getAllByRole('cell')[2].textContent).toEqual(
       colDefinitions[1].label
     );
-
-    expect(container.querySelectorAll('[aria-label="delete"]').length).toEqual(
-      1
-    );
   });
 
   it('Test Selected function', () => {
-    act(() => {
-      render(
-        <Table
-          selected={[1]}
-          onSelected={onSelected}
-          isSelected={isSelected}
-          onSelectedAll={onSelectedAll}
-          rows={data}
-          primaryKey="id"
-          colDefinitions={colDefinitions}
-        ></Table>,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('input')[1].checked).toBeTruthy();
-    expect(container.querySelectorAll('input')[0].checked).toBeTruthy();
-
-    isSelected = jest.fn().mockImplementation(() => false);
-    act(() => {
-      render(
-        <Table
-          selected={[]}
-          onSelected={onSelected}
-          isSelected={isSelected}
-          onSelectedAll={onSelectedAll}
-          rows={data}
-          primaryKey="id"
-          colDefinitions={colDefinitions}
-        ></Table>,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('input')[1].checked).toBeFalsy();
-    expect(container.querySelectorAll('input')[0].checked).toBeFalsy();
-
-    expect(isSelected).toHaveBeenCalledTimes(1);
-  });
-
-  it('Test Table Loading status', () => {
-    act(() => {
-      render(
-        <Table
-          selected={[]}
-          onSelected={onSelected}
-          isSelected={isSelected}
-          onSelectedAll={onSelectedAll}
-          rows={data}
-          primaryKey="id"
-          colDefinitions={colDefinitions}
-          isLoading={true}
-        ></Table>,
-        container
-      );
-    });
+    const res: any = render(
+      <Table
+        editHeads={[]}
+        selected={[1]}
+        onSelected={onSelected}
+        isSelected={isSelected}
+        onSelectedAll={onSelectedAll}
+        rows={data}
+        primaryKey="id"
+        colDefinitions={colDefinitions}
+      ></Table>
+    );
 
-    expect(container.querySelectorAll('.loading').length).toBe(1);
+    const cbx = res.getAllByRole('checkbox')[1];
+    fireEvent.click(cbx);
+    expect(cbx.checked).toBeTruthy();
+    expect(onSelected).toBeCalledTimes(1);
   });
 });

+ 49 - 87
client/src/components/__test__/grid/TableHead.spec.tsx

@@ -1,87 +1,50 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
 import TableHead from '../../grid/TableHead';
-import { fireEvent } from '@testing-library/react';
-
-let container: any = null;
-
-jest.mock('@material-ui/core/TableHead', () => {
-  return (props: any) => {
-    return <div id="table-head">{props.children}</div>;
-  };
-});
-jest.mock('@material-ui/core/TableRow', () => {
-  return (props: any) => {
-    return <div id="table-row">{props.children}</div>;
-  };
-});
-
-jest.mock('@material-ui/core/TableCell', () => {
-  return (props: any) => {
-    return <div className="table-cell">{props.children}</div>;
-  };
-});
+import { fireEvent, render } from '@testing-library/react';
+import { vi } from 'vitest';
 
 describe('Test Table Head', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
-  it('Test no checkbox', () => {
-    act(() => {
-      render(
-        <TableHead
-          colDefinitions={[]}
-          numSelected={0}
-          order={'desc'}
-          orderBy={'id'}
-          onSelectAllClick={() => {}}
-          onRequestSort={() => {}}
-          rowCount={0}
-          openCheckBox={false}
-        />,
-        container
-      );
-    });
-    expect(container.querySelectorAll('.table-cell').length).toEqual(0);
-  });
+  // it('Test no checkbox', () => {
+  //   const res = render(
+  //     <TableHead
+  //       colDefinitions={[]}
+  //       numSelected={0}
+  //       order={'desc'}
+  //       orderBy={'id'}
+  //       onSelectAllClick={() => {}}
+  //       handleSort={() => {}}
+  //       rowCount={0}
+  //       openCheckBox={false}
+  //     />
+  //   );
+  //   expect(res.getAllByText('.table-cell').length).toEqual(0);
+  // });
 
   it('Test checkbox open', () => {
-    const selectAllSpy = jest.fn();
-    act(() => {
-      render(
+    const selectAllSpy = vi.fn();
+    const res = render(
+      <div>
         <TableHead
           colDefinitions={[]}
           numSelected={10}
           order={'desc'}
           orderBy={'id'}
           onSelectAllClick={selectAllSpy}
-          onRequestSort={() => {}}
+          handleSort={() => {}}
           rowCount={10}
           openCheckBox={true}
-        />,
-        container
-      );
-    });
-
-    const checkboxDom = container.querySelector('input[type="checkbox"]');
-    expect(container.querySelectorAll('.table-cell').length).toEqual(1);
-    expect(checkboxDom).toBeDefined();
-
-    fireEvent.click(checkboxDom);
+        />
+      </div>
+    );
+    // screen.debug();
+    const checkboxes: any = res.getAllByRole('checkbox');
+    expect(checkboxes.length).toEqual(1);
+    fireEvent.click(checkboxes[0]);
     expect(selectAllSpy).toBeCalledTimes(1);
-    expect(checkboxDom.checked).toBeTruthy();
+    expect(checkboxes[0].checked).toBe(true);
   });
 
   it('Test header cells', () => {
-    const onRequestSortSpy = jest.fn();
+    const onRequestSortSpy = vi.fn();
     const colDefinitions = [
       {
         id: 'id',
@@ -96,31 +59,30 @@ describe('Test Table Head', () => {
         label: 'name',
       },
     ];
-    act(() => {
-      render(
-        <TableHead
-          colDefinitions={colDefinitions}
-          numSelected={10}
-          order={'desc'}
-          orderBy={'id'}
-          onSelectAllClick={() => {}}
-          onRequestSort={onRequestSortSpy}
-          rowCount={10}
-          openCheckBox={false}
-        />,
-        container
-      );
-    });
+    const res = render(
+      <TableHead
+        colDefinitions={colDefinitions}
+        numSelected={10}
+        order={'desc'}
+        orderBy={'id'}
+        onSelectAllClick={() => {}}
+        handleSort={onRequestSortSpy}
+        rowCount={10}
+        openCheckBox={false}
+      />
+    );
 
-    const headerCells = container.querySelectorAll('.MuiTableSortLabel-root');
+    const headerCells = res.getAllByRole('cell');
     expect(headerCells.length).toEqual(colDefinitions.length);
 
-    fireEvent.click(headerCells[0]);
+    expect(headerCells[0].textContent).toContain('id');
+    expect(headerCells[0].textContent).toContain('sorted descending');
+
+    const sortButton = res.getAllByRole('button');
+    fireEvent.click(sortButton[0]);
     expect(onRequestSortSpy).toBeCalledTimes(1);
 
-    fireEvent.click(headerCells[1]);
+    fireEvent.click(sortButton[0]);
     expect(onRequestSortSpy).toBeCalledTimes(2);
-    expect(headerCells[0].textContent).toContain('id');
-    expect(headerCells[0].textContent).toContain('sorted descending');
   });
 });

+ 30 - 76
client/src/components/__test__/grid/Toolbar.spec.tsx

@@ -1,74 +1,32 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { render, screen } from '@testing-library/react';
 import Toolbar from '../../grid/ToolBar';
 import { ToolBarConfig } from '../../grid/Types';
+import { vi } from 'vitest';
 
-jest.mock('@material-ui/icons/Search', () => {
-  return () => {
-    return <div id="search">search</div>;
-  };
-});
-
-jest.mock('../../customButton/CustomButton', () => {
-  return () => {
-    return <div className="button">button</div>;
-  };
-});
-
-jest.mock('../../customInput/SearchInput', () => {
-  return props => {
-    return <div>{props.children}</div>;
-  };
-});
-
-jest.mock('@material-ui/core/TextField', () => {
-  return props => {
-    return <input {...props} className="input" />;
-  };
-});
-
-let container: any = null;
-
-const cb = jest.fn().mockImplementation(resolve => resolve('a'));
+const cb = vi.fn().mockImplementation(resolve => resolve('a'));
 
-let toolbarConfig: ToolBarConfig[] = [];
+let toolbarConfig: ToolBarConfig[] = [
+  {
+    label: 'collection',
+    icon: 'delete',
+    onClick: cb,
+    disabled: selected => selected.length > 1,
+  },
+];
 
 describe('Test ToolBar', () => {
-  beforeEach(() => {
-    toolbarConfig = [
-      {
-        label: 'collection',
-        icon: 'delete',
-        onClick: cb,
-        disabled: selected => selected.length > 1,
-      },
-    ];
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Test only one config', () => {
-    act(() => {
-      render(
-        <Toolbar
-          selected={[]}
-          setSelected={() => {}}
-          toolbarConfigs={toolbarConfig}
-        ></Toolbar>,
-        container
-      );
-    });
-
-    const btnDom = container.querySelector('.button');
-    expect(container.querySelectorAll('.button').length).toBe(1);
-    expect(btnDom.className.includes('disabled')).toBeFalsy();
-    expect(container.querySelector('#search')).toBeNull();
+    const res = render(
+      <Toolbar
+        selected={[]}
+        setSelected={() => {}}
+        toolbarConfigs={toolbarConfig}
+      />
+    );
+
+    const button = res.getByRole('button');
+    expect(res.getAllByRole('button').length).toBe(1);
+    expect(button.className.includes('disabled')).toBeFalsy();
   });
 
   it('Test Search Config', () => {
@@ -78,17 +36,13 @@ describe('Test ToolBar', () => {
       onClick: cb,
       onSearch: cb,
     });
-    act(() => {
-      render(
-        <Toolbar
-          selected={[]}
-          setSelected={() => {}}
-          toolbarConfigs={toolbarConfig}
-        ></Toolbar>,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('.input').length).toBe(0);
+    const res = render(
+      <Toolbar
+        selected={[]}
+        setSelected={() => {}}
+        toolbarConfigs={toolbarConfig}
+      ></Toolbar>
+    );
+    expect(res.getAllByPlaceholderText('search').length).toBe(1);
   });
 });

+ 1 - 1
client/src/components/__test__/grid/Utils.spec.ts

@@ -2,7 +2,7 @@ import {
   descendingComparator,
   getComparator,
   stableSort,
-} from '../../grid/Utils';
+} from '../../../utils/Sort';
 
 describe('Test Gird Utils', () => {
   it('Test descendingComparator', () => {

+ 2 - 1
client/src/components/__test__/layout/GlobalEffect.spec.tsx

@@ -1,10 +1,11 @@
 import { render, unmountComponentAtNode } from 'react-dom';
 import { act } from 'react-dom/test-utils';
 import GlobalEffect from '../../layout/GlobalEffect';
+import { vi } from 'vitest';
 
 let container: any = null;
 
-jest.mock('react-router-dom', () => {
+vi.mock('react-router-dom', () => {
   return {
     useHistory: () => ({ location: { pathname: '' } }),
   };

+ 33 - 44
client/src/components/__test__/layout/Layout.spec.tsx

@@ -1,57 +1,46 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { render } from '@testing-library/react';
 import Layout from '../../layout/Layout';
 import { MuiThemeProvider } from '@material-ui/core/styles';
 import { theme } from '../../../styles/theme';
+import { vi } from 'vitest';
 
-let container: any = null;
-
-jest.mock('react-i18next', () => ({
-  useTranslation: () => ({
-    t: (key: any) => key,
-  }),
-}));
+vi.mock('react-i18next', () => {
+  return {
+    useTranslation: () => ({
+      t: (key: any) => key,
+    }),
+  };
+});
 
-jest.mock('react-router-dom', () => ({
-  useHistory: () => ({
-    push: jest.fn(),
-  }),
-  useLocation: () => ({
-    hash: '',
-    pathname: '/use-location-mock',
-    search: '',
-    state: undefined,
-  }),
-}));
+vi.mock('react-router-dom', () => {
+  return {
+    useHistory: () => ({
+      push: vi.fn(),
+    }),
+    useLocation: () => ({
+      hash: '',
+      pathname: '/use-location-mock',
+      search: '',
+      state: undefined,
+    }),
+  };
+});
 
-jest.mock('../../layout/GlobalEffect', () => {
-  return () => {
-    return <div id="global">{}</div>;
+vi.mock('../../layout/GlobalEffect', () => {
+  return {
+    default: () => {
+      return <div role="global">{}</div>;
+    },
   };
 });
 
 describe('Test Layout', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Test Render', () => {
-    act(() => {
-      render(
-        <MuiThemeProvider theme={theme}>
-          <Layout />
-        </MuiThemeProvider>,
-        container
-      );
-    });
-
-    expect(container.querySelectorAll('#global').length).toEqual(1);
+    const res = render(
+      <MuiThemeProvider theme={theme}>
+        <Layout />
+      </MuiThemeProvider>
+    );
+    expect(res.getAllByRole('global').length).toEqual(1);
   });
 });

+ 3 - 29
client/src/components/__test__/menu/SimpleMenu.spec.tsx

@@ -1,31 +1,7 @@
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
+import { render, screen } from '@testing-library/react';
 import SimpleMenu from '../../menu/SimpleMenu';
 
-let container: any = null;
-
-jest.mock('@material-ui/core/styles/makeStyles', () => {
-  return () => () => ({});
-});
-
-jest.mock('@material-ui/core/MenuItem', () => {
-  return () => {
-    return <div className="menu-item"></div>;
-  };
-});
-
 describe('Test Simple Menu', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Test props ', () => {
     const items = [
       {
@@ -39,10 +15,8 @@ describe('Test Simple Menu', () => {
         },
       },
     ];
-    act(() => {
-      render(<SimpleMenu label="test" menuItems={items} />, container);
-    });
+    const res = render(<SimpleMenu label="test" menuItems={items} />);
 
-    expect(container.querySelector('button').textContent).toEqual('test');
+    expect(res.getByRole('button').textContent).toEqual('test');
   });
 });

+ 9 - 36
client/src/components/__test__/status/Status.spec.tsx

@@ -1,16 +1,13 @@
-import { ReactNode } from 'react';
-import { render, unmountComponentAtNode } from 'react-dom';
-import { act } from 'react-dom/test-utils';
 import Status from '../../status/Status';
-import { StatusEnum } from '../../status/Types';
+import { LOADING_STATE } from '../../../consts/Milvus';
+import { render, screen } from '@testing-library/react';
+import { vi } from 'vitest';
 
-let container: any = null;
-
-jest.mock('@material-ui/core/styles/makeStyles', () => {
+vi.mock('@material-ui/core/styles/makeStyles', () => {
   return () => () => ({});
 });
 
-jest.mock('react-i18next', () => {
+vi.mock('react-i18next', () => {
   return {
     useTranslation: () => {
       return {
@@ -26,35 +23,11 @@ jest.mock('react-i18next', () => {
   };
 });
 
-jest.mock('@material-ui/core/Typography', () => {
-  return (props: { children: ReactNode }) => {
-    return <div className="label">{props.children}</div>;
-  };
-});
-
 describe('Test Status', () => {
-  beforeEach(() => {
-    container = document.createElement('div');
-    document.body.appendChild(container);
-  });
-
-  afterEach(() => {
-    unmountComponentAtNode(container);
-    container.remove();
-    container = null;
-  });
-
   it('Test props status', () => {
-    act(() => {
-      render(<Status status={StatusEnum.loaded} />, container);
-    });
-
-    expect(container.querySelector('.label').textContent).toEqual('loaded');
-
-    act(() => {
-      render(<Status status={StatusEnum.unloaded} />, container);
-    });
-
-    expect(container.querySelector('.label').textContent).toEqual('unloaded');
+    render(<Status status={LOADING_STATE.LOADED} />);
+    expect(screen.queryByText('loaded')?.textContent).toEqual('loaded');
+    render(<Status status={LOADING_STATE.UNLOADED} />);
+    expect(screen.queryByText('unloaded')?.textContent).toEqual('unloaded');
   });
 });

+ 2 - 2
client/src/components/customDialog/CustomDialogTitle.tsx

@@ -6,8 +6,9 @@ import {
 } from '@material-ui/core';
 import MuiDialogTitle from '@material-ui/core/DialogTitle';
 import icons from '../icons/Icons';
+import { theme } from '../../styles/theme';
 
-const getStyles = makeStyles((theme: Theme) => ({
+const getStyles = makeStyles(() => ({
   root: {
     margin: 0,
     display: 'flex',
@@ -20,7 +21,6 @@ const getStyles = makeStyles((theme: Theme) => ({
   icon: {
     fontSize: '24px',
     color: theme.palette.attuDark.main,
-
     cursor: 'pointer',
   },
 }));

+ 22 - 20
client/src/components/customInput/CustomInput.tsx

@@ -32,10 +32,10 @@ const handleOnBlur = (param: IBlurParam) => {
   const input = event.target.value;
   const isValid = validations
     ? checkValid({
-      key,
-      value: input,
-      rules: validations,
-    })
+        key,
+        value: input,
+        rules: validations,
+      })
     : true;
 
   if (isValid) {
@@ -52,10 +52,10 @@ const handleOnChange = (param: IChangeParam) => {
   const input = event.target.value;
   const isValid = validations
     ? checkValid({
-      key,
-      value: input,
-      rules: validations,
-    })
+        key,
+        value: input,
+        rules: validations,
+      })
     : true;
 
   if (isValid) {
@@ -90,19 +90,20 @@ const getAdornmentInput = (
   const classes = getAdornmentStyles();
 
   const param = {
-    cb: onInputBlur || (() => { }),
+    cb: onInputBlur || (() => {}),
     validations: validations || [],
     checkValid,
   };
 
   const info = validInfo ? validInfo[key] : null;
+  const type = isPasswordType ? (showPassword ? 'text' : 'password') : 'text';
 
   return (
     <FormControl>
       <InputLabel htmlFor="standard-adornment-password">{label}</InputLabel>
       <Input
         classes={{ root: `${inputClass || {}}` }}
-        type={isPasswordType ? (showPassword ? 'text' : 'password') : 'text'}
+        type={type}
         onBlur={e => {
           handleOnBlur({ event: e, key, param });
         }}
@@ -112,13 +113,13 @@ const getAdornmentInput = (
             key,
             param: {
               ...param,
-              cb: onInputChange || (() => { }),
+              cb: onInputChange || (() => {}),
             },
           });
         }}
         endAdornment={
           <InputAdornment position="end">
-            <IconButton onClick={onIconClick || (() => { })} edge="end">
+            <IconButton onClick={onIconClick || (() => {})} edge="end" role="icon-button">
               {isPasswordType
                 ? showPassword
                   ? Icons.visible({ classes: { root: classes.icon } })
@@ -128,6 +129,7 @@ const getAdornmentInput = (
           </InputAdornment>
         }
         inputProps={{
+          'role': 'textbox',
           'data-cy': key,
         }}
       />
@@ -162,10 +164,10 @@ const getIconInput = (
         {inputType === 'icon'
           ? getTextfield(inputConfig as ITextfieldConfig, checkValid, validInfo)
           : getAdornmentInput(
-            inputConfig as IAdornmentConfig,
-            checkValid,
-            validInfo
-          )}
+              inputConfig as IAdornmentConfig,
+              checkValid,
+              validInfo
+            )}
       </Grid>
     </Grid>
   );
@@ -196,7 +198,7 @@ const getTextfield = (
   }
 
   const param = {
-    cb: onBlur || (() => { }),
+    cb: onBlur || (() => {}),
     validations: validations || [],
     checkValid,
   };
@@ -214,8 +216,8 @@ const getTextfield = (
       placeholder={placeholder || ''}
       inputProps={
         inputProps
-          ? { ...inputProps, ...defaultInputProps }
-          : { ...defaultInputProps }
+          ? { ...inputProps, ...defaultInputProps, role: 'textbox' }
+          : { ...defaultInputProps, role: 'textbox' }
       }
       error={info?.result && info.errText !== ''}
       InputProps={InputProps ? { ...InputProps } : {}}
@@ -233,7 +235,7 @@ const getTextfield = (
         handleOnChange({
           event,
           key,
-          param: { ...param, cb: onChange || (() => { }) },
+          param: { ...param, cb: onChange || (() => {}) },
         });
       }}
     />

+ 1 - 1
client/src/components/grid/Grid.tsx

@@ -177,7 +177,7 @@ const AttuGrid: FC<AttuGridType> = props => {
     >
       {title && (
         <Grid item xs={12} className={classes.tableTitle}>
-          <Breadcrumbs separator="›" aria-label="breadcrumb">
+          <Breadcrumbs separator="›" aria-label="breadcrumb" role="breadcrumb">
             {title.map(
               (v: any, i: number) =>
                 v && (

+ 1 - 1
client/src/components/grid/LoadingTable.tsx

@@ -29,7 +29,7 @@ const LoadingTable = (props: { wrapperClass?: string; count: number }) => {
   return (
     <div className={`${classes.wrapper} ${wrapperClass}`}>
       {rows.map((row, index) => (
-        <div key={index} className={classes.tr}>
+        <div key={index} className={classes.tr} role="skeleton">
           <Skeleton height={16} classes={{ root: classes.skeleton }} />
           <Skeleton height={16} classes={{ root: classes.skeleton }} />
         </div>

+ 4 - 2
client/src/components/grid/Table.tsx

@@ -213,7 +213,6 @@ const EnhancedTable: FC<TableType> = props => {
                       hover={showHoverStyle}
                       key={'row' + row[primaryKey] + index}
                       onClick={event => onSelected(event, row)}
-                      role="checkbox"
                       aria-checked={isItemSelected}
                       tabIndex={-1}
                       selected={isItemSelected && !disableSelect}
@@ -229,7 +228,10 @@ const EnhancedTable: FC<TableType> = props => {
                           <Checkbox
                             checked={isItemSelected}
                             color="primary"
-                            inputProps={{ 'aria-labelledby': labelId }}
+                            inputProps={{
+                              'aria-labelledby': labelId,
+                              role: 'checkbox',
+                            }}
                           />
                         </TableCell>
                       )}

+ 3 - 2
client/src/components/grid/TableHead.tsx

@@ -57,13 +57,13 @@ const EnhancedTableHead: FC<TableHeadType> = props => {
     <TableHead>
       <TableRow className={classes.tableRow}>
         {openCheckBox && (
-          <TableCell padding="checkbox">
+          <TableCell padding="checkbox" role="cell">
             <Checkbox
               color="primary"
               indeterminate={numSelected > 0 && numSelected < rowCount}
               checked={rowCount > 0 && numSelected === rowCount}
               onChange={onSelectAllClick}
-              inputProps={{ 'aria-label': 'select all desserts' }}
+              inputProps={{ 'aria-label': 'select all desserts', 'role': 'checkbox' }}
             />
           </TableCell>
         )}
@@ -77,6 +77,7 @@ const EnhancedTableHead: FC<TableHeadType> = props => {
               orderBy === (headCell.sortBy || headCell.id) ? order : false
             }
             className={classes.tableCell}
+            role="cell"
           >
             {headCell.label && handleSort && !headCell.notSort ? (
               <TableSortLabel

+ 3 - 1
client/src/components/grid/ToolBar.tsx

@@ -59,7 +59,7 @@ const CustomToolBar: FC<ToolBarType> = props => {
 
   return (
     <>
-      <Grid container>
+      <Grid container role="toolbar">
         <Grid item xs={8}>
           {leftConfigs.map((c, i) => {
             const isSelect = c.type === 'select' || c.type === 'groupSelect';
@@ -86,6 +86,7 @@ const CustomToolBar: FC<ToolBarType> = props => {
                 variant={c.btnVariant || 'contained'}
                 tooltip={tooltip}
                 className={classes.btn}
+                role="button"
               >
                 <Typography variant="button">{c.label}</Typography>
               </CustomButton>
@@ -97,6 +98,7 @@ const CustomToolBar: FC<ToolBarType> = props => {
                 onClick={c.onClick}
                 tooltip={tooltip}
                 disabled={disabled}
+                role="button"
               >
                 {Icon}
               </CustomIconButton>

+ 29 - 28
client/src/components/insert/ImportSample.tsx

@@ -6,35 +6,37 @@ import CustomSelector from '../customSelector/CustomSelector';
 import { rootContext } from '../../context/Root';
 import { InsertStatusEnum } from './Types';
 
-const getStyles = makeStyles((theme: Theme) => ({
-  icon: {
-    fontSize: '16px',
-  },
+const getStyles = makeStyles((theme: Theme) => {
+  return {
+    icon: {
+      fontSize: '16px',
+    },
 
-  selectors: {
-    '& .selectorWrapper': {
-      display: 'flex',
-      flexDirection: 'column',
-      marginBottom: theme.spacing(2),
+    selectors: {
+      '& .selectorWrapper': {
+        display: 'flex',
+        flexDirection: 'column',
+        marginBottom: theme.spacing(2),
 
-      '& .selectLabel': {
-        fontSize: '14px',
-        lineHeight: '20px',
-        color: theme.palette.attuDark.main,
-      },
+        '& .selectLabel': {
+          fontSize: '14px',
+          lineHeight: '20px',
+          color: theme.palette.attuDark.main,
+        },
 
-      '& .description': {
-        color: theme.palette.attuGrey.dark,
-        marginBottom: theme.spacing(1),
-        fontSize: 12,
+        '& .description': {
+          color: theme.palette.attuGrey.dark,
+          marginBottom: theme.spacing(1),
+          fontSize: 12,
+        },
       },
-    },
 
-    '& .selector': {
-      minWidth: '128px',
+      '& .selector': {
+        minWidth: '128px',
+      },
     },
-  },
-}));
+  };
+});
 
 /**
  * this component contains processes during insert
@@ -80,10 +82,7 @@ const ImportSample: FC<{ collection: string; handleImport: Function }> =
       }
       // start loading
       setInsertStatus(InsertStatusEnum.loading);
-      const { result, msg } = await props.handleImport(
-        props.collection,
-        size
-      );
+      const { result, msg } = await props.handleImport(props.collection, size);
 
       if (!result) {
         openSnackBar(msg, 'error');
@@ -97,7 +96,9 @@ const ImportSample: FC<{ collection: string; handleImport: Function }> =
 
     return (
       <DialogTemplate
-        title={insertTrans('importSampleData', { collection: props.collection })}
+        title={insertTrans('importSampleData', {
+          collection: props.collection,
+        })}
         handleClose={handleCloseDialog}
         confirmLabel={
           insertStatus === InsertStatusEnum.init

+ 15 - 22
client/src/components/layout/Layout.tsx

@@ -10,9 +10,6 @@ import { useHistory, useLocation } from 'react-router-dom';
 import { authContext } from '../../context/Auth';
 import { rootContext } from '../../context/Root';
 import { IconsType } from '../icons/Types';
-import loadable from '@loadable/component';
-
-const PLUGIN_DEV = process.env?.REACT_APP_PLUGIN_DEV;
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
@@ -79,16 +76,16 @@ const Layout = (props: any) => {
       label: navTrans('overview'),
       onClick: () => history.push('/'),
     },
-    {
-      icon: icons.navPerson,
-      label: navTrans('user'),
-      onClick: () => history.push('/users'),
-    },
     {
       icon: icons.navCollection,
       label: navTrans('collection'),
       onClick: () => history.push('/collections'),
     },
+    {
+      icon: icons.navPerson,
+      label: navTrans('user'),
+      onClick: () => history.push('/users'),
+    },
     // {
     //   icon: icons.navSearch,
     //   label: navTrans('search'),
@@ -98,10 +95,11 @@ const Layout = (props: any) => {
     // },
   ];
 
-  function importAll(r: any, outOfRoot = false) {
-    r.keys().forEach((key: any) => {
-      const content = r(key);
+  function importAll(r: any) {
+    Object.keys(r).forEach((key: any) => {
+      const content = r[key];
       const pathName = content.client?.path;
+
       if (!pathName) return;
       const result: NavMenuItem = {
         icon: icons.navOverview,
@@ -109,27 +107,22 @@ const Layout = (props: any) => {
       };
       result.onClick = () => history.push(`/${pathName}`);
       const iconName: IconsType = content.client?.iconName;
-      const iconEntry = content.client?.icon;
-      const dirName = key.split('/config.json').shift().split('/')[1];
-      // const fileEntry = content.client?.entry;
       if (iconName) {
         result.icon = icons[iconName];
-      } else if (iconEntry) {
-        const customIcon = outOfRoot
-          ? loadable(() => import(`all_plugins/${dirName}/client/${iconEntry}`))
-          : loadable(() => import(`../../plugins/${dirName}/${iconEntry}`));
-        result.icon = customIcon;
       }
       content.client?.iconActiveClass &&
         (result.iconActiveClass = content.client?.iconActiveClass);
       content.client?.iconNormalClass &&
         (result.iconNormalClass = content.client?.iconNormalClass);
+
       menuItems.push(result);
     });
   }
-  importAll(require.context('../../plugins', true, /config\.json$/));
-  PLUGIN_DEV &&
-    importAll(require.context('all_plugins/', true, /config\.json$/), true);
+  const pluginConfigs = import.meta.glob(`../../plugins/**/config.json`, {
+    eager: true,
+  });
+
+  importAll(pluginConfigs);
 
   return (
     <div className={classes.root}>

+ 2 - 2
client/src/http/Axios.ts

@@ -2,12 +2,12 @@ import axios from 'axios';
 import { MILVUS_ADDRESS } from '../consts/Localstorage';
 // import { SESSION } from '../consts/Localstorage';
 
-// console.log(process.env.NODE_ENV, 'api:', process.env.REACT_APP_BASE_URL);
+// console.log(import.meta.env.NODE_ENV, 'api:', import.meta.env.VITE_BASE_URL);
 // console.log('docker env', (window as any)._env_);
 const isElectron =
   (window as any)._env_ && (window as any)._env_.IS_ELECTRON === 'yes';
 export const url =
-  process.env.NODE_ENV === 'development' || isElectron
+  import.meta.env.MODE === 'development' || isElectron
     ? (window as any)._env_ && (window as any)._env_.HOST_URL
     : '';
 

+ 1 - 1
client/src/http/User.ts

@@ -2,7 +2,7 @@ import {
   CreateUserParams,
   DeleteUserParams,
   UpdateUserParams,
-} from 'insight_src/pages/user/Types';
+} from '../pages/user/Types';
 import BaseModel from './BaseModel';
 
 export class UserHttp extends BaseModel {

+ 9 - 9
client/src/pages/user/User.tsx

@@ -1,24 +1,24 @@
-import { UserHttp } from '../../http/User';
 import React, { useContext, useEffect, useState } from 'react';
-import AttuGrid from 'insight_src/components/grid/Grid';
+import { makeStyles, Theme } from '@material-ui/core';
+import { useTranslation } from 'react-i18next';
+import { UserHttp } from '../../http/User';
+import AttuGrid from '../../components/grid/Grid';
 import {
   ColDefinitionsType,
   ToolBarConfig,
-} from 'insight_src/components/grid/Types';
-import { makeStyles, Theme } from '@material-ui/core';
+} from '../../components/grid/Types';
 import {
   CreateUserParams,
   DeleteUserParams,
   UpdateUserParams,
   UserData,
 } from './Types';
-import { rootContext } from 'insight_src/context/Root';
+import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
+import { rootContext } from '../../context/Root';
+import { useNavigationHook } from '../../hooks/Navigation';
+import { ALL_ROUTER_TYPES } from '../../router/Types';
 import CreateUser from './Create';
-import { useTranslation } from 'react-i18next';
-import DeleteTemplate from 'insight_src/components/customDialog/DeleteDialogTemplate';
 import UpdateUser from './Update';
-import { useNavigationHook } from 'insight_src/hooks/Navigation';
-import { ALL_ROUTER_TYPES } from 'insight_src/router/Types';
 
 const useStyles = makeStyles((theme: Theme) => ({
   actionButton: {

+ 1 - 1
client/src/plugins/search/config.json

@@ -3,7 +3,7 @@
   "version": "0.1.0",
   "client": {
     "path": "search",
-    "entry": "VectorSearch.tsx",
+    "entry": "VectorSearch",
     "label": "Vector Search",
     "iconName": "navSearch",
     "auth": true,

+ 1 - 1
client/src/plugins/system/config.json

@@ -3,7 +3,7 @@
   "version": "0.1.0",
   "client": {
     "path": "system",
-    "entry": "SystemView.tsx",
+    "entry": "SystemView",
     "label": "System View",
     "iconName": "navSystem",
     "auth": true

+ 11 - 12
client/src/router/Config.ts

@@ -7,8 +7,6 @@ import { RouterConfigType } from './Types';
 import loadable from '@loadable/component';
 import Users from '../pages/user/User';
 
-const PLUGIN_DEV = process.env.REACT_APP_PLUGIN_DEV;
-
 const RouterConfig: RouterConfigType[] = [
   {
     path: '/',
@@ -33,18 +31,17 @@ const RouterConfig: RouterConfigType[] = [
   { path: '/users', component: Users, auth: true },
 ];
 
-function importAll(r: any, outOfRoot = false) {
-  r.keys().forEach((key: any) => {
-    const content = r(key);
+async function importAll(r: any) {
+  Object.keys(r).forEach((key: any) => {
+    const content = r[key];
     const dirName = key.split('/config.json').shift().split('/')[1];
     const pathName = content.client?.path;
     const fileEntry = content.client?.entry;
     if (!pathName || !fileEntry) return;
-    // console.log(content);
     const auth = content.client?.auth || false;
-    const OtherComponent = outOfRoot
-      ? loadable(() => import(`all_plugins/${dirName}/client/${fileEntry}`))
-      : loadable(() => import(`../plugins/${dirName}/${fileEntry}`));
+    const OtherComponent = loadable(
+      () => import(`../${dirName}/${pathName}/${fileEntry}.tsx`)
+    );
     RouterConfig.push({
       path: `/${pathName}`,
       component: OtherComponent,
@@ -52,8 +49,10 @@ function importAll(r: any, outOfRoot = false) {
     });
   });
 }
-importAll(require.context('../plugins/', true, /config\.json$/));
-PLUGIN_DEV &&
-  importAll(require.context('all_plugins/', true, /config\.json$/), true);
+
+const pluginConfigs = import.meta.glob(`../plugins/**/config.json`, {
+  eager: true,
+});
+importAll(pluginConfigs);
 
 export default RouterConfig;

+ 2 - 2
client/src/utils/__test__/Validation.spec.ts

@@ -45,8 +45,8 @@ describe('Test validation utils', () => {
     expect(
       getCheckResult({
         value: '12345',
-        extraParam: { lenMin: 8 },
-        rule: 'length',
+        extraParam: { min: 8 },
+        rule: 'range',
       })
     ).toBeFalsy();
   });

+ 9 - 11
client/tsconfig.json

@@ -1,11 +1,14 @@
 {
   "compilerOptions": {
-    "target": "es5",
-    "lib": [
-      "dom",
-      "dom.iterable",
-      "esnext"
+    "target": "ESNext",
+    "lib": ["dom", "dom.iterable"],
+    "types": [
+      "vite/client",
+      "vite-plugin-svgr/client",
+      "jest",
+      "@testing-library/jest-dom"
     ],
+
     "allowJs": true,
     "skipLibCheck": true,
     "esModuleInterop": true,
@@ -20,10 +23,5 @@
     "noEmit": true,
     "jsx": "react-jsx"
   },
-  "include": [
-    "src",
-    "../../src",
-    "all_plugins"
-  ],
-  "extends": "./tsconfig.paths.json"
+  "include": ["src", "../../src"]
 }

+ 0 - 9
client/tsconfig.paths.json

@@ -1,9 +0,0 @@
-{
-  "compilerOptions": {
-    "baseUrl": ".",
-    "paths": {
-      "all_plugins/*": ["src/plugins"],
-      "insight_src/*": ["src/*"]
-    }
-  }
-}

+ 25 - 0
client/vite.config.ts

@@ -0,0 +1,25 @@
+import { defineConfig } from 'vite';
+import reactRefresh from '@vitejs/plugin-react';
+const svgrPlugin = require('vite-plugin-svgr');
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  base: '',
+  // This changes the out put dir from dist to build
+  // comment this out if that isn't relevant for your project
+  build: {
+    outDir: 'build',
+  },
+  server: {
+    port: 3001,
+  },
+  plugins: [
+    reactRefresh(),
+    svgrPlugin({
+      svgrOptions: {
+        icon: true,
+        // ...svgr options (https://react-svgr.com/docs/options/)
+      },
+    }),
+  ],
+});

+ 19 - 0
client/vitest.config.ts

@@ -0,0 +1,19 @@
+/// <reference types="vitest" />
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import * as path from 'path';
+
+export default defineConfig({
+  plugins: [react()],
+  test: {
+    // include: ["src/**.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
+    environment: 'jsdom',
+    globals: true,
+    setupFiles: ['src/setupTests.ts'],
+  },
+  resolve: {
+    alias: {
+      '@': path.resolve(__dirname, './src'),
+    },
+  },
+});

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 2178 - 8873
client/yarn.lock


+ 0 - 35
server/generate-csv.ts

@@ -1,35 +0,0 @@
-import { createObjectCsvWriter as createCsvWriter } from 'csv-writer';
-
-// use to test vector insert
-const csvWriter = createCsvWriter({
-  path: './vectors.csv',
-  header: [{ id: 'vector', title: 'vector' }],
-});
-
-const records: any[] = [];
-
-const generateVector = (dimension: number) => {
-  let index = 0;
-  const vectors: any[] = [];
-  while (index < dimension) {
-    vectors.push(1 + Math.random());
-    index++;
-  }
-  return JSON.stringify(vectors);
-};
-
-while (records.length < 1000) {
-  const value = generateVector(960);
-  records.push({
-    vector: value,
-    // name: `${records.length}_id`,
-    // age: records.length * 2,
-    // job: Math.random() * 1000 > 500 ? 'designer' : 'programer',
-  });
-}
-
-csvWriter
-  .writeRecords(records) // returns a promise
-  .then(() => {
-    console.log('...Done');
-  });

+ 2 - 2
server/package.json

@@ -63,8 +63,8 @@
     "@types/swagger-jsdoc": "^6.0.1",
     "@types/swagger-ui-express": "^4.1.3",
     "@types/ws": "^8.2.0",
-    "electron": "^16.0.2",
-    "electron-builder": "^22.14.5",
+    "electron": "^21.0.1",
+    "electron-builder": "^23.6.0",
     "jest": "^27.3.1",
     "nodemon": "^2.0.14",
     "prettier": "^2.4.1",

+ 2 - 2
server/src/utils/Helper.ts

@@ -44,11 +44,11 @@ export const genRows = (fields: FieldSchema[], size: number) => {
 };
 
 export const makeRandomId = (length: number): string => {
-  var result = '';
+  let result = '';
   const characters =
     'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
   const charactersLength = characters.length;
-  for (var i = 0; i < length; i++) {
+  for (let i = 0; i < length; i++) {
     result += characters.charAt(Math.floor(Math.random() * charactersLength));
   }
   return result;

+ 262 - 185
server/yarn.lock

@@ -375,10 +375,10 @@
     ajv "^6.12.0"
     ajv-keywords "^3.4.1"
 
-"@electron/get@^1.13.0":
-  version "1.13.1"
-  resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.13.1.tgz#42a0aa62fd1189638bd966e23effaebb16108368"
-  integrity sha512-U5vkXDZ9DwXtkPqlB45tfYnnYBN8PePp1z/XDCupnSpdrxT8/ThCv9WCwPLf9oqiSGZTkH6dx2jDUPuoXpjkcA==
+"@electron/get@^1.14.1":
+  version "1.14.1"
+  resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.14.1.tgz#16ba75f02dffb74c23965e72d617adc721d27f40"
+  integrity sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==
   dependencies:
     debug "^4.1.1"
     env-paths "^2.2.0"
@@ -391,16 +391,18 @@
     global-agent "^3.0.0"
     global-tunnel-ng "^2.7.1"
 
-"@electron/universal@1.0.5":
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-1.0.5.tgz#b812340e4ef21da2b3ee77b2b4d35c9b86defe37"
-  integrity sha512-zX9O6+jr2NMyAdSkwEUlyltiI4/EBLu2Ls/VD3pUQdi3cAYeYfdQnT2AJJ38HE4QxLccbU13LSpccw1IWlkyag==
+"@electron/universal@1.2.1":
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-1.2.1.tgz#3c2c4ff37063a4e9ab1e6ff57db0bc619bc82339"
+  integrity sha512-7323HyMh7KBAl/nPDppdLsC87G6RwRU02dy5FPeGB1eS7rUePh55+WNWiDPLhFQqqVPHzh77M69uhmoT8XnwMQ==
   dependencies:
     "@malept/cross-spawn-promise" "^1.1.0"
-    asar "^3.0.3"
+    asar "^3.1.0"
     debug "^4.3.1"
     dir-compare "^2.4.0"
     fs-extra "^9.0.1"
+    minimatch "^3.0.4"
+    plist "^3.0.4"
 
 "@grpc/grpc-js@^1.2.12":
   version "1.4.2"
@@ -800,6 +802,11 @@
   resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
   integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
 
+"@tootallnate/once@2":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
+  integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
+
 "@tsconfig/node10@^1.0.7":
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9"
@@ -1032,10 +1039,10 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.24.tgz#c37ac69cb2948afb4cef95f424fa0037971a9a5c"
   integrity sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==
 
-"@types/node@^14.6.2":
-  version "14.17.34"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.34.tgz#fe4b38b3f07617c0fa31ae923fca9249641038f0"
-  integrity sha512-USUftMYpmuMzeWobskoPfzDi+vkpe0dvcOBRNOscFrGxVp4jomnRxWuVohgqBow2xyIPC0S3gjxV/5079jhmDg==
+"@types/node@^16.11.26":
+  version "16.18.3"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc"
+  integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg==
 
 "@types/plist@^3.0.1":
   version "3.0.2"
@@ -1137,6 +1144,13 @@
   dependencies:
     "@types/yargs-parser" "*"
 
+"@types/yauzl@^2.9.1":
+  version "2.10.0"
+  resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
+  integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==
+  dependencies:
+    "@types/node" "*"
+
 "@zilliz/milvus2-sdk-node@^2.1.3":
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/@zilliz/milvus2-sdk-node/-/milvus2-sdk-node-2.1.3.tgz#f8f54e1385f67edc4485ba3724b8b20f45537303"
@@ -1264,40 +1278,41 @@ anymatch@^3.0.3, anymatch@~3.1.2:
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
 
-app-builder-bin@3.7.1:
-  version "3.7.1"
-  resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-3.7.1.tgz#cb0825c5e12efc85b196ac3ed9c89f076c61040e"
-  integrity sha512-ql93vEUq6WsstGXD+SBLSIQw6SNnhbDEM0swzgugytMxLp3rT24Ag/jcC80ZHxiPRTdew1niuR7P3/FCrDqIjw==
+app-builder-bin@4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-4.0.0.tgz#1df8e654bd1395e4a319d82545c98667d7eed2f0"
+  integrity sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==
 
-app-builder-lib@22.14.5:
-  version "22.14.5"
-  resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-22.14.5.tgz#a61a50b132b858e98fdc70b6b88994ae99b4f96d"
-  integrity sha512-k3VwKP4kpsnUaXoUkm1s4zaSHPHIMFnN4kPMU9yXaKmE1LfHHqBaEah5bXeTAX5V/BC41wFdg8CF5vOjvgy8Rg==
+app-builder-lib@23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-23.6.0.tgz#03cade02838c077db99d86212d61c5fc1d6da1a8"
+  integrity sha512-dQYDuqm/rmy8GSCE6Xl/3ShJg6Ab4bZJMT8KaTKGzT436gl1DN4REP3FCWfXoh75qGTJ+u+WsdnnpO9Jl8nyMA==
   dependencies:
     "7zip-bin" "~5.1.1"
     "@develar/schema-utils" "~2.6.5"
-    "@electron/universal" "1.0.5"
+    "@electron/universal" "1.2.1"
     "@malept/flatpak-bundler" "^0.4.0"
     async-exit-hook "^2.0.1"
     bluebird-lst "^1.0.9"
-    builder-util "22.14.5"
-    builder-util-runtime "8.9.1"
+    builder-util "23.6.0"
+    builder-util-runtime "9.1.1"
     chromium-pickle-js "^0.2.0"
-    debug "^4.3.2"
-    ejs "^3.1.6"
-    electron-osx-sign "^0.5.0"
-    electron-publish "22.14.5"
+    debug "^4.3.4"
+    ejs "^3.1.7"
+    electron-osx-sign "^0.6.0"
+    electron-publish "23.6.0"
     form-data "^4.0.0"
-    fs-extra "^10.0.0"
-    hosted-git-info "^4.0.2"
+    fs-extra "^10.1.0"
+    hosted-git-info "^4.1.0"
     is-ci "^3.0.0"
-    isbinaryfile "^4.0.8"
+    isbinaryfile "^4.0.10"
     js-yaml "^4.1.0"
     lazy-val "^1.0.5"
-    minimatch "^3.0.4"
+    minimatch "^3.1.2"
     read-config-file "6.2.0"
     sanitize-filename "^1.6.3"
-    semver "^7.3.5"
+    semver "^7.3.7"
+    tar "^6.1.11"
     temp-file "^3.4.0"
 
 arg@^4.1.0:
@@ -1322,10 +1337,10 @@ array-flatten@1.1.1:
   resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
   integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
 
-asar@^3.0.3:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/asar/-/asar-3.1.0.tgz#70b0509449fe3daccc63beb4d3c7d2e24d3c6473"
-  integrity sha512-vyxPxP5arcAqN4F/ebHd/HhwnAiZtwhglvdmc7BR2f0ywbVNTOpSeyhLDbGXtE/y58hv1oC75TaNIXutnsOZsQ==
+asar@^3.1.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/asar/-/asar-3.2.0.tgz#e6edb5edd6f627ebef04db62f771c61bea9c1221"
+  integrity sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==
   dependencies:
     chromium-pickle-js "^0.2.0"
     commander "^5.0.0"
@@ -1349,10 +1364,10 @@ async-exit-hook@^2.0.1:
   resolved "https://registry.yarnpkg.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3"
   integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==
 
-async@0.9.x:
-  version "0.9.2"
-  resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
-  integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=
+async@^3.2.3:
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
+  integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
 
 asynckit@^0.4.0:
   version "0.4.0"
@@ -1590,29 +1605,31 @@ buffer@^5.1.0:
     base64-js "^1.3.1"
     ieee754 "^1.1.13"
 
-builder-util-runtime@8.9.1:
-  version "8.9.1"
-  resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.9.1.tgz#25f066b3fbc20b3e6236a9b956b1ebb0e33ff66a"
-  integrity sha512-c8a8J3wK6BIVLW7ls+7TRK9igspTbzWmUqxFbgK0m40Ggm6efUbxtWVCGIjc+dtchyr5qAMAUL6iEGRdS/6vwg==
+builder-util-runtime@9.1.1:
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.1.1.tgz#2da7b34e78a64ad14ccd070d6eed4662d893bd60"
+  integrity sha512-azRhYLEoDvRDR8Dhis4JatELC/jUvYjm4cVSj7n9dauGTOM2eeNn9KS0z6YA6oDsjI1xphjNbY6PZZeHPzzqaw==
   dependencies:
-    debug "^4.3.2"
+    debug "^4.3.4"
     sax "^1.2.4"
 
-builder-util@22.14.5:
-  version "22.14.5"
-  resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-22.14.5.tgz#42a18608d2a566c0846e91266464776c8bfb0cc9"
-  integrity sha512-zqIHDFJwmA7jV7SC9aI+33MWwT2mWoijH+Ol9IntNAwuuRXoS+7XeJwnhLBXOhcDBzXT4kDzHnRk4JKeaygEYA==
+builder-util@23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-23.6.0.tgz#1880ec6da7da3fd6fa19b8bd71df7f39e8d17dd9"
+  integrity sha512-QiQHweYsh8o+U/KNCZFSvISRnvRctb8m/2rB2I1JdByzvNKxPeFLlHFRPQRXab6aYeXc18j9LpsDLJ3sGQmWTQ==
   dependencies:
     "7zip-bin" "~5.1.1"
     "@types/debug" "^4.1.6"
     "@types/fs-extra" "^9.0.11"
-    app-builder-bin "3.7.1"
+    app-builder-bin "4.0.0"
     bluebird-lst "^1.0.9"
-    builder-util-runtime "8.9.1"
+    builder-util-runtime "9.1.1"
     chalk "^4.1.1"
     cross-spawn "^7.0.3"
-    debug "^4.3.2"
+    debug "^4.3.4"
     fs-extra "^10.0.0"
+    http-proxy-agent "^5.0.0"
+    https-proxy-agent "^5.0.0"
     is-ci "^3.0.0"
     js-yaml "^4.1.0"
     source-map-support "^0.5.19"
@@ -1675,7 +1692,7 @@ caniuse-lite@^1.0.30001274:
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001279.tgz#eb06818da481ef5096a3b3760f43e5382ed6b0ce"
   integrity sha512-VfEHpzHEXj6/CxggTwSFoZBBYGQfQv9Cf42KPlO79sWXCD1QNKWKsKzFeWL7QpZHJQYAvocqV6Rty1yJMkqWLQ==
 
-chalk@*, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
+chalk@*, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
   integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -1683,7 +1700,7 @@ chalk@*, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
-chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.2:
+chalk@^2.0.0, chalk@^2.3.0:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
   integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -1712,6 +1729,11 @@ chokidar@^3.2.2:
   optionalDependencies:
     fsevents "~2.3.2"
 
+chownr@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
+  integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
+
 chromium-pickle-js@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205"
@@ -1775,6 +1797,15 @@ cliui@^7.0.2:
     strip-ansi "^6.0.0"
     wrap-ansi "^7.0.0"
 
+cliui@^8.0.1:
+  version "8.0.1"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
+  integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
+  dependencies:
+    string-width "^4.2.0"
+    strip-ansi "^6.0.1"
+    wrap-ansi "^7.0.0"
+
 clone-response@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
@@ -1870,16 +1901,6 @@ concat-map@0.0.1:
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
 
-concat-stream@^1.6.2:
-  version "1.6.2"
-  resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
-  integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
-  dependencies:
-    buffer-from "^1.0.0"
-    inherits "^2.0.3"
-    readable-stream "^2.2.2"
-    typedarray "^0.0.6"
-
 config-chain@^1.1.11:
   version "1.1.13"
   resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
@@ -1944,11 +1965,6 @@ core-util-is@1.0.2:
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
   integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
 
-core-util-is@~1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
-  integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
-
 cors@^2.8.5, cors@~2.8.5:
   version "2.8.5"
   resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
@@ -2021,7 +2037,7 @@ data-urls@^2.0.0:
     whatwg-mimetype "^2.3.0"
     whatwg-url "^8.0.0"
 
-debug@2.6.9, debug@^2.6.8, debug@^2.6.9:
+debug@2.6.9, debug@^2.6.8:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -2042,13 +2058,20 @@ debug@^3.2.6:
   dependencies:
     ms "^2.1.1"
 
-debug@^4.3.1, debug@^4.3.2:
+debug@^4.3.1:
   version "4.3.3"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
   integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
   dependencies:
     ms "2.1.2"
 
+debug@^4.3.4:
+  version "4.3.4"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+  integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+  dependencies:
+    ms "2.1.2"
+
 decimal.js@^10.2.1:
   version "10.3.1"
   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
@@ -2143,24 +2166,24 @@ dir-compare@^2.4.0:
     commander "2.9.0"
     minimatch "3.0.4"
 
-dmg-builder@22.14.5:
-  version "22.14.5"
-  resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-22.14.5.tgz#137c0b55e639badcc0b119eb060e6fa4ed61d948"
-  integrity sha512-1GvFGQE332bvPamcMwZDqWqfWfJTyyDLOsHMcGi0zs+Jh7JOn6/zuBkHJIWHdsj2QJbhzLVyd2/ZqttOKv7I8w==
+dmg-builder@23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-23.6.0.tgz#d39d3871bce996f16c07d2cafe922d6ecbb2a948"
+  integrity sha512-jFZvY1JohyHarIAlTbfQOk+HnceGjjAdFjVn3n8xlDWKsYNqbO4muca6qXEZTfGXeQMG7TYim6CeS5XKSfSsGA==
   dependencies:
-    app-builder-lib "22.14.5"
-    builder-util "22.14.5"
-    builder-util-runtime "8.9.1"
+    app-builder-lib "23.6.0"
+    builder-util "23.6.0"
+    builder-util-runtime "9.1.1"
     fs-extra "^10.0.0"
     iconv-lite "^0.6.2"
     js-yaml "^4.1.0"
   optionalDependencies:
-    dmg-license "^1.0.9"
+    dmg-license "^1.0.11"
 
-dmg-license@^1.0.9:
-  version "1.0.10"
-  resolved "https://registry.yarnpkg.com/dmg-license/-/dmg-license-1.0.10.tgz#89f52afae25d827fce8d818c13aff30af1c16bcc"
-  integrity sha512-SVeeyiOeinV5JCPHXMdKOgK1YVbak/4+8WL2rBnfqRYpA5FaeFaQnQWb25x628am1w70CbipGDv9S51biph63A==
+dmg-license@^1.0.11:
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/dmg-license/-/dmg-license-1.0.11.tgz#7b3bc3745d1b52be7506b4ee80cb61df6e4cd79a"
+  integrity sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==
   dependencies:
     "@types/plist" "^3.0.1"
     "@types/verror" "^1.10.3"
@@ -2212,35 +2235,35 @@ ee-first@1.1.1:
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
   integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
 
-ejs@^3.1.6:
-  version "3.1.6"
-  resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a"
-  integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==
+ejs@^3.1.7:
+  version "3.1.8"
+  resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b"
+  integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==
   dependencies:
-    jake "^10.6.1"
+    jake "^10.8.5"
 
-electron-builder@^22.14.5:
-  version "22.14.5"
-  resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-22.14.5.tgz#3a25547bd4fe3728d4704da80956a794c5c31496"
-  integrity sha512-N73hSbXFz6Mz5Z6h6C5ly6CB+dUN6k1LuCDJjI8VF47bMXv/QE0HE+Kkb0GPKqTqM7Hsk/yIYX+kHCfSkR5FGg==
+electron-builder@^23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-23.6.0.tgz#c79050cbdce90ed96c5feb67c34e9e0a21b5331b"
+  integrity sha512-y8D4zO+HXGCNxFBV/JlyhFnoQ0Y0K7/sFH+XwIbj47pqaW8S6PGYQbjoObolKBR1ddQFPt4rwp4CnwMJrW3HAw==
   dependencies:
     "@types/yargs" "^17.0.1"
-    app-builder-lib "22.14.5"
-    builder-util "22.14.5"
-    builder-util-runtime "8.9.1"
+    app-builder-lib "23.6.0"
+    builder-util "23.6.0"
+    builder-util-runtime "9.1.1"
     chalk "^4.1.1"
-    dmg-builder "22.14.5"
+    dmg-builder "23.6.0"
     fs-extra "^10.0.0"
     is-ci "^3.0.0"
     lazy-val "^1.0.5"
     read-config-file "6.2.0"
-    update-notifier "^5.1.0"
-    yargs "^17.0.1"
+    simple-update-notifier "^1.0.7"
+    yargs "^17.5.1"
 
-electron-osx-sign@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.5.0.tgz#fc258c5e896859904bbe3d01da06902c04b51c3a"
-  integrity sha512-icoRLHzFz/qxzDh/N4Pi2z4yVHurlsCAYQvsCSG7fCedJ4UJXBS6PoQyGH71IfcqKupcKeK7HX/NkyfG+v6vlQ==
+electron-osx-sign@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz#9b69c191d471d9458ef5b1e4fdd52baa059f1bb8"
+  integrity sha512-+hiIEb2Xxk6eDKJ2FFlpofCnemCbjbT5jz+BKGpVBrRNT3kWTGs4DfNX6IzGwgi33hUcXF+kFs9JW+r6Wc1LRg==
   dependencies:
     bluebird "^3.5.0"
     compare-version "^0.1.2"
@@ -2249,14 +2272,14 @@ electron-osx-sign@^0.5.0:
     minimist "^1.2.0"
     plist "^3.0.1"
 
-electron-publish@22.14.5:
-  version "22.14.5"
-  resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-22.14.5.tgz#34bcdce671f0e651330db20040d6919c77c94bd6"
-  integrity sha512-h+NANRdaA0PqGF15GKvorseWPzh1PXa/zx4I37//PIokW8eKIov8ky23foUSb55ZFWUHGpxQJux7y2NCfBtQeg==
+electron-publish@23.6.0:
+  version "23.6.0"
+  resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-23.6.0.tgz#ac9b469e0b07752eb89357dd660e5fb10b3d1ce9"
+  integrity sha512-jPj3y+eIZQJF/+t5SLvsI5eS4mazCbNYqatv5JihbqOstIM13k0d1Z3vAWntvtt13Itl61SO6seicWdioOU5dg==
   dependencies:
     "@types/fs-extra" "^9.0.11"
-    builder-util "22.14.5"
-    builder-util-runtime "8.9.1"
+    builder-util "23.6.0"
+    builder-util-runtime "9.1.1"
     chalk "^4.1.1"
     fs-extra "^10.0.0"
     lazy-val "^1.0.5"
@@ -2267,14 +2290,14 @@ electron-to-chromium@^1.3.886:
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.892.tgz#0e3f5bb1de577e2e5a6dffd5a4b278c4a735cd39"
   integrity sha512-YDW4yIjdfMnbRoBjRZ/aNQYmT6JgQFLwmTSDRJMQdrY4MByEzppdXp3rnJ0g4LBWcsYTUvwKKClYN1ofZ0COOQ==
 
-electron@^16.0.2:
-  version "16.0.2"
-  resolved "https://registry.yarnpkg.com/electron/-/electron-16.0.2.tgz#6d0ae3152c410478dfffdbf80852a3840679b8c5"
-  integrity sha512-kT746yVMztrP4BbT3nrFNcUcfgFu2yelUw6TWBVTy0pju+fBISaqcvoiMrq+8U0vRpoXSu2MJYygOf4T0Det7g==
+electron@^21.0.1:
+  version "21.2.2"
+  resolved "https://registry.yarnpkg.com/electron/-/electron-21.2.2.tgz#e2f3dd014981df555d2cd1655590168b404edae4"
+  integrity sha512-Q0j1tzLTM5JRjSJVAfDSONZgdtuyruHR1pc1y2IbMYQz62pVJWVWAvcJXzpty5iRh2HKzW9+B9WVlmfWNFA8ag==
   dependencies:
-    "@electron/get" "^1.13.0"
-    "@types/node" "^14.6.2"
-    extract-zip "^1.0.3"
+    "@electron/get" "^1.14.1"
+    "@types/node" "^16.11.26"
+    extract-zip "^2.0.1"
 
 emittery@^0.8.1:
   version "0.8.1"
@@ -2461,15 +2484,16 @@ express@^4.17.1:
     utils-merge "1.0.1"
     vary "~1.1.2"
 
-extract-zip@^1.0.3:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927"
-  integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==
+extract-zip@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
+  integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
   dependencies:
-    concat-stream "^1.6.2"
-    debug "^2.6.9"
-    mkdirp "^0.5.4"
+    debug "^4.1.1"
+    get-stream "^5.1.0"
     yauzl "^2.10.0"
+  optionalDependencies:
+    "@types/yauzl" "^2.9.1"
 
 extsprintf@^1.2.0:
   version "1.4.1"
@@ -2587,6 +2611,15 @@ fs-extra@^10.0.0:
     jsonfile "^6.0.1"
     universalify "^2.0.0"
 
+fs-extra@^10.1.0:
+  version "10.1.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
+  integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
+  dependencies:
+    graceful-fs "^4.2.0"
+    jsonfile "^6.0.1"
+    universalify "^2.0.0"
+
 fs-extra@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
@@ -2615,6 +2648,13 @@ fs-extra@~7.0.1:
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
+fs-minipass@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
+  integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
+  dependencies:
+    minipass "^3.0.0"
+
 fs.realpath@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -2804,10 +2844,10 @@ helmet@^4.6.0:
   resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.6.0.tgz#579971196ba93c5978eb019e4e8ec0e50076b4df"
   integrity sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==
 
-hosted-git-info@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961"
-  integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==
+hosted-git-info@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224"
+  integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==
   dependencies:
     lru-cache "^6.0.0"
 
@@ -2870,6 +2910,15 @@ http-proxy-agent@^4.0.1:
     agent-base "6"
     debug "4"
 
+http-proxy-agent@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
+  integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
+  dependencies:
+    "@tootallnate/once" "2"
+    agent-base "6"
+    debug "4"
+
 https-proxy-agent@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
@@ -2946,7 +2995,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3:
+inherits@2, inherits@2.0.4, inherits@^2.0.3:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -3069,11 +3118,6 @@ is-yarn-global@^0.3.0:
   resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
   integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
 
-isarray@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
-  integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
-
 isbinaryfile@^3.0.2:
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.3.tgz#5d6def3edebf6e8ca8cae9c30183a804b5f8be80"
@@ -3081,10 +3125,10 @@ isbinaryfile@^3.0.2:
   dependencies:
     buffer-alloc "^1.2.0"
 
-isbinaryfile@^4.0.8:
-  version "4.0.8"
-  resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.8.tgz#5d34b94865bd4946633ecc78a026fc76c5b11fcf"
-  integrity sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==
+isbinaryfile@^4.0.10:
+  version "4.0.10"
+  resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3"
+  integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==
 
 isexe@^2.0.0:
   version "2.0.0"
@@ -3143,13 +3187,13 @@ istanbul-reports@^3.0.2:
     html-escaper "^2.0.0"
     istanbul-lib-report "^3.0.0"
 
-jake@^10.6.1:
-  version "10.8.2"
-  resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b"
-  integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==
+jake@^10.8.5:
+  version "10.8.5"
+  resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
+  integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==
   dependencies:
-    async "0.9.x"
-    chalk "^2.4.2"
+    async "^3.2.3"
+    chalk "^4.0.2"
     filelist "^1.0.1"
     minimatch "^3.0.4"
 
@@ -3871,18 +3915,45 @@ minimatch@3.0.4, minimatch@^3.0.4:
   dependencies:
     brace-expansion "^1.1.7"
 
+minimatch@^3.1.2:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+  integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+  dependencies:
+    brace-expansion "^1.1.7"
+
 minimist@^1.2.0, minimist@^1.2.5:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
   integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
 
-mkdirp@^0.5.3, mkdirp@^0.5.4:
+minipass@^3.0.0:
+  version "3.3.4"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae"
+  integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==
+  dependencies:
+    yallist "^4.0.0"
+
+minizlib@^2.1.1:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
+  integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
+  dependencies:
+    minipass "^3.0.0"
+    yallist "^4.0.0"
+
+mkdirp@^0.5.3:
   version "0.5.5"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
   integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
   dependencies:
     minimist "^1.2.5"
 
+mkdirp@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
+  integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
+
 moment-timezone@^0.5.31:
   version "0.5.33"
   resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c"
@@ -4210,11 +4281,6 @@ pretty-format@^27.0.0, pretty-format@^27.3.1:
     ansi-styles "^5.0.0"
     react-is "^17.0.1"
 
-process-nextick-args@~2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
-  integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
-
 progress@^2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
@@ -4362,19 +4428,6 @@ read-config-file@6.2.0:
     json5 "^2.2.0"
     lazy-val "^1.0.4"
 
-readable-stream@^2.2.2:
-  version "2.3.7"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
-  integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.3"
-    isarray "~1.0.0"
-    process-nextick-args "~2.0.0"
-    safe-buffer "~5.1.1"
-    string_decoder "~1.1.1"
-    util-deprecate "~1.0.1"
-
 readable-stream@^3.6.0:
   version "3.6.0"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
@@ -4476,7 +4529,7 @@ roarr@^2.15.3:
     semver-compare "^1.0.0"
     sprintf-js "^1.1.2"
 
-safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@5.1.2, safe-buffer@~5.1.1:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
@@ -4522,7 +4575,7 @@ semver-diff@^3.1.1:
   dependencies:
     semver "^6.3.0"
 
-semver@7.x, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@~7.3.0:
+semver@7.x, semver@^7.3.2, semver@^7.3.4, semver@~7.3.0:
   version "7.3.5"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
   integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
@@ -4539,6 +4592,18 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0:
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
+semver@^7.3.7:
+  version "7.3.8"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
+  integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
+  dependencies:
+    lru-cache "^6.0.0"
+
+semver@~7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
+  integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
+
 send@0.17.1:
   version "0.17.1"
   resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@@ -4611,6 +4676,13 @@ signal-exit@^3.0.2, signal-exit@^3.0.3:
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f"
   integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==
 
+simple-update-notifier@^1.0.7:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc"
+  integrity sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==
+  dependencies:
+    semver "~7.0.0"
+
 sisteransi@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
@@ -4732,7 +4804,7 @@ string-length@^4.0.1:
     char-regex "^1.0.2"
     strip-ansi "^6.0.0"
 
-string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2:
+string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -4748,13 +4820,6 @@ string_decoder@^1.1.1:
   dependencies:
     safe-buffer "~5.2.0"
 
-string_decoder@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
-  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
-  dependencies:
-    safe-buffer "~5.1.0"
-
 strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
@@ -4879,6 +4944,18 @@ symbol-tree@^3.2.4:
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
   integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
 
+tar@^6.1.11:
+  version "6.1.12"
+  resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.12.tgz#3b742fb05669b55671fb769ab67a7791ea1a62e6"
+  integrity sha512-jU4TdemS31uABHd+Lt5WEYJuzn+TJTCBLljvIAHZOz6M9Os5pJ4dD+vRFLxPa/n3T0iEFzpi+0x1UfuDZYbRMw==
+  dependencies:
+    chownr "^2.0.0"
+    fs-minipass "^2.0.0"
+    minipass "^3.0.0"
+    minizlib "^2.1.1"
+    mkdirp "^1.0.3"
+    yallist "^4.0.0"
+
 temp-file@^3.4.0:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/temp-file/-/temp-file-3.4.0.tgz#766ea28911c683996c248ef1a20eea04d51652c7"
@@ -5100,11 +5177,6 @@ typedarray-to-buffer@^3.1.5:
   dependencies:
     is-typedarray "^1.0.0"
 
-typedarray@^0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
-  integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-
 typescript@^4.4.4, typescript@~4.4.2:
   version "4.4.4"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c"
@@ -5176,7 +5248,7 @@ utf8-byte-length@^1.0.1:
   resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61"
   integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=
 
-util-deprecate@^1.0.1, util-deprecate@~1.0.1:
+util-deprecate@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
@@ -5369,6 +5441,11 @@ yargs-parser@20.x, yargs-parser@^20.2.2:
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
   integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
 
+yargs-parser@^21.1.1:
+  version "21.1.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
+  integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
 yargs@^16.1.1, yargs@^16.2.0:
   version "16.2.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
@@ -5382,18 +5459,18 @@ yargs@^16.1.1, yargs@^16.2.0:
     y18n "^5.0.5"
     yargs-parser "^20.2.2"
 
-yargs@^17.0.1:
-  version "17.2.1"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea"
-  integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==
+yargs@^17.5.1:
+  version "17.6.2"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541"
+  integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==
   dependencies:
-    cliui "^7.0.2"
+    cliui "^8.0.1"
     escalade "^3.1.1"
     get-caller-file "^2.0.5"
     require-directory "^2.1.1"
-    string-width "^4.2.0"
+    string-width "^4.2.3"
     y18n "^5.0.5"
-    yargs-parser "^20.2.2"
+    yargs-parser "^21.1.1"
 
 yauzl@^2.10.0:
   version "2.10.0"

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно