Przeglądaj źródła

Merge pull request #10146 from weseek/support/136168-136169-update-axios-for-pdf-converter-client-and-remark-packages

support: Update axios for pdf converter client and remark packages
Futa Arai 7 miesięcy temu
rodzic
commit
4e9e69a647

+ 0 - 2
apps/app/package.json

@@ -273,9 +273,7 @@
     "@popperjs/core": "^2.11.8",
     "@swc-node/jest": "^1.8.1",
     "@swc/jest": "^0.2.36",
-    "@testing-library/dom": "^10.4.0",
     "@testing-library/jest-dom": "^6.5.0",
-    "@testing-library/react": "^16.0.1",
     "@testing-library/user-event": "^14.5.2",
     "@types/archiver": "^6.0.2",
     "@types/bunyan": "^1.8.11",

+ 0 - 1
apps/pdf-converter/package.json

@@ -36,7 +36,6 @@
     "@tsed/schema": "=8.5.0",
     "@tsed/swagger": "=8.5.0",
     "@tsed/terminus": "=8.5.0",
-    "axios": "^0.24.0",
     "express": "^4.19.2",
     "puppeteer": "^23.1.1",
     "puppeteer-cluster": "^0.24.0",

+ 3 - 0
package.json

@@ -47,6 +47,9 @@
     "@changesets/cli": "^2.27.3",
     "@faker-js/faker": "^9.0.1",
     "@playwright/test": "^1.49.1",
+    "@testing-library/dom": "^10.4.0",
+    "@testing-library/react": "^16.0.1",
+    "@testing-library/react-hooks": "^8.0.1",
     "@swc-node/register": "^1.10.9",
     "@swc/core": "^1.5.25",
     "@swc/helpers": "^0.5.11",

+ 1 - 1
packages/pdf-converter-client/package.json

@@ -12,7 +12,7 @@
     "build": "pnpm gen:client-code && tsc -p tsconfig.json"
   },
   "dependencies": {
-    "axios": "^0.24.0",
+    "axios": "^1.10.0",
     "tslib": "^2.8.0"
   },
   "devDependencies": {

+ 8 - 3
packages/remark-attachment-refs/package.json

@@ -41,16 +41,16 @@
     "lint:styles": "stylelint \"src/**/*.scss\" \"src/**/*.css\"",
     "lint:typecheck": "vue-tsc --noEmit",
     "lint": "run-p lint:*",
-    "test": ""
+    "test": "vitest run --coverage"
   },
   "dependencies": {
     "@growi/core": "workspace:^",
     "@growi/remark-growi-directive": "workspace:^",
     "@growi/ui": "workspace:^",
-    "axios": "^0.24.0",
+    "axios": "^1.10.0",
     "bunyan": "^1.8.15",
-    "hast-util-select": "^6.0.2",
     "express": "^4.20.0",
+    "hast-util-select": "^6.0.2",
     "mongoose": "^6.13.6",
     "swr": "^2.3.2",
     "universal-bunyan": "^0.9.2",
@@ -59,10 +59,15 @@
   "devDependencies": {
     "@types/bunyan": "^1.8.11",
     "@types/hast": "^3.0.4",
+    "@types/react": "^18.2.14",
+    "@types/react-dom": "^18.2.6",
+    "@types/supertest": "^6.0.2",
     "csstype": "^3.0.2",
+    "happy-dom": "^15.7.4",
     "hast-util-sanitize": "^5.0.1",
     "hast-util-select": "^6.0.2",
     "npm-run-all": "^4.1.5",
+    "supertest": "^7.0.0",
     "unified": "^11.0.0",
     "unist-util-visit": "^5.0.0"
   },

+ 2 - 0
packages/remark-attachment-refs/src/@types/declaration.d.ts

@@ -0,0 +1,2 @@
+// prevent TS2307: Cannot find module './xxx.module.scss' or its corresponding type declarations.
+declare module '*.scss';

+ 298 - 0
packages/remark-attachment-refs/src/client/stores/refs.spec.ts

@@ -0,0 +1,298 @@
+// tests for assuring axios request succeeds in version change
+
+import type { Server } from 'node:http';
+import { renderHook, waitFor } from '@testing-library/react';
+import axios from 'axios';
+import express from 'express';
+import refsMiddleware from '../../server';
+
+import { useSWRxRef, useSWRxRefs } from './refs';
+
+// Mock the IAttachmentHasId type for testing
+const mockAttachment = {
+  _id: '507f1f77bcf86cd799439011',
+  fileFormat: 'image/jpeg',
+  fileName: 'test-image.jpg',
+  originalName: 'test-image.jpg',
+  filePath: 'attachment/507f1f77bcf86cd799439011.jpg',
+  creator: {
+    _id: '507f1f77bcf86cd799439012',
+    name: 'Test User',
+    username: 'testuser',
+  },
+  page: '507f1f77bcf86cd799439013',
+  createdAt: '2023-01-01T00:00:00.000Z',
+  fileSize: 1024000,
+};
+
+// Mock PageQueryBuilder
+const mockPageQueryBuilder = {
+  addConditionToListWithDescendants: vi.fn().mockReturnThis(),
+  addConditionToExcludeTrashed: vi.fn().mockReturnThis(),
+  query: {
+    select: vi.fn().mockReturnValue({
+      exec: vi.fn().mockResolvedValue([{ id: '507f1f77bcf86cd799439013' }]),
+    }),
+    and: vi.fn().mockReturnThis(),
+  },
+};
+
+vi.mock('mongoose', async (importOriginal) => {
+  const actual = await importOriginal<typeof import('mongoose')>();
+  return {
+    ...actual,
+    default: {
+      ...actual.default,
+      model: vi.fn().mockImplementation((modelName) => {
+        const mockModel = {
+          findByPathAndViewer: vi.fn().mockResolvedValue({
+            _id: '507f1f77bcf86cd799439013',
+            path: '/test-page',
+          }),
+          isAccessiblePageByViewer: vi.fn().mockResolvedValue(true),
+          find: vi.fn().mockReturnValue({
+            select: vi.fn().mockReturnValue({
+              exec: vi
+                .fn()
+                .mockResolvedValue([{ id: '507f1f77bcf86cd799439013' }]),
+            }),
+            and: vi.fn().mockReturnThis(),
+          }),
+          addConditionToFilteringByViewerForList: vi.fn(),
+          PageQueryBuilder: vi
+            .fn()
+            .mockImplementation(() => mockPageQueryBuilder),
+        };
+
+        if (modelName === 'Attachment') {
+          return {
+            findOne: vi.fn().mockReturnValue({
+              populate: vi.fn().mockResolvedValue(mockAttachment),
+            }),
+            find: vi.fn().mockReturnValue({
+              and: vi.fn().mockReturnThis(),
+              populate: vi.fn().mockReturnThis(),
+              exec: vi.fn().mockResolvedValue([mockAttachment]),
+            }),
+          };
+        }
+
+        return mockModel;
+      }),
+    },
+    model: vi.fn().mockImplementation((modelName) => {
+      const mockModel = {
+        findByPathAndViewer: vi.fn().mockResolvedValue({
+          _id: '507f1f77bcf86cd799439013',
+          path: '/test-page',
+        }),
+        isAccessiblePageByViewer: vi.fn().mockResolvedValue(true),
+        find: vi.fn().mockReturnValue({
+          select: vi.fn().mockReturnValue({
+            exec: vi
+              .fn()
+              .mockResolvedValue([{ id: '507f1f77bcf86cd799439013' }]),
+          }),
+          and: vi.fn().mockReturnThis(),
+        }),
+        addConditionToFilteringByViewerForList: vi.fn(),
+        PageQueryBuilder: vi
+          .fn()
+          .mockImplementation(() => mockPageQueryBuilder),
+      };
+
+      if (modelName === 'Attachment') {
+        return {
+          findOne: vi.fn().mockReturnValue({
+            populate: vi.fn().mockResolvedValue(mockAttachment),
+          }),
+          find: vi.fn().mockReturnValue({
+            and: vi.fn().mockReturnThis(),
+            populate: vi.fn().mockReturnThis(),
+            exec: vi.fn().mockResolvedValue([mockAttachment]),
+          }),
+        };
+      }
+
+      return mockModel;
+    }),
+  };
+});
+
+// Mock @growi/core modules
+vi.mock('@growi/core', () => ({
+  SCOPE: {
+    READ: { FEATURES: { PAGE: 'read:page' } },
+  },
+}));
+
+vi.mock('@growi/core/dist/models/serializers', () => ({
+  serializeAttachmentSecurely: vi
+    .fn()
+    .mockImplementation((attachment) => attachment),
+}));
+
+vi.mock('@growi/core/dist/remark-plugins', () => ({
+  OptionParser: {
+    parseRange: vi.fn().mockReturnValue({ start: 1, end: 3 }),
+  },
+}));
+
+// Mock FilterXSS
+vi.mock('xss', () => ({
+  FilterXSS: vi.fn().mockImplementation(() => ({
+    process: vi.fn().mockImplementation((input) => input),
+  })),
+}));
+
+const TEST_PORT = 3002;
+const TEST_SERVER_URL = `http://localhost:${TEST_PORT}`;
+
+describe('useSWRxRef and useSWRxRefs integration tests', () => {
+  let server: Server;
+  let app: express.Application;
+
+  const setupAxiosSpy = () => {
+    const originalAxios = axios.create();
+    return vi.spyOn(axios, 'get').mockImplementation((url, config) => {
+      const fullUrl = url.startsWith('/_api')
+        ? `${TEST_SERVER_URL}${url}`
+        : url;
+      return originalAxios.get(fullUrl, config);
+    });
+  };
+
+  beforeAll(async () => {
+    app = express();
+    app.use(express.json());
+    app.use(express.urlencoded({ extended: true }));
+
+    app.use((req, res, next) => {
+      res.header('Access-Control-Allow-Origin', '*');
+      res.header(
+        'Access-Control-Allow-Methods',
+        'GET, POST, PUT, DELETE, OPTIONS',
+      );
+      res.header(
+        'Access-Control-Allow-Headers',
+        'Origin, X-Requested-With, Content-Type, Accept, Authorization',
+      );
+      next();
+    });
+
+    const mockCrowi = {
+      require: () => () => (req: any, res: any, next: any) => next(),
+      accessTokenParser: () => (req: any, res: any, next: any) => {
+        req.user = { _id: '507f1f77bcf86cd799439012', username: 'testuser' };
+        next();
+      },
+    };
+
+    refsMiddleware(mockCrowi, app);
+
+    return new Promise<void>((resolve) => {
+      server = app.listen(TEST_PORT, () => {
+        resolve();
+      });
+    });
+  });
+
+  afterAll(() => {
+    return new Promise<void>((resolve) => {
+      if (server) {
+        server.close(() => {
+          resolve();
+        });
+      } else {
+        resolve();
+      }
+    });
+  });
+
+  describe('useSWRxRef', () => {
+    it('should make actual server request and receive attachment data for single ref request', async () => {
+      const axiosGetSpy = setupAxiosSpy();
+
+      const { result } = renderHook(() =>
+        useSWRxRef('/test-page', 'test-image.jpg', false),
+      );
+
+      await waitFor(() => expect(result.current.data).toBeDefined(), {
+        timeout: 5000,
+      });
+
+      expect(axiosGetSpy).toHaveBeenCalledWith(
+        '/_api/attachment-refs/ref',
+        expect.objectContaining({
+          params: expect.objectContaining({
+            pagePath: '/test-page',
+            fileNameOrId: 'test-image.jpg',
+          }),
+        }),
+      );
+
+      expect(result.current.data).toBeDefined();
+      expect(result.current.error).toBeUndefined();
+
+      axiosGetSpy.mockRestore();
+    });
+  });
+
+  describe('useSWRxRefs', () => {
+    it('should make actual server request and receive attachments data for refs request with pagePath', async () => {
+      const axiosGetSpy = setupAxiosSpy();
+
+      const { result } = renderHook(() =>
+        useSWRxRefs('/test-page', undefined, {}, false),
+      );
+
+      await waitFor(() => expect(result.current.data).toBeDefined(), {
+        timeout: 5000,
+      });
+
+      expect(axiosGetSpy).toHaveBeenCalledWith(
+        '/_api/attachment-refs/refs',
+        expect.objectContaining({
+          params: expect.objectContaining({
+            pagePath: '/test-page',
+            prefix: undefined,
+            options: {},
+          }),
+        }),
+      );
+
+      expect(result.current.data).toBeDefined();
+      expect(result.current.error).toBeUndefined();
+
+      axiosGetSpy.mockRestore();
+    });
+
+    it('should make actual server request and receive attachments data for refs request with prefix', async () => {
+      const axiosGetSpy = setupAxiosSpy();
+
+      const { result } = renderHook(() =>
+        useSWRxRefs('', '/test-prefix', { depth: '2' }, false),
+      );
+
+      await waitFor(() => expect(result.current.data).toBeDefined(), {
+        timeout: 5000,
+      });
+
+      expect(axiosGetSpy).toHaveBeenCalledWith(
+        '/_api/attachment-refs/refs',
+        expect.objectContaining({
+          params: expect.objectContaining({
+            pagePath: '',
+            prefix: '/test-prefix',
+            options: { depth: '2' },
+          }),
+        }),
+      );
+
+      expect(result.current.data).toBeDefined();
+      expect(result.current.error).toBeUndefined();
+
+      axiosGetSpy.mockRestore();
+    });
+  });
+});

+ 4 - 3
packages/remark-attachment-refs/src/server/routes/refs.ts

@@ -179,9 +179,10 @@ export const routesFactory = (crowi): any => {
     async (req: RequestWithUser, res) => {
       const user = req.user;
       const { prefix, pagePath } = req.query;
-      const options: Record<string, string | undefined> = JSON.parse(
-        req.query.options?.toString() ?? '',
-      );
+      const options: Record<string, string | undefined> =
+        typeof req.query.options === 'string'
+          ? JSON.parse(req.query.options)
+          : (req.query.options ?? {});
 
       // check either 'prefix' or 'pagePath ' is specified
       if (prefix == null && pagePath == null) {

+ 1 - 0
packages/remark-attachment-refs/tsconfig.json

@@ -7,6 +7,7 @@
     "paths": {
       "~/*": ["./src/*"]
     },
+    "types": ["vitest/globals"],
 
     /* TODO: remove below flags for strict checking */
     "strict": false,

+ 16 - 0
packages/remark-attachment-refs/vitest.config.ts

@@ -0,0 +1,16 @@
+import tsconfigPaths from 'vite-tsconfig-paths';
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+  plugins: [tsconfigPaths()],
+  test: {
+    environment: 'node',
+    clearMocks: true,
+    globals: true,
+    environmentMatchGlobs: [
+      // Use jsdom for client-side tests
+      ['**/client/**/*.spec.ts', 'jsdom'],
+      ['**/client/**/*.test.ts', 'jsdom'],
+    ],
+  },
+});

+ 3 - 2
packages/remark-lsx/package.json

@@ -47,10 +47,11 @@
   "devDependencies": {
     "@types/express": "^4",
     "@types/hast": "^3.0.4",
-    "axios": "^0.24.0",
-    "is-absolute-url": "^4.0.1",
+    "axios": "^1.10.0",
     "hast-util-sanitize": "^5.0.1",
     "hast-util-select": "^6.0.2",
+    "is-absolute-url": "^4.0.1",
+    "jsdom": "^26.1.0",
     "unified": "^11.0.0",
     "unist-util-visit": "^5.0.0"
   },

+ 153 - 0
packages/remark-lsx/src/client/stores/lsx/lsx.spec.ts

@@ -0,0 +1,153 @@
+// tests for assuring axios request succeeds in version change
+
+import type { Server } from 'node:http';
+import { renderHook, waitFor } from '@testing-library/react';
+import axios from 'axios';
+import express from 'express';
+import lsxMiddleware from '../../../server';
+
+import { useSWRxLsx } from './lsx';
+
+// Mock the generateBaseQuery function
+vi.mock('../../../server/routes/list-pages/generate-base-query', () => ({
+  generateBaseQuery: vi.fn().mockResolvedValue({
+    query: {
+      skip: vi.fn().mockReturnThis(),
+      limit: vi.fn().mockReturnThis(),
+      sort: vi.fn().mockReturnThis(),
+      and: vi.fn().mockReturnThis(),
+      clone: vi.fn().mockReturnThis(),
+      count: vi.fn().mockResolvedValue(10),
+      exec: vi.fn().mockResolvedValue([]),
+    },
+    addConditionToListOnlyDescendants: vi.fn().mockReturnThis(),
+    addConditionToFilteringByViewerForList: vi.fn().mockReturnThis(),
+  }),
+}));
+
+// Mock mongoose model
+vi.mock('mongoose', () => ({
+  model: vi.fn().mockReturnValue({
+    find: vi.fn().mockReturnValue({
+      skip: vi.fn().mockReturnThis(),
+      limit: vi.fn().mockReturnThis(),
+      sort: vi.fn().mockReturnThis(),
+      and: vi.fn().mockReturnThis(),
+      clone: vi.fn().mockReturnThis(),
+      count: vi.fn().mockResolvedValue(10),
+      exec: vi.fn().mockResolvedValue([]),
+    }),
+    countDocuments: vi.fn().mockResolvedValue(0),
+    aggregate: vi.fn().mockResolvedValue([{ count: 5 }]),
+  }),
+}));
+
+const TEST_PORT = 3001;
+const TEST_SERVER_URL = `http://localhost:${TEST_PORT}`;
+
+describe('useSWRxLsx integration tests', () => {
+  let server: Server;
+  let app: express.Application;
+
+  // Helper function to setup axios spy
+  const setupAxiosSpy = () => {
+    const originalAxios = axios.create();
+    return vi.spyOn(axios, 'get').mockImplementation((url, config) => {
+      const fullUrl = url.startsWith('/_api')
+        ? `${TEST_SERVER_URL}${url}`
+        : url;
+      return originalAxios.get(fullUrl, config);
+    });
+  };
+
+  beforeAll(async () => {
+    // Create minimal Express app with just the LSX route
+    app = express();
+    app.use(express.json());
+    app.use(express.urlencoded({ extended: true }));
+
+    // Add CORS headers to prevent cross-origin issues
+    app.use((req, res, next) => {
+      res.header('Access-Control-Allow-Origin', '*');
+      res.header(
+        'Access-Control-Allow-Methods',
+        'GET, POST, PUT, DELETE, OPTIONS',
+      );
+      res.header(
+        'Access-Control-Allow-Headers',
+        'Origin, X-Requested-With, Content-Type, Accept, Authorization',
+      );
+      next();
+    });
+
+    // Mock minimal GROWI-like structure for the middleware
+    const mockCrowi = {
+      require: () => () => (req: any, res: any, next: any) => next(),
+      accessTokenParser: () => (req: any, res: any, next: any) => next(),
+    };
+
+    // Import and setup the LSX middleware
+    lsxMiddleware(mockCrowi, app);
+
+    // Start test server
+    return new Promise<void>((resolve) => {
+      server = app.listen(TEST_PORT, () => {
+        resolve();
+      });
+    });
+  });
+
+  afterAll(() => {
+    return new Promise<void>((resolve) => {
+      if (server) {
+        server.close(() => {
+          resolve();
+        });
+      } else {
+        resolve();
+      }
+    });
+  });
+
+  it('should make actual server request and receive 2xx response for basic lsx request', async () => {
+    const axiosGetSpy = setupAxiosSpy();
+
+    const { result } = renderHook(() =>
+      useSWRxLsx('/test-page', { depth: '1' }, false),
+    );
+
+    await waitFor(() => expect(result.current.data).toBeDefined(), {
+      timeout: 5000,
+    });
+
+    expect(axiosGetSpy).toHaveBeenCalledWith(
+      '/_api/lsx',
+      expect.objectContaining({
+        params: expect.objectContaining({
+          pagePath: '/test-page',
+          options: expect.objectContaining({ depth: '1' }),
+        }),
+      }),
+    );
+
+    expect(result.current.data).toBeDefined();
+    expect(result.current.error).toBeUndefined();
+
+    axiosGetSpy.mockRestore();
+  });
+
+  it('should handle server validation errors properly', async () => {
+    const axiosGetSpy = setupAxiosSpy();
+
+    const { result } = renderHook(() => useSWRxLsx('', {}, false));
+
+    await waitFor(() => expect(result.current.error).toBeDefined(), {
+      timeout: 5000,
+    });
+
+    expect(result.current.error).toBeDefined();
+    expect(result.current.data).toBeUndefined();
+
+    axiosGetSpy.mockRestore();
+  });
+});

+ 2 - 1
packages/remark-lsx/src/server/index.ts

@@ -19,7 +19,8 @@ const lsxValidator = [
     .optional()
     .customSanitizer((options) => {
       try {
-        const jsonData: LsxApiOptions = JSON.parse(options);
+        const jsonData: LsxApiOptions =
+          typeof options === 'string' ? JSON.parse(options) : options;
 
         for (const key in jsonData) {
           jsonData[key] = filterXSS.process(jsonData[key]);

+ 5 - 0
packages/remark-lsx/vitest.config.ts

@@ -7,5 +7,10 @@ export default defineConfig({
     environment: 'node',
     clearMocks: true,
     globals: true,
+    environmentMatchGlobs: [
+      // Use jsdom for client-side tests
+      ['**/client/**/*.spec.ts', 'jsdom'],
+      ['**/client/**/*.test.ts', 'jsdom'],
+    ],
   },
 });

+ 320 - 23
pnpm-lock.yaml

@@ -37,6 +37,15 @@ importers:
       '@swc/helpers':
         specifier: ^0.5.11
         version: 0.5.15
+      '@testing-library/dom':
+        specifier: ^10.4.0
+        version: 10.4.0
+      '@testing-library/react':
+        specifier: ^16.0.1
+        version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+      '@testing-library/react-hooks':
+        specifier: ^8.0.1
+        version: 8.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
       '@types/css-modules':
         specifier: ^1.0.2
         version: 1.0.2
@@ -186,7 +195,7 @@ importers:
         version: 5.0.1(typescript@5.0.4)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.43.1))
       vitest:
         specifier: ^2.1.1
-        version: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.43.1)
+        version: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.43.1)
       vitest-mock-extended:
         specifier: ^2.0.2
         version: 2.0.2(typescript@5.0.4)(vitest@2.1.1)
@@ -806,15 +815,9 @@ importers:
       '@swc/jest':
         specifier: ^0.2.36
         version: 0.2.36(@swc/core@1.10.7(@swc/helpers@0.5.15))
-      '@testing-library/dom':
-        specifier: ^10.4.0
-        version: 10.4.0
       '@testing-library/jest-dom':
         specifier: ^6.5.0
         version: 6.5.0
-      '@testing-library/react':
-        specifier: ^16.0.1
-        version: 16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
       '@testing-library/user-event':
         specifier: ^14.5.2
         version: 14.5.2(@testing-library/dom@10.4.0)
@@ -1064,9 +1067,6 @@ importers:
       '@tsed/terminus':
         specifier: '=8.5.0'
         version: 8.5.0(@godaddy/terminus@4.12.1)(@tsed/core@8.5.0)(@tsed/di@8.5.0(@tsed/core@8.5.0)(@tsed/hooks@8.5.0)(@tsed/logger@7.0.2)(@tsed/schema@8.5.0(@tsed/core@8.5.0)(@tsed/hooks@8.5.0)(@tsed/openspec@8.5.0)))(@tsed/platform-http@8.5.0(@tsed/engines@8.5.0)(@tsed/logger@7.0.2)(@tsed/openspec@8.5.0))(@tsed/schema@8.5.0(@tsed/core@8.5.0)(@tsed/hooks@8.5.0)(@tsed/openspec@8.5.0))
-      axios:
-        specifier: ^0.24.0
-        version: 0.24.0
       express:
         specifier: ^4.19.2
         version: 4.21.0
@@ -1415,8 +1415,8 @@ importers:
   packages/pdf-converter-client:
     dependencies:
       axios:
-        specifier: ^0.24.0
-        version: 0.24.0
+        specifier: ^1.10.0
+        version: 1.11.0
       tslib:
         specifier: ^2.8.0
         version: 2.8.1
@@ -1548,8 +1548,8 @@ importers:
         specifier: workspace:^
         version: link:../ui
       axios:
-        specifier: ^0.24.0
-        version: 0.24.0
+        specifier: ^1.10.0
+        version: 1.11.0
       bunyan:
         specifier: ^1.8.15
         version: 1.8.15
@@ -1584,15 +1584,30 @@ importers:
       '@types/hast':
         specifier: ^3.0.4
         version: 3.0.4
+      '@types/react':
+        specifier: ^18.2.14
+        version: 18.3.3
+      '@types/react-dom':
+        specifier: ^18.2.6
+        version: 18.3.0
+      '@types/supertest':
+        specifier: ^6.0.2
+        version: 6.0.3
       csstype:
         specifier: ^3.0.2
         version: 3.1.3
+      happy-dom:
+        specifier: ^15.7.4
+        version: 15.7.4
       hast-util-sanitize:
         specifier: ^5.0.1
         version: 5.0.1
       npm-run-all:
         specifier: ^4.1.5
         version: 4.1.5
+      supertest:
+        specifier: ^7.0.0
+        version: 7.1.1
       unified:
         specifier: ^11.0.0
         version: 11.0.5
@@ -1759,8 +1774,8 @@ importers:
         specifier: ^3.0.4
         version: 3.0.4
       axios:
-        specifier: ^0.24.0
-        version: 0.24.0
+        specifier: ^1.10.0
+        version: 1.11.0
       hast-util-sanitize:
         specifier: ^5.0.1
         version: 5.0.1
@@ -1770,6 +1785,9 @@ importers:
       is-absolute-url:
         specifier: ^4.0.1
         version: 4.0.1
+      jsdom:
+        specifier: ^26.1.0
+        version: 26.1.0
       unified:
         specifier: ^11.0.0
         version: 11.0.5
@@ -1908,6 +1926,9 @@ packages:
     peerDependencies:
       openapi-types: '>=7'
 
+  '@asamuzakjp/css-color@3.2.0':
+    resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
+
   '@asyncapi/specs@4.3.1':
     resolution: {integrity: sha512-EfexhJu/lwF8OdQDm28NKLJHFkx0Gb6O+rcezhZYLPIoNYKXJMh2J1vFGpwmfAcTTh+ffK44Oc2Hs1Q4sLBp+A==}
 
@@ -2716,16 +2737,44 @@ packages:
     resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
     engines: {node: '>=12'}
 
+  '@csstools/color-helpers@5.0.2':
+    resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==}
+    engines: {node: '>=18'}
+
+  '@csstools/css-calc@2.1.4':
+    resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      '@csstools/css-parser-algorithms': ^3.0.5
+      '@csstools/css-tokenizer': ^3.0.4
+
+  '@csstools/css-color-parser@3.0.10':
+    resolution: {integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      '@csstools/css-parser-algorithms': ^3.0.5
+      '@csstools/css-tokenizer': ^3.0.4
+
   '@csstools/css-parser-algorithms@2.6.3':
     resolution: {integrity: sha512-xI/tL2zxzEbESvnSxwFgwvy5HS00oCXxL4MLs6HUiDcYfwowsoQaABKxUElp1ARITrINzBnsECOc1q0eg2GOrA==}
     engines: {node: ^14 || ^16 || >=18}
     peerDependencies:
       '@csstools/css-tokenizer': ^2.3.1
 
+  '@csstools/css-parser-algorithms@3.0.5':
+    resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      '@csstools/css-tokenizer': ^3.0.4
+
   '@csstools/css-tokenizer@2.3.1':
     resolution: {integrity: sha512-iMNHTyxLbBlWIfGtabT157LH9DUx9X8+Y3oymFEuMj8HNc+rpE3dPFGFgHjpKfjeFDjLjYIAIhXPGvS2lKxL9g==}
     engines: {node: ^14 || ^16 || >=18}
 
+  '@csstools/css-tokenizer@3.0.4':
+    resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
+    engines: {node: '>=18'}
+
   '@csstools/media-query-list-parser@2.1.11':
     resolution: {integrity: sha512-uox5MVhvNHqitPP+SynrB1o8oPxPMt2JLgp5ghJOWf54WGQ5OKu47efne49r1SWqs3wRP8xSWjnO9MBKxhB1dA==}
     engines: {node: ^14 || ^16 || >=18}
@@ -5054,6 +5103,22 @@ packages:
     resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==}
     engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
 
+  '@testing-library/react-hooks@8.0.1':
+    resolution: {integrity: sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==}
+    engines: {node: '>=12'}
+    peerDependencies:
+      '@types/react': ^16.9.0 || ^17.0.0
+      react: ^16.9.0 || ^17.0.0
+      react-dom: ^16.9.0 || ^17.0.0
+      react-test-renderer: ^16.9.0 || ^17.0.0
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      react-dom:
+        optional: true
+      react-test-renderer:
+        optional: true
+
   '@testing-library/react@16.0.1':
     resolution: {integrity: sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg==}
     engines: {node: '>=18'}
@@ -6072,6 +6137,10 @@ packages:
     resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==}
     engines: {node: '>= 14'}
 
+  agent-base@7.1.4:
+    resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+    engines: {node: '>= 14'}
+
   agentkeepalive@4.5.0:
     resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==}
     engines: {node: '>= 8.0.0'}
@@ -7647,6 +7716,10 @@ packages:
     resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
     engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
 
+  cssstyle@4.6.0:
+    resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
+    engines: {node: '>=18'}
+
   csstype@3.1.3:
     resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
 
@@ -7846,6 +7919,10 @@ packages:
     resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
     engines: {node: '>= 14'}
 
+  data-urls@5.0.0:
+    resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
+    engines: {node: '>=18'}
+
   data-view-buffer@1.0.1:
     resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==}
     engines: {node: '>= 0.4'}
@@ -7941,6 +8018,9 @@ packages:
     resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
     engines: {node: '>=0.10.0'}
 
+  decimal.js@10.6.0:
+    resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
   decode-named-character-reference@1.0.2:
     resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
 
@@ -8305,6 +8385,10 @@ packages:
     resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
     engines: {node: '>=0.12'}
 
+  entities@6.0.1:
+    resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
+    engines: {node: '>=0.12'}
+
   env-paths@2.2.1:
     resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
     engines: {node: '>=6'}
@@ -9448,6 +9532,10 @@ packages:
     resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==}
     engines: {node: '>=14'}
 
+  html-encoding-sniffer@4.0.0:
+    resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
+    engines: {node: '>=18'}
+
   html-escaper@2.0.2:
     resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
 
@@ -9512,6 +9600,10 @@ packages:
     resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==}
     engines: {node: '>= 14'}
 
+  https-proxy-agent@7.0.6:
+    resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+    engines: {node: '>= 14'}
+
   human-id@1.0.2:
     resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==}
 
@@ -9890,6 +9982,9 @@ packages:
     resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
     engines: {node: '>=0.10.0'}
 
+  is-potential-custom-element-name@1.0.1:
+    resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
   is-property@1.0.2:
     resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}
 
@@ -10216,6 +10311,15 @@ packages:
   jsbn@1.1.0:
     resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==}
 
+  jsdom@26.1.0:
+    resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      canvas: ^3.0.0
+    peerDependenciesMeta:
+      canvas:
+        optional: true
+
   jsep@1.3.9:
     resolution: {integrity: sha512-i1rBX5N7VPl0eYb6+mHNp52sEuaS2Wi8CDYx1X5sn9naevL78+265XJqy1qENEk7mRKwS06NHpUqiBwR7qeodw==}
     engines: {node: '>= 10.16.0'}
@@ -11609,6 +11713,9 @@ packages:
   numbro@2.5.0:
     resolution: {integrity: sha512-xDcctDimhzko/e+y+Q2/8i3qNC9Svw1QgOkSkQoO0kIPI473tR9QRbo2KP88Ty9p8WbPy+3OpTaAIzehtuHq+A==}
 
+  nwsapi@2.2.21:
+    resolution: {integrity: sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==}
+
   oas-kit-common@1.0.8:
     resolution: {integrity: sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==}
 
@@ -11910,6 +12017,9 @@ packages:
   parse5@7.1.2:
     resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
 
+  parse5@7.3.0:
+    resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+
   parseurl@1.3.3:
     resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
     engines: {node: '>= 0.8'}
@@ -12970,6 +13080,9 @@ packages:
   roughjs@4.6.6:
     resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==}
 
+  rrweb-cssom@0.8.0:
+    resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
+
   run-async@2.4.1:
     resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
     engines: {node: '>=0.12.0'}
@@ -13031,6 +13144,10 @@ packages:
   sax@1.3.0:
     resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
 
+  saxes@6.0.0:
+    resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+    engines: {node: '>=v12.22.7'}
+
   scheduler@0.23.0:
     resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
 
@@ -13751,6 +13868,9 @@ packages:
     peerDependencies:
       react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
 
+  symbol-tree@3.2.4:
+    resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
   synckit@0.7.2:
     resolution: {integrity: sha512-CSZRtSRZ8RhJGMtWyLRqlarmWPPlsgZJHtV6cz0VTHNOg+R7UBoE2eNPQmB5Qrhtk3RX2AAcJmVwMXFULVQSwg==}
     engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
@@ -13875,6 +13995,13 @@ packages:
     resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
     engines: {node: '>=14.0.0'}
 
+  tldts-core@6.1.86:
+    resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
+
+  tldts@6.1.86:
+    resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
+    hasBin: true
+
   tmp@0.0.33:
     resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
     engines: {node: '>=0.6.0'}
@@ -13924,6 +14051,10 @@ packages:
     resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}
     engines: {node: '>=0.8'}
 
+  tough-cookie@5.1.2:
+    resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
+    engines: {node: '>=16'}
+
   tr46@0.0.3:
     resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
 
@@ -13935,6 +14066,10 @@ packages:
     resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==}
     engines: {node: '>=14'}
 
+  tr46@5.1.1:
+    resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
+    engines: {node: '>=18'}
+
   traverse@0.3.9:
     resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==}
 
@@ -14697,6 +14832,10 @@ packages:
   w3c-keyname@2.2.8:
     resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
 
+  w3c-xmlserializer@5.0.0:
+    resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+    engines: {node: '>=18'}
+
   walker@1.0.8:
     resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
 
@@ -14746,10 +14885,18 @@ packages:
       webpack-cli:
         optional: true
 
+  whatwg-encoding@3.1.1:
+    resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
+    engines: {node: '>=18'}
+
   whatwg-mimetype@3.0.0:
     resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
     engines: {node: '>=12'}
 
+  whatwg-mimetype@4.0.0:
+    resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
+    engines: {node: '>=18'}
+
   whatwg-url@11.0.0:
     resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==}
     engines: {node: '>=12'}
@@ -14758,6 +14905,10 @@ packages:
     resolution: {integrity: sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==}
     engines: {node: '>=16'}
 
+  whatwg-url@14.2.0:
+    resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
+    engines: {node: '>=18'}
+
   whatwg-url@5.0.0:
     resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
 
@@ -14908,6 +15059,10 @@ packages:
     resolution: {integrity: sha512-4Av83DdvAgUQQMfi/w8G01aJshbEZP9ewjmZMpS9t3H+OCZBDvyK4GJPnHGfWiXlArnPbYvR58JB9qF2x9Ds+Q==}
     engines: {node: '>=12'}
 
+  xml-name-validator@5.0.0:
+    resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+    engines: {node: '>=18'}
+
   xml2js@0.4.19:
     resolution: {integrity: sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==}
 
@@ -14927,6 +15082,9 @@ packages:
     resolution: {integrity: sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==}
     engines: {node: '>=4.0'}
 
+  xmlchars@2.2.0:
+    resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
   xmldom-sre@0.1.31:
     resolution: {integrity: sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==}
     engines: {node: '>=0.1'}
@@ -15158,6 +15316,14 @@ snapshots:
       call-me-maybe: 1.0.2
       openapi-types: 12.1.3
 
+  '@asamuzakjp/css-color@3.2.0':
+    dependencies:
+      '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-tokenizer': 3.0.4
+      lru-cache: 10.4.3
+
   '@asyncapi/specs@4.3.1':
     dependencies:
       '@types/json-schema': 7.0.15
@@ -16937,12 +17103,32 @@ snapshots:
     dependencies:
       '@jridgewell/trace-mapping': 0.3.9
 
+  '@csstools/color-helpers@5.0.2': {}
+
+  '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+    dependencies:
+      '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-tokenizer': 3.0.4
+
+  '@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+    dependencies:
+      '@csstools/color-helpers': 5.0.2
+      '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+      '@csstools/css-tokenizer': 3.0.4
+
   '@csstools/css-parser-algorithms@2.6.3(@csstools/css-tokenizer@2.3.1)':
     dependencies:
       '@csstools/css-tokenizer': 2.3.1
 
+  '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
+    dependencies:
+      '@csstools/css-tokenizer': 3.0.4
+
   '@csstools/css-tokenizer@2.3.1': {}
 
+  '@csstools/css-tokenizer@3.0.4': {}
+
   '@csstools/media-query-list-parser@2.1.11(@csstools/css-parser-algorithms@2.6.3(@csstools/css-tokenizer@2.3.1))(@csstools/css-tokenizer@2.3.1)':
     dependencies:
       '@csstools/css-parser-algorithms': 2.6.3(@csstools/css-tokenizer@2.3.1)
@@ -20051,14 +20237,21 @@ snapshots:
       lodash: 4.17.21
       redent: 3.0.0
 
-  '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+  '@testing-library/react-hooks@8.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
+    dependencies:
+      '@babel/runtime': 7.25.4
+      react: 18.2.0
+      react-error-boundary: 3.1.4(react@18.2.0)
+    optionalDependencies:
+      react-dom: 18.2.0(react@18.2.0)
+
+  '@testing-library/react@16.0.1(@testing-library/dom@10.4.0)(@types/react-dom@18.3.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
     dependencies:
       '@babel/runtime': 7.25.4
       '@testing-library/dom': 10.4.0
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     optionalDependencies:
-      '@types/react': 18.3.3
       '@types/react-dom': 18.3.0
 
   '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)':
@@ -21288,7 +21481,7 @@ snapshots:
       std-env: 3.7.0
       test-exclude: 7.0.1
       tinyrainbow: 1.2.0
-      vitest: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.43.1)
+      vitest: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.43.1)
     transitivePeerDependencies:
       - supports-color
 
@@ -21335,7 +21528,7 @@ snapshots:
       sirv: 2.0.4
       tinyglobby: 0.2.6
       tinyrainbow: 1.2.0
-      vitest: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.43.1)
+      vitest: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.43.1)
 
   '@vitest/utils@2.1.1':
     dependencies:
@@ -21569,6 +21762,8 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  agent-base@7.1.4: {}
+
   agentkeepalive@4.5.0:
     dependencies:
       humanize-ms: 1.2.1
@@ -21917,7 +22112,7 @@ snapshots:
 
   axios@1.11.0:
     dependencies:
-      follow-redirects: 1.15.9(debug@4.4.1)
+      follow-redirects: 1.15.11
       form-data: 4.0.4
       proxy-from-env: 1.1.0
     transitivePeerDependencies:
@@ -23067,6 +23262,11 @@ snapshots:
     dependencies:
       css-tree: 2.2.1
 
+  cssstyle@4.6.0:
+    dependencies:
+      '@asamuzakjp/css-color': 3.2.0
+      rrweb-cssom: 0.8.0
+
   csstype@3.1.3: {}
 
   csurf@1.11.0:
@@ -23289,6 +23489,11 @@ snapshots:
 
   data-uri-to-buffer@6.0.2: {}
 
+  data-urls@5.0.0:
+    dependencies:
+      whatwg-mimetype: 4.0.0
+      whatwg-url: 14.2.0
+
   data-view-buffer@1.0.1:
     dependencies:
       call-bind: 1.0.7
@@ -23362,6 +23567,8 @@ snapshots:
 
   decamelize@1.2.0: {}
 
+  decimal.js@10.6.0: {}
+
   decode-named-character-reference@1.0.2:
     dependencies:
       character-entities: 2.0.2
@@ -23724,6 +23931,8 @@ snapshots:
 
   entities@4.5.0: {}
 
+  entities@6.0.1: {}
+
   env-paths@2.2.1: {}
 
   environment@1.1.0: {}
@@ -25312,6 +25521,10 @@ snapshots:
 
   hpagent@1.2.0: {}
 
+  html-encoding-sniffer@4.0.0:
+    dependencies:
+      whatwg-encoding: 3.1.1
+
   html-escaper@2.0.2: {}
 
   html-parse-stringify@3.0.1:
@@ -25410,6 +25623,13 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  https-proxy-agent@7.0.6:
+    dependencies:
+      agent-base: 7.1.4
+      debug: 4.4.1(supports-color@5.5.0)
+    transitivePeerDependencies:
+      - supports-color
+
   human-id@1.0.2: {}
 
   human-signals@2.1.0: {}
@@ -25741,6 +25961,8 @@ snapshots:
 
   is-plain-object@5.0.0: {}
 
+  is-potential-custom-element-name@1.0.1: {}
+
   is-property@1.0.2: {}
 
   is-regex@1.1.4:
@@ -26230,6 +26452,33 @@ snapshots:
 
   jsbn@1.1.0: {}
 
+  jsdom@26.1.0:
+    dependencies:
+      cssstyle: 4.6.0
+      data-urls: 5.0.0
+      decimal.js: 10.6.0
+      html-encoding-sniffer: 4.0.0
+      http-proxy-agent: 7.0.2
+      https-proxy-agent: 7.0.6
+      is-potential-custom-element-name: 1.0.1
+      nwsapi: 2.2.21
+      parse5: 7.3.0
+      rrweb-cssom: 0.8.0
+      saxes: 6.0.0
+      symbol-tree: 3.2.4
+      tough-cookie: 5.1.2
+      w3c-xmlserializer: 5.0.0
+      webidl-conversions: 7.0.0
+      whatwg-encoding: 3.1.1
+      whatwg-mimetype: 4.0.0
+      whatwg-url: 14.2.0
+      ws: 8.18.0
+      xml-name-validator: 5.0.0
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+
   jsep@1.3.9: {}
 
   jsesc@2.5.2: {}
@@ -28000,6 +28249,8 @@ snapshots:
     dependencies:
       bignumber.js: 9.1.2
 
+  nwsapi@2.2.21: {}
+
   oas-kit-common@1.0.8:
     dependencies:
       fast-safe-stringify: 2.1.1
@@ -28395,6 +28646,10 @@ snapshots:
     dependencies:
       entities: 4.5.0
 
+  parse5@7.3.0:
+    dependencies:
+      entities: 6.0.1
+
   parseurl@1.3.3: {}
 
   pascal-case@3.1.2:
@@ -29667,6 +29922,8 @@ snapshots:
       points-on-curve: 0.2.0
       points-on-path: 0.2.1
 
+  rrweb-cssom@0.8.0: {}
+
   run-async@2.4.1: {}
 
   run-async@3.0.0: {}
@@ -29725,6 +29982,10 @@ snapshots:
 
   sax@1.3.0: {}
 
+  saxes@6.0.0:
+    dependencies:
+      xmlchars: 2.2.0
+
   scheduler@0.23.0:
     dependencies:
       loose-envify: 1.4.0
@@ -30638,6 +30899,8 @@ snapshots:
       react: 18.2.0
       use-sync-external-store: 1.4.0(react@18.2.0)
 
+  symbol-tree@3.2.4: {}
+
   synckit@0.7.2:
     dependencies:
       '@pkgr/utils': 2.3.0
@@ -30787,6 +31050,12 @@ snapshots:
 
   tinyspy@3.0.2: {}
 
+  tldts-core@6.1.86: {}
+
+  tldts@6.1.86:
+    dependencies:
+      tldts-core: 6.1.86
+
   tmp@0.0.33:
     dependencies:
       os-tmpdir: 1.0.2
@@ -30827,6 +31096,10 @@ snapshots:
       psl: 1.9.0
       punycode: 2.3.1
 
+  tough-cookie@5.1.2:
+    dependencies:
+      tldts: 6.1.86
+
   tr46@0.0.3: {}
 
   tr46@3.0.0:
@@ -30837,6 +31110,10 @@ snapshots:
     dependencies:
       punycode: 2.3.1
 
+  tr46@5.1.1:
+    dependencies:
+      punycode: 2.3.1
+
   traverse@0.3.9: {}
 
   trim-lines@3.0.1: {}
@@ -31549,9 +31826,9 @@ snapshots:
     dependencies:
       ts-essentials: 10.0.2(typescript@5.0.4)
       typescript: 5.0.4
-      vitest: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.43.1)
+      vitest: 2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.43.1)
 
-  vitest@2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(sass@1.77.6)(terser@5.43.1):
+  vitest@2.1.1(@types/node@20.14.0)(@vitest/ui@2.1.1)(happy-dom@15.7.4)(jsdom@26.1.0)(sass@1.77.6)(terser@5.43.1):
     dependencies:
       '@vitest/expect': 2.1.1
       '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.19(@types/node@20.14.0)(sass@1.77.6)(terser@5.43.1))
@@ -31576,6 +31853,7 @@ snapshots:
       '@types/node': 20.14.0
       '@vitest/ui': 2.1.1(vitest@2.1.1)
       happy-dom: 15.7.4
+      jsdom: 26.1.0
     transitivePeerDependencies:
       - less
       - lightningcss
@@ -31640,6 +31918,10 @@ snapshots:
 
   w3c-keyname@2.2.8: {}
 
+  w3c-xmlserializer@5.0.0:
+    dependencies:
+      xml-name-validator: 5.0.0
+
   walker@1.0.8:
     dependencies:
       makeerror: 1.0.12
@@ -31719,8 +32001,14 @@ snapshots:
       - esbuild
       - uglify-js
 
+  whatwg-encoding@3.1.1:
+    dependencies:
+      iconv-lite: 0.6.3
+
   whatwg-mimetype@3.0.0: {}
 
+  whatwg-mimetype@4.0.0: {}
+
   whatwg-url@11.0.0:
     dependencies:
       tr46: 3.0.0
@@ -31731,6 +32019,11 @@ snapshots:
       tr46: 4.1.1
       webidl-conversions: 7.0.0
 
+  whatwg-url@14.2.0:
+    dependencies:
+      tr46: 5.1.1
+      webidl-conversions: 7.0.0
+
   whatwg-url@5.0.0:
     dependencies:
       tr46: 0.0.3
@@ -31864,6 +32157,8 @@ snapshots:
       escape-html: 1.0.3
       xpath: 0.0.32
 
+  xml-name-validator@5.0.0: {}
+
   xml2js@0.4.19:
     dependencies:
       sax: 1.3.0
@@ -31880,6 +32175,8 @@ snapshots:
 
   xmlbuilder@9.0.7: {}
 
+  xmlchars@2.2.0: {}
+
   xmldom-sre@0.1.31: {}
 
   xmlhttprequest-ssl@2.1.2: {}