diff --git a/.cspell.json b/.cspell.json index 78e40221ff3..5b4cbf3728f 100644 --- a/.cspell.json +++ b/.cspell.json @@ -620,7 +620,9 @@ "uuidv", "Vonage", "runtimes", - "cafebabe" + "cafebabe", + "Icann", + "limitbar", ], "flagWords": [], "patterns": [ @@ -683,6 +685,7 @@ "apps/api/src/.env.test", "apps/ws/src/.env.test", "apps/ws/src/.example.env", + "apps/web/playwright.config.ts", ".cspell.json", "package.json", "package-lock.json", diff --git a/.github/workflows/reusable-web-e2e.yml b/.github/workflows/reusable-web-e2e.yml index 97c89a0a630..c96469d8e4b 100644 --- a/.github/workflows/reusable-web-e2e.yml +++ b/.github/workflows/reusable-web-e2e.yml @@ -24,6 +24,10 @@ jobs: # leaving the Dashboard hanging ... # https://github.com/cypress-io/github-action/issues/48 fail-fast: false + matrix: + # run 5 copies of the current job in parallel + containers: [1, 2, 3, 4, 5] + total: [5] # The type of runner that the job will run on runs-on: ubuntu-latest @@ -104,8 +108,7 @@ jobs: - run: | echo "BROWSER_PATH=$(which chrome)" >> $GITHUB_ENV - - name: Cypress run EE e2e - if: ${{ steps.setup.outputs.has_token == 'true' && inputs.ee }} + - name: Cypress run e2e uses: cypress-io/github-action@v6 env: NODE_ENV: test @@ -117,35 +120,64 @@ jobs: with: working-directory: apps/web browser: "${{ env.BROWSER_PATH }}" - record: false + record: true + parallel: true install: false - parallel: false config-file: cypress.config.ts - spec: "cypress/tests/**/*.spec-ee.{js,jsx,ts,tsx}" + spec: | + cypress/tests/**/*.spec.ts + ${{ steps.setup.outputs.has_token == 'true' && inputs.ee && 'cypress/tests/**/*.spec-ee.ts' }} - - name: Cypress run e2e - uses: cypress-io/github-action@v6 - env: - NODE_ENV: test - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_WEB_KEY }} - CYPRESS_GITHUB_USER_EMAIL: ${{ secrets.CYPRESS_GITHUB_USER_EMAIL }} - CYPRESS_GITHUB_USER_PASSWORD: ${{ secrets.CYPRESS_GITHUB_USER_PASSWORD }} - CYPRESS_IS_CI: true + - name: Playwright Install + working-directory: apps/web + run: pnpm playwright:install + + - name: Run Playwright tests + working-directory: apps/web + run: pnpm playwright:test --shard=${{ matrix.containers }}/${{ matrix.total }} + + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} with: - working-directory: apps/web - browser: "${{ env.BROWSER_PATH }}" - record: false - parallel: false - install: false - config-file: cypress.config.ts - spec: "cypress/tests/**/*.spec.{js,jsx,ts,tsx}" + name: blob-report-${{ matrix.containers }} + path: apps/web/blob-report + retention-days: 1 - - uses: actions/upload-artifact@v3 - if: failure() + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} with: - name: cypress-screenshots + name: cypress-screenshots-${{ matrix.containers }} path: apps/web/cypress/screenshots + retention-days: 30 + + merge-reports: + # Merge reports after playwright-tests, even if some shards have failed + if: ${{ !cancelled() }} + needs: [e2e_web] + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20.8.1 + + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v4 + with: + path: all-blob-reports + pattern: blob-report-* + merge-multiple: true + + - name: Merge into HTML Report + run: npx playwright merge-reports --reporter html ./all-blob-reports + + - name: Upload HTML report + uses: actions/upload-artifact@v4 + with: + name: html-report--attempt-${{ github.run_attempt }} + path: playwright-report + retention-days: 14 component_web: if: "!contains(github.event.head_commit.message, 'ci skip')" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 86a5862f34c..1bfa4f329a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -108,30 +108,6 @@ jobs: if: ${{ contains(fromJson(needs.get-affected.outputs.test-cypress), '@novu/widget') || contains(fromJson(needs.get-affected.outputs.test-unit), '@novu/notification-center') || contains(fromJson(needs.get-affected.outputs.test-unit), '@novu/ws') }} secrets: inherit - build_docker_api: - name: Build Docker API - runs-on: ubuntu-latest - timeout-minutes: 80 - needs: [get-affected] - if: ${{ contains(fromJson(needs.get-affected.outputs.test-e2e), '@novu/api') }} - permissions: - contents: read - packages: write - deployments: write - id-token: write - strategy: - matrix: - name: ['novu/api', 'novu/api-ee'] - steps: - - uses: actions/checkout@v3 - - uses: ./.github/actions/setup-project - - uses: ./.github/actions/setup-redis-cluster - - uses: ./.github/actions/docker/build-api - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - fork: ${{ github.event.pull_request.head.repo.fork }} - docker_name: ${{ matrix.name }} - test_providers: name: Unit Test Providers runs-on: ubuntu-latest diff --git a/apps/api/src/app/blueprint/blueprint.controller.ts b/apps/api/src/app/blueprint/blueprint.controller.ts index e9f95111375..0efb9b61dcb 100644 --- a/apps/api/src/app/blueprint/blueprint.controller.ts +++ b/apps/api/src/app/blueprint/blueprint.controller.ts @@ -1,5 +1,4 @@ import { ClassSerializerInterceptor, Controller, Get, Param, UseInterceptors } from '@nestjs/common'; - import { EnvironmentRepository, NotificationTemplateRepository } from '@novu/dal'; import { GroupedBlueprintResponse } from './dto/grouped-blueprint.response.dto'; diff --git a/apps/web/.env.playwirght.test b/apps/web/.env.playwirght.test new file mode 100644 index 00000000000..ba5e5ef43c5 --- /dev/null +++ b/apps/web/.env.playwirght.test @@ -0,0 +1,3 @@ +NODE_ENV=test +MONGODB_URL=mongodb://127.0.0.1:27017/novu-test +API_URL=http://127.0.0.1:1336 diff --git a/apps/web/.eslintrc.js b/apps/web/.eslintrc.js index a882dc23378..4417ceb2f7a 100644 --- a/apps/web/.eslintrc.js +++ b/apps/web/.eslintrc.js @@ -41,7 +41,7 @@ module.exports = { env: { 'cypress/globals': true, }, - ignorePatterns: ['craco.config.js', 'cypress/*', '**/styled-system/**/*'], + ignorePatterns: ['craco.config.js', 'cypress/*', '**/styled-system/**/*', 'tests/*'], extends: ['plugin:cypress/recommended', '../../.eslintrc.js'], plugins: ['cypress', 'react-hooks'], parserOptions: { diff --git a/apps/web/.gitignore b/apps/web/.gitignore index 44f85e781ec..3267e0dcdb1 100644 --- a/apps/web/.gitignore +++ b/apps/web/.gitignore @@ -29,4 +29,8 @@ cypress/screenshots ## Panda styled-system -styled-system-studio \ No newline at end of file +styled-system-studio +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/apps/web/cypress/tests/activity-graph.spec.ts b/apps/web/cypress/tests/activity-graph.spec.ts index 09ac09f78f5..e9a2aa20d70 100644 --- a/apps/web/cypress/tests/activity-graph.spec.ts +++ b/apps/web/cypress/tests/activity-graph.spec.ts @@ -1,4 +1,8 @@ -describe('Activity page', function () { +/** + * The tests from this file were moved to the corresponding Playwright file apps/web/tests/activity-graph.spec.ts. + * @deprecated + */ +describe.skip('Activity page', function () { beforeEach(function () { // @ts-expect-error cy.initializeSession() diff --git a/apps/web/cypress/tests/digest-playground.spec.ts b/apps/web/cypress/tests/digest-playground.spec.ts index 88fae2c7852..5ffff1d51bd 100644 --- a/apps/web/cypress/tests/digest-playground.spec.ts +++ b/apps/web/cypress/tests/digest-playground.spec.ts @@ -1,4 +1,8 @@ -describe('Digest Playground Workflow Page', function () { +/** + * The tests from this file were moved to the corresponding Playwright file apps/web/tests/digest-playground.spec.ts. + * @deprecated + */ +describe.skip('Digest Playground Workflow Page', function () { beforeEach(function () { cy.initializeSession({ noTemplates: true }).as('session'); }); diff --git a/apps/web/cypress/tests/integrations-list-modal.spec.ts b/apps/web/cypress/tests/integrations-list-modal.spec.ts index ccd963585a3..fe8c2b4e0d5 100644 --- a/apps/web/cypress/tests/integrations-list-modal.spec.ts +++ b/apps/web/cypress/tests/integrations-list-modal.spec.ts @@ -18,7 +18,11 @@ Cypress.on('window:before:load', (win) => { win.isDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches; }); -describe('Integrations List Modal', function () { +/** + * The tests from this file were moved to the corresponding Playwright file apps/web/tests/integrations-list-modal.spec.ts. + * @deprecated + */ +describe.skip('Integrations List Modal', function () { let session: any; beforeEach(function () { diff --git a/apps/web/cypress/tests/integrations-list-page.spec.ts b/apps/web/cypress/tests/integrations-list-page.spec.ts index f3b0d339912..57a31fa54bb 100644 --- a/apps/web/cypress/tests/integrations-list-page.spec.ts +++ b/apps/web/cypress/tests/integrations-list-page.spec.ts @@ -18,7 +18,11 @@ Cypress.on('window:before:load', (win) => { win.isDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches; }); -describe('Integrations List Page', function () { +/** + * The tests from this file were moved to the corresponding Playwright file apps/web/tests/integrations-list-page.spec.ts. + * @deprecated + */ +describe.skip('Integrations List Page', function () { let session: any; beforeEach(function () { diff --git a/apps/web/cypress/tests/notification-editor/main-functionality.spec.ts b/apps/web/cypress/tests/notification-editor/main-functionality.spec.ts index 4a6856c83fa..12a5af166fd 100644 --- a/apps/web/cypress/tests/notification-editor/main-functionality.spec.ts +++ b/apps/web/cypress/tests/notification-editor/main-functionality.spec.ts @@ -1,6 +1,10 @@ import { addAndEditChannel, clickWorkflow, dragAndDrop, editChannel, fillBasicNotificationDetails, goBack } from '.'; -describe('Workflow Editor - Main Functionality', function () { +/** + * The tests from this file were moved to the corresponding Playwright file apps/web/tests/main-functionality.spec.ts. + * @deprecated + */ +describe.skip('Workflow Editor - Main Functionality', function () { beforeEach(function () { cy.initializeSession().as('session'); }); diff --git a/apps/web/cypress/tests/start-from-scratch-tour.spec.ts b/apps/web/cypress/tests/start-from-scratch-tour.spec.ts index dbcdd76d6a7..ace6acd53fd 100644 --- a/apps/web/cypress/tests/start-from-scratch-tour.spec.ts +++ b/apps/web/cypress/tests/start-from-scratch-tour.spec.ts @@ -1,4 +1,8 @@ -describe('Start from scratch tour hints', function () { +/** + * The tests from this file were moved to the corresponding Playwright file apps/web/tests/start-from-scratch-tour.spec.ts. + * @deprecated + */ +describe.skip('Start from scratch tour hints', function () { beforeEach(function () { cy.initializeSession({ showOnBoardingTour: true }).as('session'); }); diff --git a/apps/web/package.json b/apps/web/package.json index 84356a43fd5..c2770d7b481 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -3,7 +3,7 @@ "version": "0.24.1", "private": true, "scripts": { - "start": "pnpm panda --watch & cross-env PORT=4200 react-app-rewired start", + "start": "pnpm panda --watch & cross-env NODE_OPTIONS=--max_old_space_size=8192 PORT=4200 react-app-rewired start", "prebuild": "rimraf build", "build": "pnpm panda && cross-env NODE_OPTIONS=--max_old_space_size=4096 GENERATE_SOURCEMAP=false react-app-rewired --max_old_space_size=4096 build", "precommit": "lint-staged", @@ -19,6 +19,12 @@ "cypress:install": "cypress install", "cypress:open": "cross-env NODE_ENV=test cypress open", "cypress:run:components": "cross-env NODE_OPTIONS=--max_old_space_size=4096 NODE_ENV=test cypress run --component", + "playwright:install": "playwright install --with-deps", + "playwright:test": "playwright test", + "playwright:test-ui": "playwright test --ui", + "playwright:codegen": "playwright codegen", + "playwright:show-report": "npx playwright show-report", + "playwright:merge-report": "playwright merge-reports --reporter html", "start:api": "cd ../../ && pnpm start:api:test", "storybook": "storybook dev -p 6006 -s public", "build-storybook": "storybook build -s public", @@ -126,6 +132,7 @@ }, "optionalDependencies": { "@novu/ee-billing-web": "^0.24.1", + "@novu/ee-echo-web": "^0.24.1", "@novu/ee-translation-web": "^0.24.1" }, "devDependencies": { @@ -137,6 +144,7 @@ "@novu/dal": "^0.24.1", "@novu/testing": "^0.24.1", "@pandacss/dev": "^0.34.0", + "@playwright/test": "^1.42.1", "@storybook/addon-actions": "^7.4.2", "@storybook/addon-essentials": "^7.4.2", "@storybook/addon-links": "^7.4.2", diff --git a/apps/web/panda.config.ts b/apps/web/panda.config.ts index 6684e53d9e5..8c6d171c36e 100644 --- a/apps/web/panda.config.ts +++ b/apps/web/panda.config.ts @@ -40,7 +40,7 @@ export default defineConfig({ // Recommended by panda maintainer due to potential bug with nesting styled-system in src importMap: 'styled-system', - + // Enables JSX util generation! jsxFramework: 'react', }); diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts new file mode 100644 index 00000000000..c6872da1b35 --- /dev/null +++ b/apps/web/playwright.config.ts @@ -0,0 +1,87 @@ +import { defineConfig, devices } from '@playwright/test'; +import dotenv from 'dotenv'; +import path from 'path'; + +dotenv.config({ path: path.resolve(__dirname, '.env.playwirght.test') }); + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 4 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: process.env.CI ? 'blob' : 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://127.0.0.1:4200', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + timeout: 60_000, + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + /* + * { + * name: 'firefox', + * use: { ...devices['Desktop Firefox'] }, + * }, + * { + * name: 'webkit', + * use: { ...devices['Desktop Safari'] }, + * }, + */ + + /* Test against mobile viewports. */ + /* + * { + * name: 'Mobile Chrome', + * use: { ...devices['Pixel 5'] }, + * }, + * { + * name: 'Mobile Safari', + * use: { ...devices['iPhone 12'] }, + * }, + */ + + /* Test against branded browsers. */ + /* + * { + * name: 'Microsoft Edge', + * use: { ...devices['Desktop Edge'], channel: 'msedge' }, + * }, + * { + * name: 'Google Chrome', + * use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + * }, + */ + ], + + /* Run your local dev server before starting the tests */ + /* + * webServer: { + * command: 'npm run start', + * url: 'http://127.0.0.1:3000', + * reuseExistingServer: !process.env.CI, + * }, + */ +}); diff --git a/apps/web/src/pages/auth/components/QuestionnaireForm.tsx b/apps/web/src/pages/auth/components/QuestionnaireForm.tsx index d698ba91821..c8c59eb87c1 100644 --- a/apps/web/src/pages/auth/components/QuestionnaireForm.tsx +++ b/apps/web/src/pages/auth/components/QuestionnaireForm.tsx @@ -9,7 +9,6 @@ import { JobTitleEnum, jobTitleToLabelMapper, ProductUseCasesEnum } from '@novu/ import type { ProductUseCases, IResponseError, ICreateOrganizationDto, IJwtPayload } from '@novu/shared'; import { Button, - colors, Digest, HalfClock, Input, @@ -26,6 +25,7 @@ import { useVercelIntegration, useVercelParams } from '../../../hooks'; import { ROUTES } from '../../../constants/routes.enum'; import { DynamicCheckBox } from './dynamic-checkbox/DynamicCheckBox'; import styled from '@emotion/styled/macro'; +import { useDomainParser } from './useDomainHook'; import { OnboardingExperimentV2ModalKey } from '../../../constants/experimentsConstants'; export function QuestionnaireForm() { @@ -34,11 +34,13 @@ export function QuestionnaireForm() { handleSubmit, formState: { errors }, control, + setError, } = useForm({}); const navigate = useNavigate(); const { setToken, token } = useAuthContext(); const { startVercelSetup } = useVercelIntegration(); const { isFromVercel } = useVercelParams(); + const { parse } = useDomainParser(); const { mutateAsync: createOrganizationMutation } = useMutation< { _id: string }, @@ -162,9 +164,14 @@ export function QuestionnaireForm() { name="domain" control={control} rules={{ - pattern: { - value: /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/, - message: 'Please make sure you specified a valid domain', + validate: { + isValiDomain: (value) => { + const val = parse(value as string); + + if (value && !val.isIcann) { + return 'Please provide a valid domain'; + } + }, }, }} render={({ field }) => { diff --git a/apps/web/src/pages/auth/components/SignUpForm.tsx b/apps/web/src/pages/auth/components/SignUpForm.tsx index 5ae9274d156..ca81b0e2b56 100644 --- a/apps/web/src/pages/auth/components/SignUpForm.tsx +++ b/apps/web/src/pages/auth/components/SignUpForm.tsx @@ -3,7 +3,6 @@ import { Link, useNavigate } from 'react-router-dom'; import { useMutation } from '@tanstack/react-query'; import { useForm } from 'react-hook-form'; import { Center } from '@mantine/core'; -import { showNotification } from '@mantine/notifications'; import { passwordConstraints, UTM_CAMPAIGN_QUERY_PARAM } from '@novu/shared'; import type { IResponseError } from '@novu/shared'; import { PasswordInput, Button, colors, Input, Text, Checkbox } from '@novu/design-system'; @@ -13,7 +12,6 @@ import { api } from '../../../api/api.client'; import { applyToken, useVercelParams } from '../../../hooks'; import { useAcceptInvite } from './useAcceptInvite'; import { PasswordRequirementPopover } from './PasswordRequirementPopover'; -import { buildGithubLink, buildVercelGithubLink } from './gitHubUtils'; import { ROUTES } from '../../../constants/routes.enum'; import { OAuth } from './OAuth'; @@ -36,9 +34,6 @@ export function SignUpForm({ invitationToken, email }: SignUpFormProps) { const { isFromVercel, code, next, configurationId } = useVercelParams(); const vercelQueryParams = `code=${code}&next=${next}&configurationId=${configurationId}`; const loginLink = isFromVercel ? `/auth/login?${vercelQueryParams}` : ROUTES.AUTH_LOGIN; - const githubLink = isFromVercel - ? buildVercelGithubLink({ code, next, configurationId }) - : buildGithubLink({ invitationToken }); const { isLoading, mutateAsync, isError, error } = useMutation< { token: string }, @@ -52,21 +47,14 @@ export function SignUpForm({ invitationToken, email }: SignUpFormProps) { >((data) => api.post('/v1/auth/register', data)); const onSubmit = async (data) => { + const [firstName, lastName] = data?.fullName.trim().split(' '); const itemData = { - firstName: data.fullName.split(' ')[0], - lastName: data.fullName.split(' ')[1], + firstName, + lastName, email: data.email, password: data.password, }; - if (!itemData.lastName) { - showNotification({ - message: 'Please write your full name including last name', - color: 'red', - }); - - return; - } const response = await mutateAsync(itemData); /** @@ -80,10 +68,9 @@ export function SignUpForm({ invitationToken, email }: SignUpFormProps) { submitToken(token, invitationToken); return true; - } else { - setToken(token); } + setToken(token); navigate(isFromVercel ? `/auth/application?${vercelQueryParams}` : ROUTES.AUTH_APPLICATION); return true; diff --git a/apps/web/src/pages/auth/components/useDomainHook.ts b/apps/web/src/pages/auth/components/useDomainHook.ts new file mode 100644 index 00000000000..68deb7fb0d5 --- /dev/null +++ b/apps/web/src/pages/auth/components/useDomainHook.ts @@ -0,0 +1,62 @@ +import { useCallback, useEffect, useRef } from 'react'; + +interface DomainInfo { + domain: string; + domainWithoutSuffix: string; + hostname: string; + isIcann: boolean; + isIp: boolean; + isPrivate: boolean; + publicSuffix: string; + subdomain: string; +} + +function stripProtocol(url: string): string { + return (url || '').replace(/(https?)?(:\/+)?/, ''); +} + +function fallbackParser(url: string) { + const parts = stripProtocol(url).split('.'); + + if (parts.length > 2) { + const [subdomain, ...domainParts] = parts; + + return { + subdomain: subdomain || '', + domain: domainParts.join('.'), + }; + } + + return { + subdomain: '', + domain: url, + }; +} + +export function useDomainParser(): { parse: (url: string) => Partial } { + const tldParser = useRef(null); + + useEffect(() => { + import( + /* webpackIgnore: true */ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line import/extensions + 'https://unpkg.com/tldts/dist/es6/index.js?module' + ) + .then((mod) => (tldParser.current = mod)) + .catch(() => (tldParser.current = null)); + }, []); + + const parse = useCallback((url: string) => { + url = stripProtocol(url); + + if (tldParser.current) { + return (tldParser.current as any).parse(url, { allowPrivateDomains: true }) as DomainInfo; + } + + return fallbackParser(url); + }, []); + + return { parse }; +} diff --git a/apps/web/src/pages/templates/components/InputVariables.tsx b/apps/web/src/pages/templates/components/InputVariables.tsx index 45365e41f18..00bfc1bcc32 100644 --- a/apps/web/src/pages/templates/components/InputVariables.tsx +++ b/apps/web/src/pages/templates/components/InputVariables.tsx @@ -18,11 +18,11 @@ export const InputVariables = ({ } try { - const module = require('@novu/ee-billing-web'); + const module = require('@novu/ee-echo-web'); const InputVariablesComponent = module.InputVariables; return ; - } catch (e) {} - - return null; + } catch (e) { + throw e; + } }; diff --git a/apps/web/src/pages/templates/components/InputVariablesForm.tsx b/apps/web/src/pages/templates/components/InputVariablesForm.tsx index dff89da03d4..38a19e495a2 100644 --- a/apps/web/src/pages/templates/components/InputVariablesForm.tsx +++ b/apps/web/src/pages/templates/components/InputVariablesForm.tsx @@ -27,7 +27,7 @@ export const InputVariablesForm = ({ onChange }: { onChange?: (data: any) => voi } try { - const module = require('@novu/ee-billing-web'); + const module = require('@novu/ee-echo-web'); const InputVariablesComponent = module.InputVariablesForm; return ( @@ -40,7 +40,7 @@ export const InputVariablesForm = ({ onChange }: { onChange?: (data: any) => voi /> ); - } catch (e) {} - - return null; + } catch (e) { + throw e; + } }; diff --git a/apps/web/tests/activity-graph.spec.ts b/apps/web/tests/activity-graph.spec.ts new file mode 100644 index 00000000000..f8b784ce627 --- /dev/null +++ b/apps/web/tests/activity-graph.spec.ts @@ -0,0 +1,25 @@ +import { test, expect } from '@playwright/test'; + +import { getByTestId, initializeSession } from './utils.ts/browser'; +import { createNotifications } from './utils.ts/plugins'; + +let session; + +test.beforeEach(async ({ context }) => { + session = await initializeSession(context); + await createNotifications({ + identifier: session.templates[0].triggers[0].identifier, + token: session.token, + count: 25, + organizationId: session.organization._id, + environmentId: session.environment._id, + }); +}); + +test('should be able to add a new channel', async ({ page }) => { + await page.goto('/activities'); + await expect(page).toHaveURL(/\/activities/); + + const addChannelButton = getByTestId(page, 'activity-stats-weekly-sent'); + await expect(addChannelButton).toContainText('25'); +}); diff --git a/apps/web/tests/digest-playground.spec.ts b/apps/web/tests/digest-playground.spec.ts new file mode 100644 index 00000000000..3dce3206873 --- /dev/null +++ b/apps/web/tests/digest-playground.spec.ts @@ -0,0 +1,185 @@ +import { test, expect } from '@playwright/test'; + +import { getByTestId, initializeSession } from './utils.ts/browser'; + +let session; + +test.beforeEach(async ({ context }) => { + session = await initializeSession(context, { noTemplates: true }); +}); + +test('should have a link to the docs', async ({ page }) => { + await page.goto('/get-started'); + + const getStartedFooterLeftSide = getByTestId(page, 'get-started-footer-left-side'); + await getStartedFooterLeftSide.click(); + + const tryDigestPlaygroundBtn = getByTestId(page, 'try-digest-playground-btn'); + await tryDigestPlaygroundBtn.click(); + + await expect(page).toHaveURL(/\/digest-playground/); + await expect(page).toHaveTitle(/Digest Workflow Playground/); + + const learnMoreLink = page.locator('a[href^="https://docs.novu.co/workflows/digest"]'); + await expect(learnMoreLink).toHaveText('Learn more in docs'); +}); + +test('the set up digest workflow should redirect to template edit page', async ({ page }) => { + await page.goto('/get-started'); + + const getStartedFooterLeftSide = getByTestId(page, 'get-started-footer-left-side'); + await getStartedFooterLeftSide.click(); + + const tryDigestPlaygroundBtn = getByTestId(page, 'try-digest-playground-btn'); + await tryDigestPlaygroundBtn.click(); + + await expect(page).toHaveURL(/\/digest-playground/); + await expect(page).toHaveTitle(/Digest Workflow Playground/); + + const setupDigestWorkflowButton = page.getByRole('button', { name: 'Set Up Digest Workflow' }); + await setupDigestWorkflowButton.click(); + + await expect(page).toHaveURL(/\/workflows\/edit/); +}); + +test('should show the digest workflow hints', async ({ page }) => { + await page.goto('/get-started'); + + const getStartedFooterLeftSide = getByTestId(page, 'get-started-footer-left-side'); + await getStartedFooterLeftSide.click(); + + // click try digest playground + const tryDigestPlaygroundBtn = getByTestId(page, 'try-digest-playground-btn'); + await tryDigestPlaygroundBtn.click(); + + await expect(page).toHaveURL(/\/digest-playground/); + await expect(page).toHaveTitle(/Digest Workflow Playground/); + + // click set up digest workflow + const setupDigestWorkflowButton = page.getByRole('button', { name: 'Set Up Digest Workflow' }); + await setupDigestWorkflowButton.click(); + + // in the template workflow editor + await expect(page).toHaveURL(/\/workflows\/edit/); + + // check the digest hint + let digestWorkflowTooltip = getByTestId(page, 'digest-workflow-tooltip'); + await expect(digestWorkflowTooltip).toContainText('Set-up time interval'); + await expect(digestWorkflowTooltip).toContainText( + 'Specify for how long the digest should collect events before sending a digested event to the next step step in the workflow.' + ); + let primaryButton = page.getByRole('button', { name: 'Next' }); + await expect(primaryButton).toBeVisible(); + let skipTourButton = page.getByRole('button', { name: 'Skip tour' }); + await expect(skipTourButton).toBeVisible(); + let dotsNavigation = getByTestId(page, 'digest-workflow-tooltip-dots-navigation'); + await expect(dotsNavigation).toBeVisible(); + + // check if has digest step + const digestNode = getByTestId(page, 'node-digestSelector'); + await expect(digestNode).toBeVisible(); + // check if digest step settings opened + let digestSettings = getByTestId(page, 'step-editor-sidebar'); + await expect(digestSettings).toBeVisible(); + await expect(digestSettings).toContainText('All events'); + + // click next on hint + await primaryButton.click(); + + // check the email hint + digestWorkflowTooltip = getByTestId(page, 'digest-workflow-tooltip'); + await expect(digestWorkflowTooltip).toContainText('Set-up email content'); + await expect(digestWorkflowTooltip).toContainText( + 'Use custom HTML or our visual editor to define how the email will look like when sent to the subscriber.' + ); + primaryButton = page.getByRole('button', { name: 'Next' }); + await expect(primaryButton).toBeVisible(); + skipTourButton = page.getByRole('button', { name: 'Skip tour' }); + await expect(skipTourButton).toBeVisible(); + dotsNavigation = getByTestId(page, 'digest-workflow-tooltip-dots-navigation'); + await expect(dotsNavigation).toBeVisible(); + + // check if email step settings opened + digestSettings = getByTestId(page, 'step-editor-sidebar'); + await expect(digestSettings).toBeVisible(); + await expect(digestSettings).toContainText('Email'); + + // click next on hint + await primaryButton.click(); + + // check the email hint + digestWorkflowTooltip = getByTestId(page, 'digest-workflow-tooltip'); + await expect(digestWorkflowTooltip).toContainText('Test your workflow'); + await expect(digestWorkflowTooltip).toContainText( + 'We will trigger the workflow multiple times to represent how it aggregates notifications.' + ); + primaryButton = page.getByRole('button', { name: 'Got it' }); + await expect(primaryButton).toBeVisible(); + skipTourButton = page.getByRole('button', { name: 'Skip tour' }); + await expect(skipTourButton).not.toBeVisible(); + dotsNavigation = getByTestId(page, 'digest-workflow-tooltip-dots-navigation'); + await expect(dotsNavigation).toBeVisible(); + + // the step settings should be hidden + const workflowSidebar = getByTestId(page, 'workflow-sidebar'); + await expect(workflowSidebar).toBeVisible(); + await expect(workflowSidebar).toContainText('Trigger'); + + // click got it should hide the hint + await primaryButton.click(); + digestWorkflowTooltip = getByTestId(page, 'digest-workflow-tooltip'); + await expect(digestWorkflowTooltip).not.toBeVisible(); +}); + +test('should hide the digest workflow hints when clicking on skip tour button', async ({ page }) => { + await page.goto('/get-started'); + + const getStartedFooterLeftSide = getByTestId(page, 'get-started-footer-left-side'); + await getStartedFooterLeftSide.click(); + + // click try digest playground + const tryDigestPlaygroundBtn = getByTestId(page, 'try-digest-playground-btn'); + await tryDigestPlaygroundBtn.click(); + + await expect(page).toHaveURL(/\/digest-playground/); + await expect(page).toHaveTitle(/Digest Workflow Playground/); + + // click set up digest workflow + const setupDigestWorkflowButton = page.getByRole('button', { name: 'Set Up Digest Workflow' }); + await setupDigestWorkflowButton.click(); + + // in the template workflow editor + await expect(page).toHaveURL(/\/workflows\/edit/); + + // check the digest hint + let digestWorkflowTooltip = getByTestId(page, 'digest-workflow-tooltip'); + await expect(digestWorkflowTooltip).toContainText('Set-up time interval'); + const skipTourButton = page.getByRole('button', { name: 'Skip tour' }); + await skipTourButton.click(); + + digestWorkflowTooltip = getByTestId(page, 'digest-workflow-tooltip'); + await expect(digestWorkflowTooltip).not.toBeVisible(); +}); + +test('when clicking on the back button from the playground it should redirect to /get-started/preview', async ({ + page, +}) => { + await page.goto('/get-started'); + + const getStartedFooterLeftSide = getByTestId(page, 'get-started-footer-left-side'); + await getStartedFooterLeftSide.click(); + + // click try digest playground + const tryDigestPlaygroundBtn = getByTestId(page, 'try-digest-playground-btn'); + await tryDigestPlaygroundBtn.click(); + + await expect(page).toHaveURL(/\/digest-playground/); + await expect(page).toHaveTitle(/Digest Workflow Playground/); + + // click set up digest workflow + const goBack = page.getByRole('button', { name: 'Go Back' }); + await goBack.click(); + + // in the template workflow editor + await expect(page).toHaveURL(/\/get-started\/preview/); +}); diff --git a/apps/web/tests/integrations-list-modal.spec.ts b/apps/web/tests/integrations-list-modal.spec.ts new file mode 100644 index 00000000000..9db0e4e0d58 --- /dev/null +++ b/apps/web/tests/integrations-list-modal.spec.ts @@ -0,0 +1,831 @@ +import { + ChannelTypeEnum, + chatProviders, + EmailProviderIdEnum, + emailProviders, + InAppProviderIdEnum, + inAppProviders, + pushProviders, + SmsProviderIdEnum, + smsProviders, +} from '@novu/shared'; +import { test, expect } from '@playwright/test'; + +import { getByTestId, initializeSession, isDarkTheme } from './utils.ts/browser'; +import { + checkTableLoading, + checkTableRow, + clickOnListRow, + interceptIntegrationsRequest, + navigateToGetStarted, +} from './utils.ts/integrations'; +import { deleteProvider } from './utils.ts/plugins'; + +let session; + +test.beforeEach(async ({ context }) => { + session = await initializeSession(context); +}); + +test('should show the table loading skeleton and empty state', async ({ page }) => { + const integrationsPromise = interceptIntegrationsRequest({ + page, + modifyBody: () => ({ data: [] }), + }); + + await navigateToGetStarted(page, 'channel-card-sms'); + + const providerSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(providerSidebar).toBeVisible(); + + const sidebarClose = getByTestId(page, 'sidebar-close'); + await sidebarClose.click(); + + await checkTableLoading(page); + await integrationsPromise; + + const noIntegrationsPlaceholder = getByTestId(page, 'no-integrations-placeholder'); + await expect(noIntegrationsPlaceholder).toBeVisible(); + await expect(noIntegrationsPlaceholder).toContainText('Choose a channel you want to start sending notifications'); + + const inAppCard = getByTestId(page, 'integration-channel-card-in_app'); + await expect(inAppCard).toBeEnabled(); + await expect(inAppCard).toContainText('In-App'); + const emailCard = getByTestId(page, 'integration-channel-card-email'); + await expect(emailCard).toBeEnabled(); + await expect(emailCard).toContainText('Email'); + const chatCard = getByTestId(page, 'integration-channel-card-chat'); + await expect(chatCard).toBeEnabled(); + await expect(chatCard).toContainText('Chat'); + const pushCard = getByTestId(page, 'integration-channel-card-push'); + await expect(pushCard).toBeEnabled(); + await expect(pushCard).toContainText('Push'); + const smsCard = getByTestId(page, 'integration-channel-card-sms'); + await expect(smsCard).toBeEnabled(); + await expect(smsCard).toContainText('SMS'); +}); + +test('should show the table loading skeleton and then table', async ({ page }) => { + const integrationsPromise = interceptIntegrationsRequest({ + page, + }); + + await navigateToGetStarted(page, 'channel-card-sms'); + + const providerSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(providerSidebar).toBeVisible(); + + const sidebarClose = getByTestId(page, 'sidebar-close'); + await sidebarClose.click(); + + await checkTableLoading(page); + await integrationsPromise; + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await expect(addProvider).toContainText('Add a provider'); + + await checkTableRow(page, { + name: 'SendGrid', + provider: 'SendGrid', + channel: 'Email', + environment: 'Development', + status: 'Active', + }); + + await checkTableRow(page, { + name: 'Twilio', + provider: 'Twilio', + channel: 'SMS', + environment: 'Development', + status: 'Active', + }); + + await checkTableRow(page, { + name: 'Slack', + provider: 'Slack', + channel: 'Chat', + environment: 'Development', + status: 'Active', + }); + + await checkTableRow(page, { + name: 'Discord', + provider: 'Discord', + channel: 'Chat', + environment: 'Development', + status: 'Active', + }); + + await checkTableRow(page, { + name: 'Firebase Cloud Messaging', + provider: 'Firebase Cloud Messaging', + channel: 'Push', + environment: 'Development', + status: 'Active', + }); + + await checkTableRow(page, { + name: 'Novu In-App', + isFree: false, + provider: 'Novu In-App', + channel: 'In-App', + environment: 'Development', + status: 'Active', + }); +}); + +test('should show the select provider sidebar', async ({ page }) => { + await deleteProvider({ + providerId: InAppProviderIdEnum.Novu, + channel: ChannelTypeEnum.IN_APP, + environmentId: session.environment.id, + organizationId: session.organization.id, + }); + + await navigateToGetStarted(page); + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await addProvider.click(); + + const selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + await expect(selectProviderSidebar).toContainText('Select a provider'); + await expect(selectProviderSidebar).toContainText('Select a provider to create instance for a channel'); + const search = selectProviderSidebar.locator('input[type="search"]'); + await expect(search).toHaveAttribute('placeholder', 'Search a provider...'); + const sidebarClose = getByTestId(selectProviderSidebar, 'sidebar-close'); + await expect(sidebarClose).toBeVisible(); + + const channelTabs = selectProviderSidebar.locator('[role="tablist"]'); + const activeTab = channelTabs.locator('[data-active="true"]'); + await expect(activeTab).toContainText('Email'); + await expect(channelTabs).toContainText('In-App'); + await expect(channelTabs).toContainText('Email'); + await expect(channelTabs).toContainText('Chat'); + await expect(channelTabs).toContainText('Push'); + await expect(channelTabs).toContainText('SMS'); + + const inAppGroup = getByTestId(page, 'providers-group-in_app'); + await expect(inAppGroup).toContainText('In-App'); + const emailGroup = getByTestId(page, 'providers-group-email'); + await expect(emailGroup).toContainText('Email'); + const chatGroup = getByTestId(page, 'providers-group-chat'); + await expect(chatGroup).toContainText('Chat'); + const pushGroup = getByTestId(page, 'providers-group-push'); + await expect(pushGroup).toContainText('Push'); + const smsGroup = getByTestId(page, 'providers-group-sms'); + await expect(smsGroup).toContainText('SMS'); + + const allProviders = inAppProviders.concat(emailProviders, chatProviders, pushProviders, smsProviders); + for (const provider of allProviders) { + if (provider.id === EmailProviderIdEnum.Novu || provider.id === SmsProviderIdEnum.Novu) { + continue; + } + + const providerInGroup = getByTestId(selectProviderSidebar, `provider-${provider.id}`); + await expect(providerInGroup).toContainText(provider.displayName); + } + + const cancel = getByTestId(selectProviderSidebar, 'select-provider-sidebar-cancel'); + await expect(cancel).toContainText('Cancel'); + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await expect(next).toBeDisabled(); +}); + +test('should allow for searching', async ({ page }) => { + await navigateToGetStarted(page); + + const selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const search = selectProviderSidebar.locator('input[type="search"]'); + await search.fill('Mail'); + + const channelTabs = selectProviderSidebar.locator('[role="tablist"]'); + const inAppTab = channelTabs.locator('button', { hasText: 'In-App' }); + await expect(inAppTab).toBeHidden(); + const emailTab = channelTabs.locator('button', { hasText: 'Email' }); + await expect(emailTab).toBeVisible(); + const chatTab = channelTabs.locator('button', { hasText: 'Chat' }); + await expect(chatTab).toBeHidden(); + const pushTab = channelTabs.locator('button', { hasText: 'Push' }); + await expect(pushTab).toBeHidden(); + const smsTab = channelTabs.locator('button', { hasText: 'SMS' }); + await expect(smsTab).toBeHidden(); + + const emailGroup = getByTestId(page, 'providers-group-email'); + await expect(emailGroup).toContainText('Email'); + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + const mailgun = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailgun}`); + await expect(mailgun).toContainText('Mailgun'); + const mailerSend = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.MailerSend}`); + await expect(mailerSend).toContainText('MailerSend'); + const emailWebhook = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.EmailWebhook}`); + await expect(emailWebhook).toContainText('Email Webhook'); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await expect(next).toBeDisabled(); +}); + +test('should show empty search results', async ({ page }) => { + await navigateToGetStarted(page); + + const selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const search = selectProviderSidebar.locator('input[type="search"]'); + await search.fill('safasdfasdfasdfasdfas'); + + const channelTabs = selectProviderSidebar.locator('[role="tablist"]'); + const inAppTab = channelTabs.locator('button', { hasText: 'In-App' }); + await expect(inAppTab).toBeHidden(); + const emailTab = channelTabs.locator('button', { hasText: 'Email' }); + await expect(emailTab).toBeHidden(); + const chatTab = channelTabs.locator('button', { hasText: 'Chat' }); + await expect(chatTab).toBeHidden(); + const pushTab = channelTabs.locator('button', { hasText: 'Push' }); + await expect(pushTab).toBeHidden(); + const smsTab = channelTabs.locator('button', { hasText: 'SMS' }); + await expect(smsTab).toBeHidden(); + + const noSearchResults = getByTestId(page, 'select-provider-no-search-results-img'); + await expect(noSearchResults).toBeVisible(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await expect(next).toBeDisabled(); +}); + +test('should allow selecting a provider', async ({ page }) => { + await navigateToGetStarted(page); + + const selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const selectedProviderImage = getByTestId( + selectProviderSidebar, + `selected-provider-image-${EmailProviderIdEnum.Mailjet}` + ).first(); + const isDarkThemeEnabled = await isDarkTheme(page); + await expect(selectedProviderImage).toHaveAttribute( + 'src', + `/static/images/providers/${isDarkThemeEnabled ? 'dark' : 'light'}/square/${EmailProviderIdEnum.Mailjet}.svg` + ); + + const selectedProviderName = getByTestId(selectProviderSidebar, 'selected-provider-name'); + await expect(selectedProviderName).toContainText('Mailjet'); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await expect(next).toBeEnabled(); +}); + +test('should allow moving to create sidebar', async ({ page }) => { + await navigateToGetStarted(page); + + const selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + const createProviderInstanceSidebar = getByTestId(page, 'create-provider-instance-sidebar'); + await expect(createProviderInstanceSidebar).toBeVisible(); + await expect(createProviderInstanceSidebar).toContainText( + 'Specify assignment preferences to automatically allocate the provider instance to the Email channel.' + ); + await expect(createProviderInstanceSidebar).toContainText('Environment'); + await expect(createProviderInstanceSidebar).toContainText('Provider instance executes only for'); + + const sidebarClose = getByTestId(createProviderInstanceSidebar, 'sidebar-close'); + await expect(sidebarClose).toBeVisible(); + + const selectedProviderImage = getByTestId( + createProviderInstanceSidebar, + `selected-provider-image-${EmailProviderIdEnum.Mailjet}` + ).first(); + const isDarkThemeEnabled = await isDarkTheme(page); + await expect(selectedProviderImage).toHaveAttribute( + 'src', + `/static/images/providers/${isDarkThemeEnabled ? 'dark' : 'light'}/square/${EmailProviderIdEnum.Mailjet}.svg` + ); + + const selectedProviderName = getByTestId(createProviderInstanceSidebar, 'provider-instance-name'); + await expect(selectedProviderName).toBeVisible(); + await expect(selectedProviderName).toHaveValue('Mailjet'); + + const environmentRadios = createProviderInstanceSidebar.locator('[role="radiogroup"]'); + const selectedEnv = environmentRadios.locator('[data-checked="true"]'); + await expect(selectedEnv).toContainText('Development'); + await expect(environmentRadios).toContainText('Production'); + + const cancel = getByTestId(createProviderInstanceSidebar, 'create-provider-instance-sidebar-cancel'); + await expect(cancel).toContainText('Cancel'); + await expect(cancel).toBeEnabled(); + + const create = getByTestId(createProviderInstanceSidebar, 'create-provider-instance-sidebar-create'); + await expect(create).toContainText('Create'); + await expect(create).toBeEnabled(); +}); + +test('should allow moving back from create provider sidebar to select provider sidebar', async ({ page }) => { + await navigateToGetStarted(page); + + let selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + const back = getByTestId(page, 'create-provider-instance-sidebar-back'); + await expect(back).toBeVisible(); + await back.click(); + + selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); +}); + +test('should create a new mailjet integration', async ({ page }) => { + await navigateToGetStarted(page); + + let selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + const providerName = getByTestId(page, 'provider-instance-name'); + await providerName.clear(); + await providerName.fill('Mailjet Integration'); + + const create = getByTestId(page, 'create-provider-instance-sidebar-create'); + await expect(create).toContainText('Create'); + await expect(create).toBeEnabled(); + await create.click(); + + const updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + const close = getByTestId(updateProviderSidebar, 'sidebar-close'); + await expect(close).toBeVisible(); + await close.click(); + + await checkTableRow(page, { + name: 'Mailjet Integration', + provider: 'Mailjet', + channel: 'Email', + environment: 'Development', + status: 'Disabled', + }); +}); + +test('should update the mailjet integration', async ({ page }) => { + await navigateToGetStarted(page); + + let selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + let providerName = getByTestId(page, 'provider-instance-name'); + await providerName.clear(); + await providerName.fill('Mailjet Integration'); + + const create = getByTestId(page, 'create-provider-instance-sidebar-create'); + await expect(create).toContainText('Create'); + await expect(create).toBeEnabled(); + await create.click(); + + const updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + await expect(updateProviderSidebar).toContainText('Set up credentials to start sending notifications.'); + + const integrationChannel = getByTestId(updateProviderSidebar, 'provider-instance-channel'); + await expect(integrationChannel).toContainText('Email'); + + const integrationEnvironment = getByTestId(updateProviderSidebar, 'provider-instance-environment'); + await expect(integrationEnvironment).toContainText('Development'); + + const isActive = getByTestId(updateProviderSidebar, 'is_active_id'); + await expect(isActive).toHaveValue('false'); + + providerName = updateProviderSidebar.getByPlaceholder('Enter instance name'); + await expect(providerName).toHaveValue('Mailjet Integration'); + + const identifier = getByTestId(updateProviderSidebar, 'provider-instance-identifier'); + await expect(identifier).toHaveValue(/mailjet/); + + const updateButton = getByTestId(updateProviderSidebar, 'update-provider-sidebar-update'); + await expect(updateButton).toBeDisabled(); + + await providerName.clear(); + await providerName.fill('Mailjet Integration Updated'); + + await isActive.locator('~ label').click(); + + const apiKey = getByTestId(updateProviderSidebar, 'apiKey'); + await apiKey.fill('fake-api-key'); + + const secretKey = getByTestId(updateProviderSidebar, 'secretKey'); + await secretKey.fill('fake-secret-key'); + + const fromField = getByTestId(updateProviderSidebar, 'from'); + await fromField.fill('info@novu.co'); + + const senderName = getByTestId(updateProviderSidebar, 'senderName'); + await senderName.fill('Novu'); + + await expect(updateButton).toBeEnabled(); + await updateButton.click(); + + const modalClose = page.locator('.mantine-Modal-close'); + await modalClose.click(); + const sidebarClose = getByTestId(page, 'sidebar-close'); + await sidebarClose.click(); + + await checkTableRow(page, { + name: 'Mailjet Integration Updated', + provider: 'Mailjet', + channel: 'Email', + environment: 'Development', + status: 'Active', + }); +}); + +test('should update the mailjet integration from the list', async ({ page }) => { + await navigateToGetStarted(page); + + let selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + let providerName = getByTestId(page, 'provider-instance-name'); + await providerName.clear(); + await providerName.fill('Mailjet Integration'); + + const create = getByTestId(page, 'create-provider-instance-sidebar-create'); + await expect(create).toContainText('Create'); + await expect(create).toBeEnabled(); + await create.click(); + + let updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + let sidebarClose = getByTestId(page, 'sidebar-close'); + await sidebarClose.click(); + + await clickOnListRow(page, 'Mailjet Integration'); + + updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + const isActive = getByTestId(updateProviderSidebar, 'is_active_id'); + await expect(isActive).toHaveValue('false'); + + providerName = updateProviderSidebar.getByPlaceholder('Enter instance name'); + await expect(providerName).toHaveValue('Mailjet Integration'); + + const identifier = getByTestId(updateProviderSidebar, 'provider-instance-identifier'); + await expect(identifier).toHaveValue(/mailjet/); + + const updateButton = getByTestId(updateProviderSidebar, 'update-provider-sidebar-update'); + await expect(updateButton).toBeDisabled(); + + providerName = updateProviderSidebar.getByPlaceholder('Enter instance name'); + await providerName.clear(); + await providerName.fill('Mailjet Integration Updated'); + + await isActive.locator('~ label').click(); + + const apiKey = getByTestId(updateProviderSidebar, 'apiKey'); + await apiKey.fill('fake-api-key'); + + const secretKey = getByTestId(updateProviderSidebar, 'secretKey'); + await secretKey.fill('fake-secret-key'); + + const fromField = getByTestId(updateProviderSidebar, 'from'); + await fromField.fill('info@novu.co'); + + const senderName = getByTestId(updateProviderSidebar, 'senderName'); + await senderName.fill('Novu'); + + await expect(updateButton).toBeEnabled(); + await updateButton.click(); + + const modalClose = page.locator('.mantine-Modal-close'); + await modalClose.click(); + sidebarClose = getByTestId(page, 'sidebar-close'); + await sidebarClose.click(); + + await checkTableRow(page, { + name: 'Mailjet Integration Updated', + provider: 'Mailjet', + channel: 'Email', + environment: 'Development', + status: 'Active', + }); +}); + +test('should allow to delete the mailjet integration', async ({ page }) => { + await navigateToGetStarted(page); + + let selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + let providerName = getByTestId(page, 'provider-instance-name'); + await providerName.clear(); + await providerName.fill('Mailjet Integration'); + + const create = getByTestId(page, 'create-provider-instance-sidebar-create'); + await expect(create).toContainText('Create'); + await expect(create).toBeEnabled(); + await create.click(); + + let updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + let sidebarClose = getByTestId(page, 'sidebar-close'); + await sidebarClose.click(); + + await clickOnListRow(page, 'Mailjet Integration'); + + updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + const menu = updateProviderSidebar.locator('[aria-haspopup="menu"]'); + await menu.click(); + const deleteButton = updateProviderSidebar.locator('button[data-menu-item="true"]', { hasText: 'Delete' }); + await deleteButton.click(); + + const deleteModal = getByTestId(page, 'delete-provider-instance-modal'); + await expect(deleteModal).toBeVisible(); + await expect(deleteModal).toContainText('Delete Mailjet Integration instance?'); + await expect(deleteModal).toContainText( + 'Deleting a provider instance will fail workflows relying on its configuration, leading to undelivered notifications.' + ); + + const cancel = deleteModal.getByRole('button', { name: 'Cancel' }); + await expect(cancel).toBeEnabled(); + const deleteInstanceButton = deleteModal.getByRole('button', { name: 'Delete instance' }); + await expect(deleteInstanceButton).toBeEnabled(); + await deleteInstanceButton.click(); + + const integrationsTable = getByTestId(page, 'integration-name-cell', { hasText: 'Mailjet Integration' }); + await expect(integrationsTable).toBeHidden(); +}); + +test('should show the Novu in-app integration', async ({ page }) => { + await navigateToGetStarted(page); + + await clickOnListRow(page, new RegExp(`Novu In-App.*Development`)); + + const updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + await expect(updateProviderSidebar).toContainText( + 'Select a framework to set up credentials to start sending notifications.' + ); + + const sidebarClose = getByTestId(updateProviderSidebar, 'sidebar-close'); + await expect(sidebarClose).toBeVisible(); + + const integrationChannel = getByTestId(updateProviderSidebar, 'provider-instance-channel'); + await expect(integrationChannel).toContainText('In-App'); + + const integrationEnvironment = getByTestId(updateProviderSidebar, 'provider-instance-environment'); + await expect(integrationEnvironment).toContainText('Development'); + + const linkToDocs = updateProviderSidebar.getByRole('link', { name: 'Explore set-up guide' }); + await expect(linkToDocs).toBeVisible(); + + const isActive = getByTestId(updateProviderSidebar, 'is_active_id'); + await expect(isActive).toHaveValue('true'); + + const isDarkThemeEnabled = await isDarkTheme(page); + const selectedProviderImage = getByTestId( + updateProviderSidebar, + `selected-provider-image-${InAppProviderIdEnum.Novu}` + ); + await expect(selectedProviderImage).toHaveAttribute( + 'src', + `/static/images/providers/${isDarkThemeEnabled ? 'dark' : 'light'}/square/${InAppProviderIdEnum.Novu}.svg` + ); + + const selectedProviderName = getByTestId(updateProviderSidebar, 'provider-instance-name').first(); + await expect(selectedProviderName).toBeVisible(); + await expect(selectedProviderName).toHaveValue('Novu In-App'); + + const identifier = getByTestId(updateProviderSidebar, 'provider-instance-identifier'); + await expect(identifier).toHaveValue(/novu-in-app/); + + const hmacCheckbox = getByTestId(updateProviderSidebar, 'hmac'); + await expect(hmacCheckbox).not.toBeChecked(); + + const novuInAppFrameworks = getByTestId(updateProviderSidebar, 'novu-in-app-frameworks'); + await expect(novuInAppFrameworks).toContainText('Integrate In-App using a framework below'); + await expect(novuInAppFrameworks).toContainText('React'); + await expect(novuInAppFrameworks).toContainText('Angular'); + await expect(novuInAppFrameworks).toContainText('Web Component'); + await expect(novuInAppFrameworks).toContainText('Headless'); + await expect(novuInAppFrameworks).toContainText('Vue'); + await expect(novuInAppFrameworks).toContainText('iFrame'); + + const updateButton = getByTestId(updateProviderSidebar, 'update-provider-sidebar-update'); + await expect(updateButton).toContainText('Update'); + await expect(updateButton).toBeDisabled(); +}); + +test('should show the Novu in-app integration - React guide', async ({ page }) => { + await navigateToGetStarted(page); + + await clickOnListRow(page, new RegExp(`Novu In-App.*Development`)); + + let updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + const novuInAppFrameworks = getByTestId(updateProviderSidebar, 'novu-in-app-frameworks'); + await expect(novuInAppFrameworks).toContainText('React'); + + const reactGuide = novuInAppFrameworks.locator('div').filter({ hasText: 'React' }).nth(1); + await reactGuide.click(); + + updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toContainText('React integration guide'); + + const sidebarBack = getByTestId(updateProviderSidebar, 'sidebar-back'); + await expect(sidebarBack).toBeVisible(); + const setupTimeline = getByTestId(updateProviderSidebar, 'setup-timeline'); + await expect(setupTimeline).toBeVisible(); + + const updateButton = getByTestId(updateProviderSidebar, 'update-provider-sidebar-update'); + await expect(updateButton).toContainText('Update'); + await expect(updateButton).toBeDisabled(); +}); + +test('should show the Novu Email integration sidebar', async ({ page }) => { + const integrationsPromise = interceptIntegrationsRequest({ + page, + modifyBody: (body) => { + const [firstIntegration] = body.data; + body.data = [ + { + _id: EmailProviderIdEnum.Novu, + _environmentId: firstIntegration._environmentId, + providerId: EmailProviderIdEnum.Novu, + active: true, + channel: ChannelTypeEnum.EMAIL, + name: 'Novu Email', + identifier: EmailProviderIdEnum.Novu, + }, + ...body.data, + ]; + + return body; + }, + }); + + await navigateToGetStarted(page); + await integrationsPromise; + + const sidebarClose = getByTestId(page, 'sidebar-close'); + await sidebarClose.click(); + + await clickOnListRow(page, new RegExp(`Novu Email.*Development`)); + + let updateProviderSidebar = getByTestId(page, 'update-provider-sidebar-novu'); + await expect(updateProviderSidebar).toContainText('Test Provider'); + await expect(updateProviderSidebar).toBeVisible(); + + const isDarkThemeEnabled = await isDarkTheme(page); + const novuEmailLogo = updateProviderSidebar.locator( + `img[src="/static/images/providers/${isDarkThemeEnabled ? 'dark' : 'light'}/square/${ + EmailProviderIdEnum.Novu + }.svg"]` + ); + await expect(novuEmailLogo).toBeVisible(); + + const integrationChannel = getByTestId(updateProviderSidebar, 'provider-instance-channel'); + await expect(integrationChannel).toContainText('Email'); + + const integrationEnvironment = getByTestId(updateProviderSidebar, 'provider-instance-environment'); + await expect(integrationEnvironment).toContainText('Development'); + + const selectedProviderName = getByTestId(updateProviderSidebar, 'provider-instance-name').first(); + await expect(selectedProviderName).toBeVisible(); + await expect(selectedProviderName).toHaveValue('Novu Email'); + + const providerLimits = getByTestId(updateProviderSidebar, 'novu-provider-limits'); + const providerLimitsText = await providerLimits.innerText(); + await expect(providerLimitsText).toEqual( + 'Novu provider allows sending max 300 emails per month,\nto send more messages, configure a different provider' + ); + + const limitbarLimit = getByTestId(updateProviderSidebar, 'limitbar-limit'); + const limitbarText = await limitbarLimit.innerText(); + await expect(limitbarText).toEqual('300 emails per month'); +}); + +test('should show the Novu SMS integration sidebar', async ({ page }) => { + const integrationsPromise = interceptIntegrationsRequest({ + page, + modifyBody: (body) => { + const [firstIntegration] = body.data; + body.data = [ + { + _id: SmsProviderIdEnum.Novu, + _environmentId: firstIntegration._environmentId, + providerId: SmsProviderIdEnum.Novu, + active: true, + channel: ChannelTypeEnum.SMS, + name: 'Novu SMS', + identifier: SmsProviderIdEnum.Novu, + }, + ...body.data, + ]; + + return body; + }, + }); + + await navigateToGetStarted(page); + await integrationsPromise; + + const sidebarClose = getByTestId(page, 'sidebar-close'); + await sidebarClose.click(); + + await clickOnListRow(page, new RegExp(`Novu SMS.*Development`)); + + let updateProviderSidebar = getByTestId(page, 'update-provider-sidebar-novu'); + await expect(updateProviderSidebar).toContainText('Test Provider'); + await expect(updateProviderSidebar).toBeVisible(); + + const isDarkThemeEnabled = await isDarkTheme(page); + const novuEmailLogo = updateProviderSidebar.locator( + `img[src="/static/images/providers/${isDarkThemeEnabled ? 'dark' : 'light'}/square/${SmsProviderIdEnum.Novu}.svg"]` + ); + await expect(novuEmailLogo).toBeVisible(); + + const integrationChannel = getByTestId(updateProviderSidebar, 'provider-instance-channel'); + await expect(integrationChannel).toContainText('SMS'); + + const integrationEnvironment = getByTestId(updateProviderSidebar, 'provider-instance-environment'); + await expect(integrationEnvironment).toContainText('Development'); + + const selectedProviderName = getByTestId(updateProviderSidebar, 'provider-instance-name').first(); + await expect(selectedProviderName).toBeVisible(); + await expect(selectedProviderName).toHaveValue('Novu SMS'); + + const providerLimits = getByTestId(updateProviderSidebar, 'novu-provider-limits'); + const providerLimitsText = await providerLimits.innerText(); + await expect(providerLimitsText).toEqual( + 'Novu provider allows sending max 20 messages per month,\nto send more messages, configure a different provider' + ); + + const limitbarLimit = getByTestId(updateProviderSidebar, 'limitbar-limit'); + const limitbarText = await limitbarLimit.innerText(); + await expect(limitbarText).toEqual('20 messages per month'); +}); diff --git a/apps/web/tests/integrations-list-page.spec.ts b/apps/web/tests/integrations-list-page.spec.ts new file mode 100644 index 00000000000..88c1056c81f --- /dev/null +++ b/apps/web/tests/integrations-list-page.spec.ts @@ -0,0 +1,1105 @@ +import { + ChannelTypeEnum, + chatProviders, + EmailProviderIdEnum, + emailProviders, + InAppProviderIdEnum, + inAppProviders, + pushProviders, + SmsProviderIdEnum, + smsProviders, +} from '@novu/shared'; +import { test, expect } from '@playwright/test'; + +import { getByTestId, initializeSession, isDarkTheme } from './utils.ts/browser'; +import { + checkTableLoading, + checkTableRow, + clickOnListRow, + interceptIntegrationsRequest, +} from './utils.ts/integrations'; +import { deleteProvider } from './utils.ts/plugins'; + +let session; + +test.beforeEach(async ({ context }) => { + session = await initializeSession(context); +}); + +test('should show the table loading skeleton and empty state', async ({ page }) => { + const integrationsPromise = interceptIntegrationsRequest({ + page, + modifyBody: () => ({ data: [] }), + }); + + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + await checkTableLoading(page); + await integrationsPromise; + + const noIntegrationsPlaceholder = getByTestId(page, 'no-integrations-placeholder'); + await expect(noIntegrationsPlaceholder).toBeVisible(); + await expect(noIntegrationsPlaceholder).toContainText('Choose a channel you want to start sending notifications'); + + const inAppCard = getByTestId(page, 'integration-channel-card-in_app'); + await expect(inAppCard).toBeEnabled(); + await expect(inAppCard).toContainText('In-App'); + const emailCard = getByTestId(page, 'integration-channel-card-email'); + await expect(emailCard).toBeEnabled(); + await expect(emailCard).toContainText('Email'); + const chatCard = getByTestId(page, 'integration-channel-card-chat'); + await expect(chatCard).toBeEnabled(); + await expect(chatCard).toContainText('Chat'); + const pushCard = getByTestId(page, 'integration-channel-card-push'); + await expect(pushCard).toBeEnabled(); + await expect(pushCard).toContainText('Push'); + const smsCard = getByTestId(page, 'integration-channel-card-sms'); + await expect(smsCard).toBeEnabled(); + await expect(smsCard).toContainText('SMS'); +}); + +test('should show the table loading skeleton and then table', async ({ page }) => { + const integrationsPromise = interceptIntegrationsRequest({ + page, + }); + + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + await checkTableLoading(page); + await integrationsPromise; + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await expect(addProvider).toContainText('Add a provider'); + + await checkTableRow(page, { + name: 'SendGrid', + provider: 'SendGrid', + channel: 'Email', + environment: 'Development', + status: 'Active', + }); + + await checkTableRow(page, { + name: 'Twilio', + provider: 'Twilio', + channel: 'SMS', + environment: 'Development', + status: 'Active', + }); + + await checkTableRow(page, { + name: 'Slack', + provider: 'Slack', + channel: 'Chat', + environment: 'Development', + status: 'Active', + }); + + await checkTableRow(page, { + name: 'Discord', + provider: 'Discord', + channel: 'Chat', + environment: 'Development', + status: 'Active', + }); + + await checkTableRow(page, { + name: 'Firebase Cloud Messaging', + provider: 'Firebase Cloud Messaging', + channel: 'Push', + environment: 'Development', + status: 'Active', + }); + + await checkTableRow(page, { + name: 'Novu In-App', + isFree: false, + provider: 'Novu In-App', + channel: 'In-App', + environment: 'Development', + status: 'Active', + }); +}); + +test('should show the select provider sidebar', async ({ page }) => { + await deleteProvider({ + providerId: InAppProviderIdEnum.Novu, + channel: ChannelTypeEnum.IN_APP, + environmentId: session.environment.id, + organizationId: session.organization.id, + }); + + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await addProvider.click(); + + const selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + await expect(selectProviderSidebar).toContainText('Select a provider'); + await expect(selectProviderSidebar).toContainText('Select a provider to create instance for a channel'); + const search = selectProviderSidebar.locator('input[type="search"]'); + await expect(search).toHaveAttribute('placeholder', 'Search a provider...'); + const sidebarClose = getByTestId(selectProviderSidebar, 'sidebar-close'); + await expect(sidebarClose).toBeVisible(); + + const channelTabs = selectProviderSidebar.locator('[role="tablist"]'); + const activeTab = channelTabs.locator('[data-active="true"]'); + await expect(activeTab).toContainText('In-App'); + await expect(channelTabs).toContainText('In-App'); + await expect(channelTabs).toContainText('Email'); + await expect(channelTabs).toContainText('Chat'); + await expect(channelTabs).toContainText('Push'); + await expect(channelTabs).toContainText('SMS'); + + const inAppGroup = getByTestId(page, 'providers-group-in_app'); + await expect(inAppGroup).toContainText('In-App'); + const emailGroup = getByTestId(page, 'providers-group-email'); + await expect(emailGroup).toContainText('Email'); + const chatGroup = getByTestId(page, 'providers-group-chat'); + await expect(chatGroup).toContainText('Chat'); + const pushGroup = getByTestId(page, 'providers-group-push'); + await expect(pushGroup).toContainText('Push'); + const smsGroup = getByTestId(page, 'providers-group-sms'); + await expect(smsGroup).toContainText('SMS'); + + const allProviders = inAppProviders.concat(emailProviders, chatProviders, pushProviders, smsProviders); + for (const provider of allProviders) { + if (provider.id === EmailProviderIdEnum.Novu || provider.id === SmsProviderIdEnum.Novu) { + continue; + } + + const providerInGroup = getByTestId(selectProviderSidebar, `provider-${provider.id}`); + await expect(providerInGroup).toContainText(provider.displayName); + } + + const cancel = getByTestId(selectProviderSidebar, 'select-provider-sidebar-cancel'); + await expect(cancel).toContainText('Cancel'); + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await expect(next).toBeDisabled(); +}); + +test('should allow for searching', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await addProvider.click(); + + const selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const search = selectProviderSidebar.locator('input[type="search"]'); + await search.fill('Mail'); + + const channelTabs = selectProviderSidebar.locator('[role="tablist"]'); + const inAppTab = channelTabs.locator('button', { hasText: 'In-App' }); + await expect(inAppTab).toBeHidden(); + const emailTab = channelTabs.locator('button', { hasText: 'Email' }); + await expect(emailTab).toBeVisible(); + const chatTab = channelTabs.locator('button', { hasText: 'Chat' }); + await expect(chatTab).toBeHidden(); + const pushTab = channelTabs.locator('button', { hasText: 'Push' }); + await expect(pushTab).toBeHidden(); + const smsTab = channelTabs.locator('button', { hasText: 'SMS' }); + await expect(smsTab).toBeHidden(); + + const emailGroup = getByTestId(page, 'providers-group-email'); + await expect(emailGroup).toContainText('Email'); + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + const mailgun = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailgun}`); + await expect(mailgun).toContainText('Mailgun'); + const mailerSend = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.MailerSend}`); + await expect(mailerSend).toContainText('MailerSend'); + const emailWebhook = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.EmailWebhook}`); + await expect(emailWebhook).toContainText('Email Webhook'); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await expect(next).toBeDisabled(); +}); + +test('should show empty search results', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await addProvider.click(); + + const selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const search = selectProviderSidebar.locator('input[type="search"]'); + await search.fill('safasdfasdfasdfasdfas'); + + const channelTabs = selectProviderSidebar.locator('[role="tablist"]'); + const inAppTab = channelTabs.locator('button', { hasText: 'In-App' }); + await expect(inAppTab).toBeHidden(); + const emailTab = channelTabs.locator('button', { hasText: 'Email' }); + await expect(emailTab).toBeHidden(); + const chatTab = channelTabs.locator('button', { hasText: 'Chat' }); + await expect(chatTab).toBeHidden(); + const pushTab = channelTabs.locator('button', { hasText: 'Push' }); + await expect(pushTab).toBeHidden(); + const smsTab = channelTabs.locator('button', { hasText: 'SMS' }); + await expect(smsTab).toBeHidden(); + + const noSearchResults = getByTestId(page, 'select-provider-no-search-results-img'); + await expect(noSearchResults).toBeVisible(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await expect(next).toBeDisabled(); +}); + +test('should allow selecting a provider', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await addProvider.click(); + + const selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const selectedProviderImage = getByTestId( + selectProviderSidebar, + `selected-provider-image-${EmailProviderIdEnum.Mailjet}` + ).first(); + const isDarkThemeEnabled = await isDarkTheme(page); + await expect(selectedProviderImage).toHaveAttribute( + 'src', + `/static/images/providers/${isDarkThemeEnabled ? 'dark' : 'light'}/square/${EmailProviderIdEnum.Mailjet}.svg` + ); + + const selectedProviderName = getByTestId(selectProviderSidebar, 'selected-provider-name'); + await expect(selectedProviderName).toContainText('Mailjet'); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await expect(next).toBeEnabled(); +}); + +test('should allow moving to create sidebar', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await addProvider.click(); + + const selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + const createProviderInstanceSidebar = getByTestId(page, 'create-provider-instance-sidebar'); + await expect(createProviderInstanceSidebar).toBeVisible(); + await expect(createProviderInstanceSidebar).toContainText( + 'Specify assignment preferences to automatically allocate the provider instance to the Email channel.' + ); + await expect(createProviderInstanceSidebar).toContainText('Environment'); + await expect(createProviderInstanceSidebar).toContainText('Provider instance executes only for'); + + const sidebarClose = getByTestId(createProviderInstanceSidebar, 'sidebar-close'); + await expect(sidebarClose).toBeVisible(); + + const selectedProviderImage = getByTestId( + createProviderInstanceSidebar, + `selected-provider-image-${EmailProviderIdEnum.Mailjet}` + ).first(); + const isDarkThemeEnabled = await isDarkTheme(page); + await expect(selectedProviderImage).toHaveAttribute( + 'src', + `/static/images/providers/${isDarkThemeEnabled ? 'dark' : 'light'}/square/${EmailProviderIdEnum.Mailjet}.svg` + ); + + const selectedProviderName = getByTestId(createProviderInstanceSidebar, 'provider-instance-name'); + await expect(selectedProviderName).toBeVisible(); + await expect(selectedProviderName).toHaveValue('Mailjet'); + + const environmentRadios = createProviderInstanceSidebar.locator('[role="radiogroup"]'); + const selectedEnv = environmentRadios.locator('[data-checked="true"]'); + await expect(selectedEnv).toContainText('Development'); + await expect(environmentRadios).toContainText('Production'); + + const cancel = getByTestId(createProviderInstanceSidebar, 'create-provider-instance-sidebar-cancel'); + await expect(cancel).toContainText('Cancel'); + await expect(cancel).toBeEnabled(); + + const create = getByTestId(createProviderInstanceSidebar, 'create-provider-instance-sidebar-create'); + await expect(create).toContainText('Create'); + await expect(create).toBeEnabled(); +}); + +test('should allow moving back from create provider sidebar to select provider sidebar', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await addProvider.click(); + + let selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + const back = getByTestId(page, 'create-provider-instance-sidebar-back'); + await expect(back).toBeVisible(); + await back.click(); + + selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); +}); + +test('should create a new mailjet integration', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await addProvider.click(); + + let selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + const providerName = getByTestId(page, 'provider-instance-name'); + await providerName.clear(); + await providerName.fill('Mailjet Integration'); + + const create = getByTestId(page, 'create-provider-instance-sidebar-create'); + await expect(create).toContainText('Create'); + await expect(create).toBeEnabled(); + await create.click(); + + const updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + const close = getByTestId(updateProviderSidebar, 'sidebar-close'); + await expect(close).toBeVisible(); + await close.click(); + + await checkTableRow(page, { + name: 'Mailjet Integration', + provider: 'Mailjet', + channel: 'Email', + environment: 'Development', + status: 'Disabled', + }); +}); + +test('should create a new mailjet integration with conditions', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await addProvider.click(); + + let selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + const providerName = getByTestId(page, 'provider-instance-name'); + await providerName.clear(); + await providerName.fill('Mailjet Integration'); + + const addConditionsButton = getByTestId(page, 'add-conditions-btn'); + await addConditionsButton.click(); + + const conditionsTitle = getByTestId(page, 'conditions-form-title'); + await expect(conditionsTitle).toContainText('Conditions for Mailjet Integration provider instance'); + + const addConditionButton = getByTestId(page, 'add-new-condition'); + await addConditionButton.click(); + + const formOn = getByTestId(page, 'conditions-form-on'); + await expect(formOn).toHaveValue('Tenant'); + + const formKey = getByTestId(page, 'conditions-form-key'); + await formKey.fill('identifier'); + + const formOperator = getByTestId(page, 'conditions-form-operator'); + await expect(formOperator).toHaveValue('Equal'); + + const formValue = getByTestId(page, 'conditions-form-value'); + await formValue.fill('tenant123'); + + let applyButton = getByTestId(page, 'apply-conditions-btn'); + await applyButton.click(); + + await expect(addConditionsButton).toContainText('Edit conditions'); + + const create = getByTestId(page, 'create-provider-instance-sidebar-create'); + await expect(create).toContainText('Create'); + await expect(create).toBeEnabled(); + await create.click(); + + const updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + let headerAddConditions = getByTestId(updateProviderSidebar, 'header-add-conditions-btn'); + await expect(headerAddConditions).toContainText('1'); + await headerAddConditions.click(); + + const addNewCondition = getByTestId(page, 'add-new-condition'); + await addNewCondition.click(); + + const conditionsFormKey = getByTestId(page, 'conditions-form-key').last(); + await conditionsFormKey.fill('identifier'); + + const conditionsFormValue = getByTestId(page, 'conditions-form-value').last(); + await conditionsFormValue.fill('tenant456'); + + applyButton = getByTestId(page, 'apply-conditions-btn'); + await applyButton.click(); + + headerAddConditions = getByTestId(updateProviderSidebar, 'header-add-conditions-btn'); + await expect(headerAddConditions).toContainText('2'); + + await expect(page).toHaveURL(/\/integrations\//); + + const close = getByTestId(updateProviderSidebar, 'sidebar-close'); + await expect(close).toBeVisible(); + await close.click(); + + await checkTableRow(page, { + name: 'Mailjet Integration', + provider: 'Mailjet', + channel: 'Email', + environment: 'Development', + status: 'Disabled', + }); +}); + +test('should remove as primary when adding conditions', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + await clickOnListRow(page, new RegExp(`SendGrid.*Development`)); + + const headerAddConditions = getByTestId(page, 'header-add-conditions-btn'); + await headerAddConditions.click(); + + const removePrimaryFlagModal = getByTestId(page, 'remove-primary-flag-modal'); + await expect(removePrimaryFlagModal).toBeVisible(); + await expect(removePrimaryFlagModal).toContainText('Primary flag will be removed'); + await expect(removePrimaryFlagModal).toContainText( + 'Adding conditions to the primary provider instance removes its primary status when a user applies changes by' + ); + + const cancel = removePrimaryFlagModal.getByRole('button', { name: 'Cancel' }); + await expect(cancel).toBeVisible(); + + const gotIt = removePrimaryFlagModal.getByRole('button', { name: 'Got it' }); + await expect(gotIt).toBeVisible(); + await gotIt.click(); + + const conditionsTitle = getByTestId(page, 'conditions-form-title'); + await expect(conditionsTitle).toContainText('Conditions for SendGrid provider instance'); + + const addConditionButton = getByTestId(page, 'add-new-condition'); + await addConditionButton.click(); + + const formOn = getByTestId(page, 'conditions-form-on'); + await expect(formOn).toHaveValue('Tenant'); + + const formKey = getByTestId(page, 'conditions-form-key'); + await formKey.fill('identifier'); + + const formOperator = getByTestId(page, 'conditions-form-operator'); + await expect(formOperator).toHaveValue('Equal'); + + const formValue = getByTestId(page, 'conditions-form-value'); + await formValue.fill('tenant123'); + + let applyButton = getByTestId(page, 'apply-conditions-btn'); + await applyButton.click(); + + const providerName = page.getByPlaceholder('Enter instance name'); + await providerName.clear(); + await providerName.fill('SendGrid test'); + + const fromField = getByTestId(page, 'from'); + await fromField.fill('info@novu.co'); + + const senderName = getByTestId(page, 'senderName'); + await senderName.fill('Novu'); + + const updateButton = getByTestId(page, 'update-provider-sidebar-update'); + await expect(updateButton).toContainText('Update'); + await expect(updateButton).toBeEnabled(); + await updateButton.click(); + + const makePrimaryButton = page.locator('button', { hasText: 'Make primary' }); + await expect(makePrimaryButton).toBeVisible(); + + const closeButton = page.locator('.mantine-Modal-close'); + await closeButton.click(); +}); + +test('should remove conditions when set to primary', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await addProvider.click(); + + await expect(page).toHaveURL(/\/integrations\/create/); + + const selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + await expect(page).toHaveURL(/\/integrations\/create\/email\/mailjet/); + + const providerName = page.getByPlaceholder('Enter instance name'); + await providerName.clear(); + await providerName.fill('Mailjet Integration'); + + const addConditionsButton = getByTestId(page, 'add-conditions-btn'); + await addConditionsButton.click(); + + const conditionsTitle = getByTestId(page, 'conditions-form-title'); + await expect(conditionsTitle).toContainText('Conditions for Mailjet Integration provider instance'); + + const addNewCondition = getByTestId(page, 'add-new-condition'); + await addNewCondition.click(); + + const formOn = getByTestId(page, 'conditions-form-on'); + await expect(formOn).toHaveValue('Tenant'); + + const formKey = getByTestId(page, 'conditions-form-key'); + await formKey.fill('identifier'); + + const formOperator = getByTestId(page, 'conditions-form-operator'); + await expect(formOperator).toHaveValue('Equal'); + + const formValue = getByTestId(page, 'conditions-form-value'); + await formValue.fill('tenant123'); + + let applyButton = getByTestId(page, 'apply-conditions-btn'); + await applyButton.click(); + + const create = getByTestId(page, 'create-provider-instance-sidebar-create'); + await expect(create).toContainText('Create'); + await expect(create).toBeEnabled(); + await create.click(); + + const updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + const headerAddConditions = getByTestId(updateProviderSidebar, 'header-add-conditions-btn'); + await expect(headerAddConditions).toContainText('1'); + + let makePrimaryButton = getByTestId(updateProviderSidebar, 'header-make-primary-btn'); + await makePrimaryButton.click(); + + const removeConditionsModal = getByTestId(page, 'remove-conditions-modal'); + await expect(removeConditionsModal).toBeVisible(); + await expect(removeConditionsModal).toContainText('Conditions will be removed'); + await expect(removeConditionsModal).toContainText('Marking this instance as primary will remove all conditions'); + const cancel = removeConditionsModal.getByRole('button', { name: 'Cancel' }); + await expect(cancel).toBeVisible(); + const removeConditionsButton = removeConditionsModal.getByRole('button', { name: 'Remove conditions' }); + await expect(removeConditionsButton).toBeVisible(); + await removeConditionsButton.click(); + + makePrimaryButton = getByTestId(updateProviderSidebar, 'header-make-primary-btn'); + await expect(makePrimaryButton).toBeHidden(); + + const sidebarClose = getByTestId(page, 'sidebar-close'); + await sidebarClose.click(); + + await clickOnListRow(page, new RegExp(`Mailjet Integration.*Development`)); +}); + +test('should update the mailjet integration', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await addProvider.click(); + + let selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + let providerName = getByTestId(page, 'provider-instance-name'); + await providerName.clear(); + await providerName.fill('Mailjet Integration'); + + const create = getByTestId(page, 'create-provider-instance-sidebar-create'); + await expect(create).toContainText('Create'); + await expect(create).toBeEnabled(); + await create.click(); + + const updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + await expect(updateProviderSidebar).toContainText('Set up credentials to start sending notifications.'); + + const integrationChannel = getByTestId(updateProviderSidebar, 'provider-instance-channel'); + await expect(integrationChannel).toContainText('Email'); + + const integrationEnvironment = getByTestId(updateProviderSidebar, 'provider-instance-environment'); + await expect(integrationEnvironment).toContainText('Development'); + + const isActive = getByTestId(updateProviderSidebar, 'is_active_id'); + await expect(isActive).toHaveValue('false'); + + providerName = updateProviderSidebar.getByPlaceholder('Enter instance name'); + await expect(providerName).toHaveValue('Mailjet Integration'); + + const identifier = getByTestId(updateProviderSidebar, 'provider-instance-identifier'); + await expect(identifier).toHaveValue(/mailjet/); + + const updateButton = getByTestId(updateProviderSidebar, 'update-provider-sidebar-update'); + await expect(updateButton).toBeDisabled(); + + await providerName.clear(); + await providerName.fill('Mailjet Integration Updated'); + + await isActive.locator('~ label').click(); + + const apiKey = getByTestId(updateProviderSidebar, 'apiKey'); + await apiKey.fill('fake-api-key'); + + const secretKey = getByTestId(updateProviderSidebar, 'secretKey'); + await secretKey.fill('fake-secret-key'); + + const fromField = getByTestId(updateProviderSidebar, 'from'); + await fromField.fill('info@novu.co'); + + const senderName = getByTestId(updateProviderSidebar, 'senderName'); + await senderName.fill('Novu'); + + await expect(updateButton).toBeEnabled(); + await updateButton.click(); + + const modalClose = page.locator('.mantine-Modal-close'); + await modalClose.click(); + const sidebarClose = getByTestId(page, 'sidebar-close'); + await sidebarClose.click(); + + await checkTableRow(page, { + name: 'Mailjet Integration Updated', + provider: 'Mailjet', + channel: 'Email', + environment: 'Development', + status: 'Active', + }); +}); + +test('should update the mailjet integration from the list', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await addProvider.click(); + + let selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + let providerName = getByTestId(page, 'provider-instance-name'); + await providerName.clear(); + await providerName.fill('Mailjet Integration'); + + const create = getByTestId(page, 'create-provider-instance-sidebar-create'); + await expect(create).toContainText('Create'); + await expect(create).toBeEnabled(); + await create.click(); + + let updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + let sidebarClose = getByTestId(page, 'sidebar-close'); + await sidebarClose.click(); + + await clickOnListRow(page, 'Mailjet Integration'); + + updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + const isActive = getByTestId(updateProviderSidebar, 'is_active_id'); + await expect(isActive).toHaveValue('false'); + + providerName = updateProviderSidebar.getByPlaceholder('Enter instance name'); + await expect(providerName).toHaveValue('Mailjet Integration'); + + const identifier = getByTestId(updateProviderSidebar, 'provider-instance-identifier'); + await expect(identifier).toHaveValue(/mailjet/); + + const updateButton = getByTestId(updateProviderSidebar, 'update-provider-sidebar-update'); + await expect(updateButton).toBeDisabled(); + + providerName = updateProviderSidebar.getByPlaceholder('Enter instance name'); + await providerName.clear(); + await providerName.fill('Mailjet Integration Updated'); + + await isActive.locator('~ label').click(); + + const apiKey = getByTestId(updateProviderSidebar, 'apiKey'); + await apiKey.fill('fake-api-key'); + + const secretKey = getByTestId(updateProviderSidebar, 'secretKey'); + await secretKey.fill('fake-secret-key'); + + const fromField = getByTestId(updateProviderSidebar, 'from'); + await fromField.fill('info@novu.co'); + + const senderName = getByTestId(updateProviderSidebar, 'senderName'); + await senderName.fill('Novu'); + + await expect(updateButton).toBeEnabled(); + await updateButton.click(); + + const modalClose = page.locator('.mantine-Modal-close'); + await modalClose.click(); + sidebarClose = getByTestId(page, 'sidebar-close'); + await sidebarClose.click(); + + await checkTableRow(page, { + name: 'Mailjet Integration Updated', + provider: 'Mailjet', + channel: 'Email', + environment: 'Development', + status: 'Active', + }); +}); + +test('should allow to delete the mailjet integration', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + const addProvider = getByTestId(page, 'add-provider'); + await expect(addProvider).toBeEnabled(); + await addProvider.click(); + + let selectProviderSidebar = getByTestId(page, 'select-provider-sidebar'); + await expect(selectProviderSidebar).toBeVisible(); + + const mailjet = getByTestId(selectProviderSidebar, `provider-${EmailProviderIdEnum.Mailjet}`); + await expect(mailjet).toContainText('Mailjet'); + await mailjet.click(); + + const next = getByTestId(selectProviderSidebar, 'select-provider-sidebar-next'); + await expect(next).toContainText('Next'); + await next.click(); + + let providerName = getByTestId(page, 'provider-instance-name'); + await providerName.clear(); + await providerName.fill('Mailjet Integration'); + + const create = getByTestId(page, 'create-provider-instance-sidebar-create'); + await expect(create).toContainText('Create'); + await expect(create).toBeEnabled(); + await create.click(); + + let updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + let sidebarClose = getByTestId(page, 'sidebar-close'); + await sidebarClose.click(); + + await clickOnListRow(page, 'Mailjet Integration'); + + updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + const menu = updateProviderSidebar.locator('[aria-haspopup="menu"]'); + await menu.click(); + const deleteButton = updateProviderSidebar.locator('button[data-menu-item="true"]', { hasText: 'Delete' }); + await deleteButton.click(); + + const deleteModal = getByTestId(page, 'delete-provider-instance-modal'); + await expect(deleteModal).toBeVisible(); + await expect(deleteModal).toContainText('Delete Mailjet Integration instance?'); + await expect(deleteModal).toContainText( + 'Deleting a provider instance will fail workflows relying on its configuration, leading to undelivered notifications.' + ); + + const cancel = deleteModal.getByRole('button', { name: 'Cancel' }); + await expect(cancel).toBeEnabled(); + const deleteInstanceButton = deleteModal.getByRole('button', { name: 'Delete instance' }); + await expect(deleteInstanceButton).toBeEnabled(); + await deleteInstanceButton.click(); + + const integrationsTable = getByTestId(page, 'integration-name-cell', { hasText: 'Mailjet Integration' }); + await expect(integrationsTable).toBeHidden(); +}); + +test('should show the Novu in-app integration', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + await clickOnListRow(page, new RegExp(`Novu In-App.*Development`)); + + const updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + await expect(updateProviderSidebar).toContainText( + 'Select a framework to set up credentials to start sending notifications.' + ); + + const sidebarClose = getByTestId(updateProviderSidebar, 'sidebar-close'); + await expect(sidebarClose).toBeVisible(); + + const integrationChannel = getByTestId(updateProviderSidebar, 'provider-instance-channel'); + await expect(integrationChannel).toContainText('In-App'); + + const integrationEnvironment = getByTestId(updateProviderSidebar, 'provider-instance-environment'); + await expect(integrationEnvironment).toContainText('Development'); + + const linkToDocs = updateProviderSidebar.getByRole('link', { name: 'Explore set-up guide' }); + await expect(linkToDocs).toBeVisible(); + + const isActive = getByTestId(updateProviderSidebar, 'is_active_id'); + await expect(isActive).toHaveValue('true'); + + const isDarkThemeEnabled = await isDarkTheme(page); + const selectedProviderImage = getByTestId( + updateProviderSidebar, + `selected-provider-image-${InAppProviderIdEnum.Novu}` + ); + await expect(selectedProviderImage).toHaveAttribute( + 'src', + `/static/images/providers/${isDarkThemeEnabled ? 'dark' : 'light'}/square/${InAppProviderIdEnum.Novu}.svg` + ); + + const selectedProviderName = getByTestId(updateProviderSidebar, 'provider-instance-name').first(); + await expect(selectedProviderName).toBeVisible(); + await expect(selectedProviderName).toHaveValue('Novu In-App'); + + const identifier = getByTestId(updateProviderSidebar, 'provider-instance-identifier'); + await expect(identifier).toHaveValue(/novu-in-app/); + + const hmacCheckbox = getByTestId(updateProviderSidebar, 'hmac'); + await expect(hmacCheckbox).not.toBeChecked(); + + const novuInAppFrameworks = getByTestId(updateProviderSidebar, 'novu-in-app-frameworks'); + await expect(novuInAppFrameworks).toContainText('Integrate In-App using a framework below'); + await expect(novuInAppFrameworks).toContainText('React'); + await expect(novuInAppFrameworks).toContainText('Angular'); + await expect(novuInAppFrameworks).toContainText('Web Component'); + await expect(novuInAppFrameworks).toContainText('Headless'); + await expect(novuInAppFrameworks).toContainText('Vue'); + await expect(novuInAppFrameworks).toContainText('iFrame'); + + const updateButton = getByTestId(updateProviderSidebar, 'update-provider-sidebar-update'); + await expect(updateButton).toContainText('Update'); + await expect(updateButton).toBeDisabled(); +}); + +test('should show the Novu in-app integration - React guide', async ({ page }) => { + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + + await clickOnListRow(page, new RegExp(`Novu In-App.*Development`)); + + let updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toBeVisible(); + + const novuInAppFrameworks = getByTestId(updateProviderSidebar, 'novu-in-app-frameworks'); + await expect(novuInAppFrameworks).toContainText('React'); + + const reactGuide = novuInAppFrameworks.locator('div').filter({ hasText: 'React' }).nth(1); + await reactGuide.click(); + + updateProviderSidebar = getByTestId(page, 'update-provider-sidebar'); + await expect(updateProviderSidebar).toContainText('React integration guide'); + + const sidebarBack = getByTestId(updateProviderSidebar, 'sidebar-back'); + await expect(sidebarBack).toBeVisible(); + const setupTimeline = getByTestId(updateProviderSidebar, 'setup-timeline'); + await expect(setupTimeline).toBeVisible(); + + const updateButton = getByTestId(updateProviderSidebar, 'update-provider-sidebar-update'); + await expect(updateButton).toContainText('Update'); + await expect(updateButton).toBeDisabled(); +}); + +test('should show the Novu Email integration sidebar', async ({ page }) => { + const integrationsPromise = interceptIntegrationsRequest({ + page, + modifyBody: (body) => { + const [firstIntegration] = body.data; + body.data = [ + { + _id: EmailProviderIdEnum.Novu, + _environmentId: firstIntegration._environmentId, + providerId: EmailProviderIdEnum.Novu, + active: true, + channel: ChannelTypeEnum.EMAIL, + name: 'Novu Email', + identifier: EmailProviderIdEnum.Novu, + }, + ...body.data, + ]; + + return body; + }, + }); + + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + await integrationsPromise; + + await clickOnListRow(page, new RegExp(`Novu Email.*Development`)); + + let updateProviderSidebar = getByTestId(page, 'update-provider-sidebar-novu'); + await expect(updateProviderSidebar).toContainText('Test Provider'); + await expect(updateProviderSidebar).toBeVisible(); + + const isDarkThemeEnabled = await isDarkTheme(page); + const novuEmailLogo = updateProviderSidebar.locator( + `img[src="/static/images/providers/${isDarkThemeEnabled ? 'dark' : 'light'}/square/${ + EmailProviderIdEnum.Novu + }.svg"]` + ); + await expect(novuEmailLogo).toBeVisible(); + + const integrationChannel = getByTestId(updateProviderSidebar, 'provider-instance-channel'); + await expect(integrationChannel).toContainText('Email'); + + const integrationEnvironment = getByTestId(updateProviderSidebar, 'provider-instance-environment'); + await expect(integrationEnvironment).toContainText('Development'); + + const selectedProviderName = getByTestId(updateProviderSidebar, 'provider-instance-name').first(); + await expect(selectedProviderName).toBeVisible(); + await expect(selectedProviderName).toHaveValue('Novu Email'); + + const providerLimits = getByTestId(updateProviderSidebar, 'novu-provider-limits'); + const providerLimitsText = await providerLimits.innerText(); + await expect(providerLimitsText).toEqual( + 'Novu provider allows sending max 300 emails per month,\nto send more messages, configure a different provider' + ); + + const limitbarLimit = getByTestId(updateProviderSidebar, 'limitbar-limit'); + const limitbarText = await limitbarLimit.innerText(); + await expect(limitbarText).toEqual('300 emails per month'); +}); + +test('should show the Novu SMS integration sidebar', async ({ page }) => { + const integrationsPromise = interceptIntegrationsRequest({ + page, + modifyBody: (body) => { + const [firstIntegration] = body.data; + body.data = [ + { + _id: SmsProviderIdEnum.Novu, + _environmentId: firstIntegration._environmentId, + providerId: SmsProviderIdEnum.Novu, + active: true, + channel: ChannelTypeEnum.SMS, + name: 'Novu SMS', + identifier: SmsProviderIdEnum.Novu, + }, + ...body.data, + ]; + + return body; + }, + }); + + await page.goto('/integrations'); + await expect(page).toHaveURL(/\/integrations/); + await integrationsPromise; + + await clickOnListRow(page, new RegExp(`Novu SMS.*Development`)); + + let updateProviderSidebar = getByTestId(page, 'update-provider-sidebar-novu'); + await expect(updateProviderSidebar).toContainText('Test Provider'); + await expect(updateProviderSidebar).toBeVisible(); + + const isDarkThemeEnabled = await isDarkTheme(page); + const novuEmailLogo = updateProviderSidebar.locator( + `img[src="/static/images/providers/${isDarkThemeEnabled ? 'dark' : 'light'}/square/${SmsProviderIdEnum.Novu}.svg"]` + ); + await expect(novuEmailLogo).toBeVisible(); + + const integrationChannel = getByTestId(updateProviderSidebar, 'provider-instance-channel'); + await expect(integrationChannel).toContainText('SMS'); + + const integrationEnvironment = getByTestId(updateProviderSidebar, 'provider-instance-environment'); + await expect(integrationEnvironment).toContainText('Development'); + + const selectedProviderName = getByTestId(updateProviderSidebar, 'provider-instance-name').first(); + await expect(selectedProviderName).toBeVisible(); + await expect(selectedProviderName).toHaveValue('Novu SMS'); + + const providerLimits = getByTestId(updateProviderSidebar, 'novu-provider-limits'); + const providerLimitsText = await providerLimits.innerText(); + await expect(providerLimitsText).toEqual( + 'Novu provider allows sending max 20 messages per month,\nto send more messages, configure a different provider' + ); + + const limitbarLimit = getByTestId(updateProviderSidebar, 'limitbar-limit'); + const limitbarText = await limitbarLimit.innerText(); + await expect(limitbarText).toEqual('20 messages per month'); +}); diff --git a/apps/web/tests/main-functionality.spec.ts b/apps/web/tests/main-functionality.spec.ts new file mode 100644 index 00000000000..0ccbf787aa6 --- /dev/null +++ b/apps/web/tests/main-functionality.spec.ts @@ -0,0 +1,577 @@ +import { test, expect } from '@playwright/test'; +import os from 'node:os'; + +import { dragAndDrop, getByTestId, initializeSession } from './utils.ts/browser'; +import { + addAndEditChannel, + editChannel, + fillBasicNotificationDetails, + goBack, + updateWorkflowButtonClick, +} from './utils.ts/workflow-editor'; + +let session; + +const isMac = os.platform() === 'darwin'; +const modifier = isMac ? 'Meta' : 'Control'; + +test.beforeEach(async ({ context }) => { + session = await initializeSession(context); +}); + +test('should not reset data when switching channel types', async ({ page }) => { + await page.goto('/workflows/create'); + + await fillBasicNotificationDetails(page); + await goBack(page); + await addAndEditChannel(page, 'inApp'); + + const editorParent = page.locator('.monaco-editor textarea').locator('xpath=..'); + await editorParent.click(); + await editorParent.locator('textarea').fill('{{firstName}} someone assigned you to {{taskName}}'); + + await goBack(page); + await addAndEditChannel(page, 'email'); + + const subjectEl = getByTestId(page, 'emailSubject'); + await subjectEl.fill('this is email subject'); + + const preheaderEl = getByTestId(page, 'emailPreheader'); + await preheaderEl.fill('this is email preheader'); + + const editableText = getByTestId(page, 'editable-text-content'); + await editableText.clear(); + await editableText.pressSequentially('This text is written from a test {{firstName}}'); + + await goBack(page); + + await editChannel(page, 'inApp'); + await goBack(page); + + await editChannel(page, 'email'); + await expect(getByTestId(page, 'emailSubject')).toHaveValue('this is email subject'); + await expect(getByTestId(page, 'emailPreheader')).toHaveValue('this is email preheader'); + await expect(getByTestId(page, 'editable-text-content')).toContainText('This text is written from a test'); +}); + +test('should update to empty data when switching from editor to customHtml', async ({ page }) => { + await page.goto('/workflows/create'); + + await fillBasicNotificationDetails(page, { title: 'Test Notification' }); + await goBack(page); + await addAndEditChannel(page, 'email'); + + const editableText = getByTestId(page, 'editable-text-content'); + await editableText.clear(); + await editableText.pressSequentially('This text is written from a test {{firstName}}'); + + let subjectEl = getByTestId(page, 'emailSubject'); + await subjectEl.fill('this is email subject'); + + await updateWorkflowButtonClick(page); + + await page + .locator('[data-test-id="editor-type-selector"] .mantine-Tabs-tabsList') + .getByText(/Custom Code/) + .first() + .click(); + + subjectEl = getByTestId(page, 'emailSubject'); + await subjectEl.clear(); + await subjectEl.fill('new email subject'); + + await updateWorkflowButtonClick(page, { noWaitAfter: true }); + + const templatesLinkPage = getByTestId(page, 'side-nav-templates-link'); + await templatesLinkPage.click(); + + const notificationsTemplate = getByTestId(page, 'notifications-template'); + await notificationsTemplate.getByText(/Test Notification/).click(); + + await editChannel(page, 'email'); + + await expect( + page + .locator('[data-test-id="editor-type-selector"] .mantine-Tabs-tabsList') + .locator('[data-active="true"]') + .getByText(/Custom Code/) + .first() + ).toBeVisible(); +}); + +test('should save avatar enabled and content for in app', async ({ page }) => { + await page.goto('/workflows/create'); + + await fillBasicNotificationDetails(page, { title: 'Test save avatar' }); + await goBack(page); + await addAndEditChannel(page, 'inApp'); + + const editorParent = page.locator('.monaco-editor textarea').locator('xpath=..'); + await editorParent.click(); + await editorParent.locator('textarea').fill('new content for notification'); + + const enableAddAvatar = getByTestId(page, 'enable-add-avatar'); + await enableAddAvatar.click(); + const chooseAvatar = getByTestId(page, 'choose-avatar-btn'); + await chooseAvatar.click(); + const avatarIconInfo = getByTestId(page, 'avatar-icon-info'); + await avatarIconInfo.click(); + + await updateWorkflowButtonClick(page); + + await expect(getByTestId(page, 'enabled-avatar')).toBeChecked(); + await expect(getByTestId(page, 'avatar-icon-info')).toBeVisible(); +}); + +test('should edit in-app notification', async ({ page }) => { + const template = session.templates[0]; + await page.goto(`/workflows/edit/${template._id}`); + + const settingsPage = getByTestId(page, 'settings-page'); + await settingsPage.click(); + + let nameInput = getByTestId(page, 'name-input'); + await expect(nameInput.first()).toHaveValue(template.name); + + await goBack(page); + await editChannel(page, 'inApp'); + + let editorParent = page.locator('.monaco-editor textarea').locator('xpath=..'); + await editorParent.click(); + await editorParent.locator('textarea').fill('Test content for {{firstName}}'); + + await goBack(page); + + nameInput = getByTestId(page, 'name-input'); + await nameInput.clear(); + await nameInput.fill('This is the new notification title'); + + await editChannel(page, 'inApp'); + const feedButton = getByTestId(page, 'feed-button-1'); + await feedButton.click(); + + const monacoEditor = page.locator('.monaco-editor').nth(0); + await monacoEditor.click(); + await monacoEditor.press(`${modifier}+KeyX`); + await page.keyboard.type('new content for notification'); + + await goBack(page); + + await updateWorkflowButtonClick(page); + + await page.goto(`/workflows`); + const notificationsTemplate = getByTestId(page, 'notifications-template'); + await expect(await notificationsTemplate.getByText(/This is the new notification title/)).toBeVisible(); + + await page.goto(`/workflows/edit/${template._id}`); + await editChannel(page, 'inApp'); + + await expect(getByTestId(page, 'feed-button-0')).toBeVisible(); + await expect(getByTestId(page, 'feed-button-1-checked')).toBeVisible(); + const createFeedInput = getByTestId(page, 'create-feed-input'); + await createFeedInput.fill('test4'); + + const addFeedButton = getByTestId(page, 'add-feed-button'); + await addFeedButton.click(); + await expect(getByTestId(page, 'feed-button-2-checked')).toBeVisible(); +}); + +test('should unset feedId for in app step', async ({ page }) => { + const template = session.templates[0]; + + await page.goto(`/workflows/edit/${template._id}`); + await editChannel(page, 'inApp'); + + let feedsCheckbox = getByTestId(page, 'use-feeds-checkbox'); + await expect(feedsCheckbox).toBeChecked(); + await feedsCheckbox.click(); + + await updateWorkflowButtonClick(page); + + await page.goto(`/workflows`); + + const notificationsTemplate = getByTestId(page, 'notifications-template'); + await expect(notificationsTemplate.getByText(template.name, { exact: false })).toBeVisible(); + + await page.goto(`/workflows/edit/${template._id}`); + await editChannel(page, 'inApp'); + + feedsCheckbox = getByTestId(page, 'use-feeds-checkbox'); + await expect(feedsCheckbox).not.toBeChecked(); +}); + +test('should edit email notification', async ({ page }) => { + const template = session.templates[0]; + + await page.goto(`/workflows/edit/${template._id}`); + await editChannel(page, 'email'); + + const emailEditor = getByTestId(page, 'email-editor'); + const firstEditorRow = getByTestId(emailEditor, 'editor-row').first(); + await firstEditorRow.click(); + await firstEditorRow.press(`${modifier}+KeyA`); + await firstEditorRow.press(`${modifier}+KeyX`); + await page.keyboard.type('Hello world!'); +}); + +test('should update notification active status', async ({ page }) => { + const template = session.templates[0]; + + await page.goto(`/workflows/edit/${template._id}`); + + let settingsPage = getByTestId(page, 'settings-page'); + await settingsPage.click(); + + const toggleSwitch = getByTestId(page, 'active-toggle-switch'); + await expect(toggleSwitch).toBeVisible(); + await expect(page.getByText('Active')).toBeVisible(); + await toggleSwitch.locator('~ label').click({ force: true }); + await expect(page.getByText('Inactive')).toBeVisible(); + + await page.goto(`/workflows/edit/${template._id}`); + + settingsPage = getByTestId(page, 'settings-page'); + await settingsPage.click(); + + await expect(page.getByText('Inactive')).toBeVisible(); +}); + +test('should toggle active states of channels', async ({ page }) => { + await page.goto(`/workflows/create`); + + await fillBasicNotificationDetails(page, { title: 'Test toggle active states of channels' }); + await goBack(page); + + await addAndEditChannel(page, 'email'); + + let stepActiveSwitch = getByTestId(page, 'step-active-switch'); + await expect(stepActiveSwitch).toHaveValue('on'); + + await stepActiveSwitch.locator('~ label').click({ force: true }); + await stepActiveSwitch.locator('~ label').click({ force: true }); + + await goBack(page); + + await addAndEditChannel(page, 'inApp'); + stepActiveSwitch = getByTestId(page, 'step-active-switch'); + await expect(stepActiveSwitch).toHaveValue('on'); +}); + +test('should show trigger snippet block when editing', async ({ page }) => { + const template = session.templates[0]; + await page.goto(`/workflows/edit/${template._id}`); + + const getSnippetButton = getByTestId(page, 'get-snippet-btn'); + await getSnippetButton.click(); + + const triggerCodeSnippet = getByTestId(page, 'trigger-code-snippet'); + await expect(triggerCodeSnippet).toContainText('test-event'); +}); + +test('should show error on node if message field is missing', async ({ page }) => { + await page.goto(`/workflows/create`); + + await fillBasicNotificationDetails(page); + await goBack(page); + + await dragAndDrop(page, `dnd-emailSelector`, 'addNodeButton'); + let emailNode = getByTestId(page, 'node-emailSelector'); + let errorCircle = getByTestId(emailNode, 'error-circle'); + await expect(errorCircle).toBeVisible(); + + await editChannel(page, 'email'); + const emailSubject = getByTestId(page, 'emailSubject'); + await expect(emailSubject).toHaveClass(/mantine-TextInput-invalid/); + + await emailSubject.fill('this is email subject'); + await goBack(page); + emailNode = getByTestId(page, 'node-emailSelector'); + errorCircle = getByTestId(emailNode, 'error-circle'); + await expect(errorCircle).not.toBeVisible(); +}); + +test('should allow uploading a logo from email editor', async ({ page }) => { + await page.route('**/organizations', async (route) => { + const response = await page.request.fetch(route.request()); + const body = await response.json(); + if (body) { + delete body.data[0].branding.logo; + } + + await route.fulfill({ + response, + body, + }); + }); + + await page.goto(`/workflows/create`); + await fillBasicNotificationDetails(page, { title: 'Test allow uploading a logo from email editor' }); + await goBack(page); + + await addAndEditChannel(page, 'email'); + const uploadImageButton = getByTestId(page, 'upload-image-button'); + await uploadImageButton.click(); + + const modalButton = page.getByRole('button', { name: 'Yes' }); + + await modalButton.click(); + await expect(page.url()).toContain('/brand'); +}); + +test('should show the brand logo on main page', async ({ page }) => { + await page.goto(`/workflows/create`); + await fillBasicNotificationDetails(page, { title: 'Test allow uploading a logo from email editor' }); + await goBack(page); + + await addAndEditChannel(page, 'email'); + + const brandLogo = getByTestId(page, 'brand-logo'); + await expect(brandLogo).toHaveAttribute('src', 'https://web.novu.co/static/images/logo-light.png'); +}); + +test('should support RTL text content', async ({ page }) => { + await page.goto(`/workflows/create`); + await fillBasicNotificationDetails(page, { title: 'Test support RTL text content' }); + await goBack(page); + + await addAndEditChannel(page, 'email'); + + let editableTextContent = getByTestId(page, 'editable-text-content'); + await editableTextContent.hover(); + await expect(editableTextContent).toHaveCSS('text-align', 'left'); + + const settingsRowButton = getByTestId(page, 'settings-row-btn'); + await settingsRowButton.click(); + + const alignRightButton = getByTestId(page, 'align-right-btn'); + await alignRightButton.click(); + editableTextContent = getByTestId(page, 'editable-text-content'); + await expect(editableTextContent).toHaveCSS('text-align', 'right'); +}); + +test('should create an SMS channel message', async ({ page }) => { + await page.goto(`/workflows/create`); + await fillBasicNotificationDetails(page, { title: 'Test SMS Notification Title' }); + await goBack(page); + + await addAndEditChannel(page, 'sms'); + + const editorParent = page.locator('.monaco-editor textarea').locator('xpath=..'); + await editorParent.click(); + await editorParent.locator('textarea').fill('{{firstName}} someone assigned you to {{taskName}}'); + await goBack(page); + + const submitButton = getByTestId(page, 'notification-template-submit-btn'); + await submitButton.click(); + + const getSnippetButton = getByTestId(page, 'get-snippet-btn'); + await getSnippetButton.click(); + const workflowSidebar = getByTestId(page, 'workflow-sidebar'); + await expect(workflowSidebar).toBeVisible(); + const triggerCodeSnippet = getByTestId(workflowSidebar, 'trigger-code-snippet'); + await expect(triggerCodeSnippet).toContainText('test-sms-notification-title'); + await expect(triggerCodeSnippet).toContainText("import { Novu } from '@novu/node'"); + await expect(triggerCodeSnippet).toContainText('taskName'); + await expect(triggerCodeSnippet).toContainText('firstName'); +}); + +test('should save HTML template email', async ({ page }) => { + await page.goto(`/workflows/create`); + await fillBasicNotificationDetails(page, { title: 'Custom Code HTML Notification Title' }); + await goBack(page); + + await addAndEditChannel(page, 'email'); + + const subjectEl = getByTestId(page, 'emailSubject'); + await subjectEl.fill('this is email subject'); + + await page + .locator('[data-test-id="editor-type-selector"] .mantine-Tabs-tabsList') + .getByText(/Custom Code/) + .first() + .click(); + + let editorParent = page.locator('.monaco-editor textarea').locator('xpath=..'); + await editorParent.click(); + await editorParent.locator('textarea').fill('Hello world code {{name}}
Test
'); + + await goBack(page); + + await editChannel(page, 'email'); + + editorParent = page.locator('.monaco-editor textarea').locator('xpath=..'); + await editorParent.click(); + await expect(editorParent).toContainText('Hello world code {{name}}
Test
'); +}); + +test('should redirect to the workflows page when switching environments', async ({ page }) => { + await page.goto(`/workflows/create`); + await fillBasicNotificationDetails(page, { title: 'Environment Switching' }); + await goBack(page); + + await updateWorkflowButtonClick(page); + + await page.goto(`/changes`); + const promoteChangesPromise = page.waitForResponse((response) => { + return !!response.url().match(/\/v1\/changes\/.*\/apply/) && response.request().method() === 'POST'; + }); + const promoteButton = getByTestId(page, 'promote-btn').first(); + await promoteButton.click(); + await promoteChangesPromise; + + let environmentSwitchPromise = page.waitForResponse((response) => { + return !!response.url().match(/\/auth\/environments\/.*\/switch/) && response.request().method() === 'POST'; + }); + let environmentSwitch = getByTestId(page, 'environment-switch'); + const productionButton = environmentSwitch.getByText('Production'); + await productionButton.click(); + await environmentSwitchPromise; + await expect(page).toHaveURL(/\/workflows/); + + const notificationsTemplate = getByTestId(page, 'notifications-template'); + await notificationsTemplate.getByText(/Environment Switching/).click(); + await expect(page).toHaveURL(/\/workflows\/edit/); + + environmentSwitchPromise = page.waitForResponse((response) => { + return !!response.url().match(/\/auth\/environments\/.*\/switch/) && response.request().method() === 'POST'; + }); + environmentSwitch = getByTestId(page, 'environment-switch'); + const developmentButton = environmentSwitch.getByText('Development'); + await developmentButton.click(); + await environmentSwitchPromise; + await expect(page).toHaveURL(/\/workflows/); +}); + +test('New workflow button should be disabled in the Production', async ({ page }) => { + await page.goto(`/workflows`); + + let environmentSwitch = getByTestId(page, 'environment-switch'); + const productionButton = environmentSwitch.getByText('Production'); + await productionButton.click(); + + const createWorkflowButton = getByTestId(page, 'create-workflow-btn'); + await expect(createWorkflowButton).toBeDisabled(); +}); + +test('Should not allow to go to New Template page in Production', async ({ page }) => { + await page.goto(`/workflows/create`); + + let environmentSwitch = getByTestId(page, 'environment-switch'); + const productionButton = environmentSwitch.getByText('Production'); + await productionButton.click(); + + await expect(page.url()).toContain('/workflows'); +}); + +test('should save Cta buttons state in inApp channel', async ({ page }) => { + await page.goto(`/workflows/create`); + await fillBasicNotificationDetails(page, { title: 'In App CTA Button' }); + await goBack(page); + + await addAndEditChannel(page, 'inApp'); + const editorParent = page.locator('.monaco-editor textarea').locator('xpath=..'); + await editorParent.click(); + await editorParent.locator('textarea').fill('Text content'); + + const controlAdd = getByTestId(page, 'control-add').first(); + await controlAdd.click(); + + const clickArea = getByTestId(page, 'template-container-click-area').first(); + await clickArea.click(); + + await goBack(page); + + await updateWorkflowButtonClick(page); + + await page.goto(`/workflows`); + + const notificationsTemplate = getByTestId(page, 'notifications-template'); + await notificationsTemplate.getByText(/In App CTA Button/).click(); + await expect(page.url()).toContain('/workflows/edit'); + + await editChannel(page, 'inApp'); + + const templateContainerInput = getByTestId(page, 'template-container').first().locator('input'); + await expect(templateContainerInput).toHaveCount(1); + + const removeButton = getByTestId(page, 'remove-button-icon'); + await removeButton.click(); + + await goBack(page); + + await editChannel(page, 'inApp'); + getByTestId(page, 'control-add').first(); +}); + +test('should load successfully the recently created notification template, when going back from editor -> templates list -> editor', async ({ + page, +}) => { + await page.goto(`/workflows`); + + const createWorkflowButton = getByTestId(page, 'create-workflow-btn'); + await createWorkflowButton.click(); + + const createBlankWorkflow = getByTestId(page, 'create-workflow-blank'); + await createBlankWorkflow.click(); + + await fillBasicNotificationDetails(page, { title: 'Test notification' }); + await goBack(page); + + await addAndEditChannel(page, 'inApp'); + const editorParent = page.locator('.monaco-editor textarea').locator('xpath=..'); + await editorParent.click(); + await editorParent.locator('textarea').fill('Test in-app'); + await goBack(page); + + await addAndEditChannel(page, 'email'); + const editableText = getByTestId(page, 'editable-text-content'); + await editableText.clear(); + await editableText.pressSequentially('Test email'); + const subjectEl = getByTestId(page, 'emailSubject'); + await subjectEl.fill('this is email subject'); + const emailPreheader = getByTestId(page, 'emailPreheader'); + await emailPreheader.fill('this is email preheader'); + await goBack(page); + + await updateWorkflowButtonClick(page); + + const workflowsLink = getByTestId(page, 'side-nav-templates-link'); + await workflowsLink.click(); + + const notificationsTemplate = getByTestId(page, 'notifications-template'); + await notificationsTemplate.getByText(/Test notification/).click(); + await expect(page.url()).toContain('/workflows/edit'); + + const inAppNode = getByTestId(page, 'node-inAppSelector'); + await expect(inAppNode).toBeVisible(); + const emailNode = getByTestId(page, 'node-emailSelector'); + await expect(emailNode).toBeVisible(); +}); + +test('should load successfully the same notification template, when going back from templates list -> editor -> templates list -> editor', async ({ + page, +}) => { + await page.goto(`/workflows`); + const template = session.templates[0]; + + let notificationsTemplate = getByTestId(page, 'notifications-template'); + await notificationsTemplate.getByText(template.name).click(); + await expect(page.url()).toContain('/workflows/edit'); + + let inAppNode = getByTestId(page, 'node-inAppSelector'); + await expect(inAppNode).toBeVisible(); + let emailNode = getByTestId(page, 'node-emailSelector'); + await expect(emailNode).toBeVisible(); + + const workflowsLink = getByTestId(page, 'side-nav-templates-link'); + await workflowsLink.click(); + + notificationsTemplate = getByTestId(page, 'notifications-template'); + await notificationsTemplate.getByText(template.name).click(); + await expect(page.url()).toContain('/workflows/edit'); + + inAppNode = getByTestId(page, 'node-inAppSelector'); + await expect(inAppNode).toBeVisible(); + emailNode = getByTestId(page, 'node-emailSelector'); + await expect(emailNode).toBeVisible(); +}); diff --git a/apps/web/tests/start-from-scratch-tour.spec.ts b/apps/web/tests/start-from-scratch-tour.spec.ts new file mode 100644 index 00000000000..9463676dadb --- /dev/null +++ b/apps/web/tests/start-from-scratch-tour.spec.ts @@ -0,0 +1,183 @@ +import { test, expect } from '@playwright/test'; +import os from 'node:os'; + +import { getByTestId, initializeSession } from './utils.ts/browser'; + +let session; + +test.beforeEach(async ({ context }) => { + session = await initializeSession(context, { showOnBoardingTour: true }); +}); + +test('should show the start from scratch intro step', async ({ page }) => { + await page.goto('/workflows/create'); + + const scratchWorkflowTooltip = await getByTestId(page, 'scratch-workflow-tooltip'); + await expect(scratchWorkflowTooltip).toBeVisible(); + + const scratchWorkflowTooltipTitle = await getByTestId(page, 'scratch-workflow-tooltip-title'); + await expect(scratchWorkflowTooltipTitle).toHaveText('Discover a quick guide'); + + const scratchWorkflowTooltipDescription = await getByTestId(page, 'scratch-workflow-tooltip-description'); + await expect(scratchWorkflowTooltipDescription).toHaveText('Four simple tips to become a workflow expert.'); + + const scratchWorkflowTooltipSkipButton = await getByTestId(page, 'scratch-workflow-tooltip-skip-button'); + await expect(scratchWorkflowTooltipSkipButton).toHaveText('Watch later'); + + const scratchWorkflowTooltipPrimaryButton = await getByTestId(page, 'scratch-workflow-tooltip-primary-button'); + await expect(scratchWorkflowTooltipPrimaryButton).toHaveText('Show me'); +}); + +test('should hide the start from scratch intro step after clicking on watch later', async ({ page }) => { + await page.goto('/workflows/create'); + + const scratchWorkflowTooltip = await getByTestId(page, 'scratch-workflow-tooltip'); + await expect(scratchWorkflowTooltip).toBeVisible(); + + const scratchWorkflowTooltipTitle = await getByTestId(page, 'scratch-workflow-tooltip-title'); + await expect(scratchWorkflowTooltipTitle).toHaveText('Discover a quick guide'); + + const scratchWorkflowTooltipDescription = await getByTestId(page, 'scratch-workflow-tooltip-description'); + await expect(scratchWorkflowTooltipDescription).toHaveText('Four simple tips to become a workflow expert.'); + + const scratchWorkflowTooltipPrimaryButton = await getByTestId(page, 'scratch-workflow-tooltip-primary-button'); + await expect(scratchWorkflowTooltipPrimaryButton).toHaveText('Show me'); + + const scratchWorkflowTooltipSkipButton = await getByTestId(page, 'scratch-workflow-tooltip-skip-button'); + await expect(scratchWorkflowTooltipSkipButton).toHaveText('Watch later'); + await scratchWorkflowTooltipSkipButton.click(); + + await expect(scratchWorkflowTooltip).not.toBeVisible(); +}); + +test('should show the start from scratch tour hints', async ({ page }) => { + await page.goto('/workflows/create'); + + const scratchWorkflowTooltip = await getByTestId(page, 'scratch-workflow-tooltip'); + await expect(scratchWorkflowTooltip).toBeVisible(); + + const scratchWorkflowTooltipTitle = await getByTestId(page, 'scratch-workflow-tooltip-title'); + await expect(scratchWorkflowTooltipTitle).toHaveText('Discover a quick guide'); + + const scratchWorkflowTooltipDescription = await getByTestId(page, 'scratch-workflow-tooltip-description'); + await expect(scratchWorkflowTooltipDescription).toHaveText('Four simple tips to become a workflow expert.'); + + const scratchWorkflowTooltipSkipButton = await getByTestId(page, 'scratch-workflow-tooltip-skip-button'); + await expect(scratchWorkflowTooltipSkipButton).toHaveText('Watch later'); + + const scratchWorkflowTooltipPrimaryButton = await getByTestId(page, 'scratch-workflow-tooltip-primary-button'); + await expect(scratchWorkflowTooltipPrimaryButton).toHaveText('Show me'); + await scratchWorkflowTooltipPrimaryButton.click(); + + const scratchWorkflowTooltipTitle2 = await getByTestId(page, 'scratch-workflow-tooltip-title'); + await expect(scratchWorkflowTooltipTitle2).toHaveText('Click to edit workflow name'); + + const scratchWorkflowTooltipDescription2 = await getByTestId(page, 'scratch-workflow-tooltip-description'); + await expect(scratchWorkflowTooltipDescription2).toHaveText( + 'Specify a name for your workflow here or in the workflow settings.' + ); + + const scratchWorkflowTooltipPrimaryButton2 = await getByTestId(page, 'scratch-workflow-tooltip-primary-button'); + await expect(scratchWorkflowTooltipPrimaryButton2).toHaveText('Next'); + await scratchWorkflowTooltipPrimaryButton2.click(); + + const scratchWorkflowTooltipTitle3 = await getByTestId(page, 'scratch-workflow-tooltip-title'); + await expect(scratchWorkflowTooltipTitle3).toHaveText('Verify workflow settings'); + + const scratchWorkflowTooltipDescription3 = await getByTestId(page, 'scratch-workflow-tooltip-description'); + await expect(scratchWorkflowTooltipDescription3).toHaveText( + 'Manage name, identifier, group and description. Set up channels, active by default.' + ); + + const scratchWorkflowTooltipPrimaryButton3 = await getByTestId(page, 'scratch-workflow-tooltip-primary-button'); + await expect(scratchWorkflowTooltipPrimaryButton3).toHaveText('Next'); + await scratchWorkflowTooltipPrimaryButton3.click(); + + const scratchWorkflowTooltipTitle4 = await getByTestId(page, 'scratch-workflow-tooltip-title'); + await expect(scratchWorkflowTooltipTitle4).toHaveText('Build a notification workflow'); + + const scratchWorkflowTooltipDescription4 = await getByTestId(page, 'scratch-workflow-tooltip-description'); + await expect(scratchWorkflowTooltipDescription4).toHaveText( + 'Add channels you would like to send notifications to. The channels will be inserted to the trigger snippet.' + ); + + const scratchWorkflowTooltipPrimaryButton4 = await getByTestId(page, 'scratch-workflow-tooltip-primary-button'); + await expect(scratchWorkflowTooltipPrimaryButton4).toHaveText('Next'); + await scratchWorkflowTooltipPrimaryButton4.click(); + + const scratchWorkflowTooltipTitle5 = await getByTestId(page, 'scratch-workflow-tooltip-title'); + await expect(scratchWorkflowTooltipTitle5).toHaveText('Run a test or Get Snippet'); + + const scratchWorkflowTooltipDescription5 = await getByTestId(page, 'scratch-workflow-tooltip-description'); + await expect(scratchWorkflowTooltipDescription5).toHaveText( + 'Test a trigger as if it was sent from your API. Deploy it to your App by copy/paste trigger snippet.' + ); + + const gotItButton = await getByTestId(page, 'scratch-workflow-tooltip-primary-button'); + await expect(gotItButton).toHaveText('Got it'); + await gotItButton.click(); + + await expect(scratchWorkflowTooltip).not.toBeVisible(); +}); + +test('should show the dots navigation after the intro step', async ({ page }) => { + await page.goto('/workflows/create'); + + const scratchWorkflowTooltip = await getByTestId(page, 'scratch-workflow-tooltip'); + await expect(scratchWorkflowTooltip).toBeVisible(); + + const scratchWorkflowTooltipTitle = await getByTestId(page, 'scratch-workflow-tooltip-title'); + await expect(scratchWorkflowTooltipTitle).toHaveText('Discover a quick guide'); + + const scratchWorkflowTooltipDescription = await getByTestId(page, 'scratch-workflow-tooltip-description'); + await expect(scratchWorkflowTooltipDescription).toHaveText('Four simple tips to become a workflow expert.'); + + const scratchWorkflowTooltipSkipButton = await getByTestId(page, 'scratch-workflow-tooltip-skip-button'); + await expect(scratchWorkflowTooltipSkipButton).toHaveText('Watch later'); + + const scratchWorkflowTooltipPrimaryButton = await getByTestId(page, 'scratch-workflow-tooltip-primary-button'); + await expect(scratchWorkflowTooltipPrimaryButton).toHaveText('Show me'); + await scratchWorkflowTooltipPrimaryButton.click(); + + const dotsNavigation = await getByTestId(page, 'scratch-workflow-tooltip-dots-navigation'); + await expect(dotsNavigation).toBeVisible(); +}); + +test('should show not show the start from scratch tour hints after it is shown twice ', async ({ page }) => { + await page.goto('/workflows/create'); + + let scratchWorkflowTooltip = await getByTestId(page, 'scratch-workflow-tooltip'); + await expect(scratchWorkflowTooltip).toBeVisible(); + + const scratchWorkflowTooltipTitle = await getByTestId(page, 'scratch-workflow-tooltip-title'); + await expect(scratchWorkflowTooltipTitle).toHaveText('Discover a quick guide'); + + const scratchWorkflowTooltipDescription = await getByTestId(page, 'scratch-workflow-tooltip-description'); + await expect(scratchWorkflowTooltipDescription).toHaveText('Four simple tips to become a workflow expert.'); + + const scratchWorkflowTooltipPrimaryButton = await getByTestId(page, 'scratch-workflow-tooltip-primary-button'); + await expect(scratchWorkflowTooltipPrimaryButton).toHaveText('Show me'); + + let scratchWorkflowTooltipSkipButton = await getByTestId(page, 'scratch-workflow-tooltip-skip-button'); + await expect(scratchWorkflowTooltipSkipButton).toHaveText('Watch later'); + await scratchWorkflowTooltipSkipButton.click(); + + await expect(scratchWorkflowTooltip).not.toBeVisible(); + + await page.reload(); + + scratchWorkflowTooltip = await getByTestId(page, 'scratch-workflow-tooltip'); + await expect(scratchWorkflowTooltip).toBeVisible(); + + scratchWorkflowTooltipSkipButton = await getByTestId(page, 'scratch-workflow-tooltip-skip-button'); + await expect(scratchWorkflowTooltipSkipButton).toHaveText('Watch later'); + await scratchWorkflowTooltipSkipButton.click(); + + const scratchWorkflowTooltip2 = await getByTestId(page, 'scratch-workflow-tooltip'); + await expect(scratchWorkflowTooltip2).not.toBeVisible(); + + await page.reload(); + + scratchWorkflowTooltip = await getByTestId(page, 'scratch-workflow-tooltip'); + await expect(scratchWorkflowTooltip).not.toBeVisible(); +}); diff --git a/apps/web/tests/utils.ts/browser.ts b/apps/web/tests/utils.ts/browser.ts new file mode 100644 index 00000000000..1985519aa13 --- /dev/null +++ b/apps/web/tests/utils.ts/browser.ts @@ -0,0 +1,31 @@ +import { BrowserContext, Locator, Page } from '@playwright/test'; + +import { getSession, ISessionOptions } from './plugins'; + +export async function initializeSession(context: BrowserContext, settings: ISessionOptions = {}) { + const session = await getSession(settings); + + await context.addInitScript((session) => { + (window as any).isPlaywright = true; + localStorage.setItem('auth_token', session.token); + }, session); + + return session; +} + +export function getByTestId(page: Page | Locator, selector: string, options?: Parameters[1]) { + return page.locator(`[data-test-id="${selector}"]`, options); +} + +export async function dragAndDrop(page: Page, dragSelector: string, dropSelector: string) { + const dndEl = await getByTestId(page, dragSelector); + await dndEl.dragTo(await getByTestId(page, dropSelector), { force: true }); +} + +export async function isDarkTheme(page: Page) { + const backgroundColor = await page.evaluate(() => { + const body = document.body; + return window.getComputedStyle(body).backgroundColor; + }); + return backgroundColor.toLowerCase() !== '#EDF0F2' && backgroundColor.toLowerCase() !== 'rgb(237, 240, 242)'; +} diff --git a/apps/web/tests/utils.ts/integrations.ts b/apps/web/tests/utils.ts/integrations.ts new file mode 100644 index 00000000000..f16ed59cfb5 --- /dev/null +++ b/apps/web/tests/utils.ts/integrations.ts @@ -0,0 +1,107 @@ +import { expect, Locator, Page } from '@playwright/test'; + +import { getByTestId } from './browser'; + +export const navigateToGetStarted = async (page: Page, card = 'channel-card-email') => { + await page.goto('/get-started'); + await expect(page).toHaveURL(/\/get-started/); + + const cardComponent = getByTestId(page, card); + const button = cardComponent.locator('button'); + await expect(button).toContainText('Change Provider'); + await button.click(); + + const integrationsModal = getByTestId(page, 'integrations-list-modal'); + await expect(integrationsModal).toBeVisible(); + await expect(integrationsModal).toContainText('Integrations Store'); +}; + +export const checkTableLoading = async (page: Page | Locator) => { + const nameCellLoadingElements = getByTestId(page, 'integration-name-cell-loading'); + await expect(nameCellLoadingElements).toHaveCount(10); + await expect(nameCellLoadingElements.first()).toBeVisible(); + + const providerCellLoadingElements = getByTestId(page, 'integration-provider-cell-loading'); + await expect(providerCellLoadingElements).toHaveCount(10); + await expect(providerCellLoadingElements.first()).toBeVisible(); + + const channelCellLoadingElements = getByTestId(page, 'integration-channel-cell-loading'); + await expect(channelCellLoadingElements).toHaveCount(10); + await expect(channelCellLoadingElements.first()).toBeVisible(); + + const envCellLoadingElements = getByTestId(page, 'integration-environment-cell-loading'); + await expect(envCellLoadingElements).toHaveCount(10); + await expect(envCellLoadingElements.first()).toBeVisible(); + + const statusCellLoadingElements = getByTestId(page, 'integration-status-cell-loading'); + await expect(statusCellLoadingElements).toHaveCount(10); + await expect(statusCellLoadingElements.first()).toBeVisible(); +}; + +export const checkTableRow = async ( + page: Page | Locator, + { + name, + isFree, + provider, + channel, + environment, + status, + }: { + name: string; + isFree?: boolean; + provider: string; + channel: string; + environment?: string; + status: string; + } +) => { + const integrationsTable = getByTestId(page, 'integrations-list-table'); + const nthRow = integrationsTable.locator('tbody tr', { hasText: new RegExp(`${name}.*${environment ?? ''}`) }); + const nameCell = getByTestId(nthRow, 'integration-name-cell', { hasText: name }); + await expect(nameCell).toBeVisible(); + + if (isFree) { + await expect(nameCell).toContainText('Test Provider'); + } + + const providerCell = getByTestId(nthRow, 'integration-provider-cell', { hasText: provider }); + await expect(providerCell).toBeVisible(); + + const channelCell = getByTestId(nthRow, 'integration-channel-cell', { hasText: channel }); + await expect(channelCell).toBeVisible(); + + if (environment) { + const environmentCell = getByTestId(nthRow, 'integration-environment-cell', { hasText: environment }); + await expect(environmentCell).toBeVisible(); + } + + const statusCell = getByTestId(nthRow, 'integration-status-cell', { hasText: status }); + await expect(statusCell).toBeVisible(); +}; + +export const clickOnListRow = async (page: Page | Locator, name: string | RegExp) => { + const integrationsTable = getByTestId(page, 'integrations-list-table'); + const row = integrationsTable.locator('tr', { hasText: name }).first(); + await expect(row).toBeVisible(); + await row.click(); +}; + +export async function interceptIntegrationsRequest({ + page, + modifyBody, +}: { + page: Page; + modifyBody?: (body: any) => any; +}) { + return page.route('**/v1/integrations', async (route) => { + const response = await page.request.fetch(route.request()); + await new Promise((resolve) => setTimeout(resolve, 3000)); + const body = await response.json(); + + await route.fulfill({ + response, + body: JSON.stringify(modifyBody ? modifyBody(body) : body), + }); + }); +} diff --git a/apps/web/tests/utils.ts/plugins.ts b/apps/web/tests/utils.ts/plugins.ts new file mode 100644 index 00000000000..013a6dfaa42 --- /dev/null +++ b/apps/web/tests/utils.ts/plugins.ts @@ -0,0 +1,122 @@ +import { DalService, IntegrationRepository, NotificationTemplateEntity } from '@novu/dal'; +import { ChannelTypeEnum, ProvidersIdEnum } from '@novu/shared'; +import { + UserSession, + NotificationTemplateService, + SubscribersService, + NotificationsService, + JobsService, +} from '@novu/testing'; + +export interface ISessionOptions { + noEnvironment?: boolean; + partialTemplate?: Partial; + noTemplates?: boolean; + showOnBoardingTour?: boolean; +} + +export async function getSession(settings: ISessionOptions = {}) { + const dal = new DalService(); + await dal.connect(process.env.MONGODB_URL ?? ''); + + const session = new UserSession('http://127.0.0.1:1336'); + await session.initialize({ + noEnvironment: settings?.noEnvironment, + showOnBoardingTour: settings?.showOnBoardingTour, + }); + + const notificationTemplateService = new NotificationTemplateService( + session.user._id, + session.organization._id, + session.environment._id as string + ); + + let templates; + if (!settings?.noTemplates) { + const templatePartial = settings?.partialTemplate || {}; + + templates = await Promise.all([ + notificationTemplateService.createTemplate({ ...(templatePartial as any) }), + notificationTemplateService.createTemplate({ + active: false, + draft: true, + }), + notificationTemplateService.createTemplate(), + notificationTemplateService.createTemplate(), + notificationTemplateService.createTemplate(), + notificationTemplateService.createTemplate(), + ]); + } + + return { + token: session.token.split(' ')[1], + user: session.user, + organization: session.organization, + environment: session.environment, + templates, + }; +} + +export async function deleteProvider(query: { + providerId: ProvidersIdEnum; + channel: ChannelTypeEnum; + environmentId: string; + organizationId: string; +}) { + const dal = new DalService(); + await dal.connect(process.env.MONGODB_URL ?? ''); + + const repository = new IntegrationRepository(); + + return await repository.deleteMany({ + channel: query.channel, + providerId: query.providerId, + _environmentId: query.environmentId, + _organizationId: query.organizationId, + }); +} + +export async function createNotifications({ + identifier, + token, + count = 1, + subscriberId, + environmentId, + organizationId, + templateId, +}: { + identifier: string; + token: string; + count?: number; + subscriberId?: string; + environmentId: string; + organizationId: string; + templateId?: string; +}) { + const jobsService = new JobsService(); + let subId = subscriberId; + if (!subId) { + const subscribersService = new SubscribersService(organizationId, environmentId); + const subscriber = await subscribersService.createSubscriber(); + subId = subscriber.subscriberId; + } + + const triggerIdentifier = identifier; + const service = new NotificationsService(token); + const session = new UserSession(process.env.API_URL); + + // eslint-disable-next-line no-plusplus + for (let i = 0; i < count; i++) { + await service.triggerEvent(triggerIdentifier, subId, {}); + } + + if (organizationId) { + await session.awaitRunningJobs(templateId, undefined, 0, organizationId); + } + + while ((await jobsService.standardQueue.getWaitingCount()) || (await jobsService.standardQueue.getActiveCount())) { + await new Promise((resolve) => setTimeout(resolve, 50)); + } + + return 'ok'; +} diff --git a/apps/web/tests/utils.ts/workflow-editor.ts b/apps/web/tests/utils.ts/workflow-editor.ts new file mode 100644 index 00000000000..90ab651843e --- /dev/null +++ b/apps/web/tests/utils.ts/workflow-editor.ts @@ -0,0 +1,61 @@ +import { Page } from '@playwright/test'; + +import { dragAndDrop, getByTestId } from './browser'; + +export type Channel = 'inApp' | 'email' | 'sms' | 'chat' | 'push' | 'digest' | 'delay'; + +export async function fillBasicNotificationDetails( + page: Page, + { title, description }: { title?: string; description?: string } = {} +) { + const settings = await getByTestId(page, 'settings-page'); + await settings.click(); + + const titleEl = await getByTestId(page, 'title'); + await titleEl.first().clear(); + await titleEl.first().fill(title ?? 'Test Notification Title'); + + const descriptionEl = await getByTestId(page, 'description'); + await descriptionEl.fill(description ?? 'This is a test description for a test title'); +} + +export async function goBack(page: Page) { + const closeButton = await getByTestId(page, 'sidebar-close'); + await closeButton.click(); +} + +export async function editChannel(page: Page, channel: Channel) { + const stepNode = await getByTestId(page, `node-${channel}Selector`); + await stepNode.last().click(); + + if (['inApp', 'email', 'sms', 'chat', 'push'].includes(channel)) { + const sidebarComponent = await getByTestId(page, 'step-editor-sidebar'); + const editButton = await getByTestId(sidebarComponent, 'edit-action'); + await editButton.click(); + } +} + +export async function addAndEditChannel(page: Page, channel: Channel) { + await dragAndDrop(page, `dnd-${channel}Selector`, 'addNodeButton'); + await editChannel(page, channel); +} + +export async function updateWorkflowButtonClick(page: Page, { noWaitAfter = false }: { noWaitAfter?: boolean } = {}) { + if (noWaitAfter) { + const updateWorkflowButton = getByTestId(page, 'notification-template-submit-btn'); + await updateWorkflowButton.click(); + + return; + } + + const updateTemplateRequest = page.waitForResponse((response) => { + return response.url().match(/\/v1\/notification-templates\/.*/) && response.request().method() === 'PUT'; + }); + const getTemplateRequest = page.waitForResponse((response) => { + return response.url().match(/\/v1\/notification-templates\/.*/) && response.request().method() === 'GET'; + }); + let updateWorkflowButton = getByTestId(page, 'notification-template-submit-btn'); + await updateWorkflowButton.click(); + await updateTemplateRequest; + await getTemplateRequest; +} diff --git a/apps/widget/cypress/e2e/notifications-list.spec.ts b/apps/widget/cypress/e2e/notifications-list.spec.ts index e7a24666b19..5aadb6ef2f6 100644 --- a/apps/widget/cypress/e2e/notifications-list.spec.ts +++ b/apps/widget/cypress/e2e/notifications-list.spec.ts @@ -191,18 +191,9 @@ describe('Notifications List', function () { scrollToBottom(); cy.getByTestId('notification-list-item').should('have.length', 26); - cy.getByTestId('notification-list-item') - .should('exist') - .each(($item, $index) => { - const notificationContentNumber = 20 - $index > 0 ? ' ' + (20 - $index).toString() : ''; - cy.wrap($item) - .invoke('text') - .then((text) => { - if ($index !== 0) { - expect(text).to.contains(`John${notificationContentNumber}`); - } - }); - }); + for (let i = 0; i < 21; i++) { + cy.getByTestId('notification-list-item').contains(`John ${i}`).should('exist'); + } }); }); diff --git a/enterprise/packages/web/echo/.eslintrc.js b/enterprise/packages/web/echo/.eslintrc.js new file mode 100644 index 00000000000..2f449acbcd9 --- /dev/null +++ b/enterprise/packages/web/echo/.eslintrc.js @@ -0,0 +1,43 @@ +module.exports = { + rules: { + 'func-names': 'off', + 'react/jsx-props-no-spreading': 'off', + 'react/no-array-index-key': 'off', + 'no-empty-pattern': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + 'react/no-unescaped-entities': 'off', + 'react/jsx-closing-bracket-location': 'off', + '@typescript-eslint/ban-types': 'off', + 'react/jsx-wrap-multilines': 'off', + 'jsx-a11y/anchor-is-valid': 'off', + 'promise/catch-or-return': 'off', + 'react/jsx-one-expression-per-line': 'off', + '@typescript-eslint/no-explicit-any': 'off', + 'jsx-a11y/aria-role': 'off', + 'jsx-a11y/no-static-element-interactions': 'off', + 'react/require-default-props': 'off', + 'react/no-danger': 'off', + 'jsx-a11y/click-events-have-key-events': 'off', + '@typescript-eslint/naming-convention': [ + 'error', + { + filter: '_', + selector: 'variableLike', + leadingUnderscore: 'allow', + format: ['PascalCase', 'camelCase', 'UPPER_CASE'], + }, + ], + '@typescript-eslint/no-empty-function': 'off', + }, + env: { + 'cypress/globals': true, + }, + ignorePatterns: ['craco.config.js', 'cypress/*'], + extends: ['plugin:cypress/recommended', '../../../.eslintrc.js'], + plugins: ['cypress'], + parserOptions: { + project: './tsconfig.json', + ecmaVersion: 2020, + sourceType: 'module', + }, +}; diff --git a/enterprise/packages/web/echo/.gitignore b/enterprise/packages/web/echo/.gitignore new file mode 100644 index 00000000000..013add19d46 --- /dev/null +++ b/enterprise/packages/web/echo/.gitignore @@ -0,0 +1,28 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + + +# production +build +dist + +.npmrc +.idea/* +.nyc_output + +test + +src/**.js +coverage +*.log +package-lock.json + +storybook-static + diff --git a/enterprise/packages/web/echo/check-ee.mjs b/enterprise/packages/web/echo/check-ee.mjs new file mode 100644 index 00000000000..0db73052ff4 --- /dev/null +++ b/enterprise/packages/web/echo/check-ee.mjs @@ -0,0 +1,65 @@ +import spawn from 'cross-spawn'; +import { fileURLToPath } from 'url'; +import path from 'path'; +import * as fs from 'fs'; +const dirname = path.dirname(fileURLToPath(import.meta.url)); + +const ROOT_PATH = path.resolve(dirname); +const ENCODING_TYPE = 'utf8'; +const NEW_LINE_CHAR = '\n'; + +class CliLogs { + constructor() { + this._logs = []; + this.log = this.log.bind(this); + } + + log(log) { + const cleanLog = log.trim(); + if (cleanLog.length) { + this._logs.push(cleanLog); + } + } + + get logs() { + return this._logs; + } + + get joinedLogs() { + return this.logs.join(NEW_LINE_CHAR); + } +} + +function pnpmRun(...args) { + const logData = new CliLogs(); + let pnpmProcess; + + return new Promise((resolve, reject) => { + const processOptions = { + cwd: ROOT_PATH, + env: process.env, + }; + + pnpmProcess = spawn('pnpm', args, processOptions); + + pnpmProcess.stdin.setEncoding(ENCODING_TYPE); + pnpmProcess.stdout.setEncoding(ENCODING_TYPE); + pnpmProcess.stderr.setEncoding(ENCODING_TYPE); + pnpmProcess.stdout.on('data', logData.log); + pnpmProcess.stderr.on('data', logData.log); + + pnpmProcess.on('close', (code) => { + if (code !== 0) { + reject(logData.joinedLogs); + } else { + resolve(logData.joinedLogs); + } + }); + }); +} + +const hasSrcFolder = fs.existsSync(path.resolve(ROOT_PATH, 'src')); +if (hasSrcFolder) { + await pnpmRun('build:esm'); + await pnpmRun('build:types'); +} diff --git a/enterprise/packages/web/echo/package.json b/enterprise/packages/web/echo/package.json new file mode 100644 index 00000000000..4262b7a49de --- /dev/null +++ b/enterprise/packages/web/echo/package.json @@ -0,0 +1,54 @@ +{ + "name": "@novu/ee-echo-web", + "version": "0.24.1", + "description": "", + "repository": "https://github.com/novuhq/novu", + "license": "ISC", + "author": "", + "private": true, + "sideEffects": false, + "module": "dist/esm/index.js", + "types": "dist/types/index.d.ts", + "files": [ + "dist/esm" + ], + "scripts": { + "prebuild": "rimraf dist", + "build": "node ./check-ee.mjs", + "build:esm": "cross-env node_modules/.bin/tsc -p tsconfig.esm.json", + "build:esm:watch": "cross-env node_modules/.bin/tsc -p tsconfig.esm.json -w --preserveWatchOutput", + "build:types": "tsc --declaration --emitDeclarationOnly --declarationMap --declarationDir dist/types -p tsconfig.json", + "build:watch": "npm run build:esm:watch", + "lint": "eslint --ext .ts,.tsx src --no-error-on-unmatched-pattern", + "test": "echo 'skip test in the ci'", + "start": "npm run build:watch" + }, + "dependencies": { + "@mantine/core": "^5.7.1", + "@mantine/hooks": "^5.7.1", + "@novu/design-system": "^0.24.1", + "@novu/shared-web": "^0.24.1", + "@rjsf/core": "^5.17.1", + "@rjsf/validator-ajv8": "^5.17.1", + "@tanstack/react-query": "^4.20.4", + "react-router-dom": "6.2.2", + "tslib": "^2.3.1" + }, + "devDependencies": { + "eslint": "^8.33.0", + "eslint-plugin-react-hooks": "^4.4.0", + "@types/node": "^18.11.12", + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", + "rimraf": "^3.0.2", + "ts-loader": "~9.4.0", + "tslib": "^2.3.1", + "typescript": "4.9.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + } +} diff --git a/enterprise/packages/web/echo/src b/enterprise/packages/web/echo/src new file mode 120000 index 00000000000..7edcb0e30e4 --- /dev/null +++ b/enterprise/packages/web/echo/src @@ -0,0 +1 @@ +../../../../.source/web/echo/src \ No newline at end of file diff --git a/enterprise/packages/web/echo/tsconfig.esm.json b/enterprise/packages/web/echo/tsconfig.esm.json new file mode 100644 index 00000000000..48e68b0620b --- /dev/null +++ b/enterprise/packages/web/echo/tsconfig.esm.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "outDir": "./dist/esm" + } +} diff --git a/enterprise/packages/web/echo/tsconfig.json b/enterprise/packages/web/echo/tsconfig.json new file mode 100644 index 00000000000..59907bb89b1 --- /dev/null +++ b/enterprise/packages/web/echo/tsconfig.json @@ -0,0 +1,34 @@ +{ + "include": [ + "src" + ], + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist/cjs", + "forceConsistentCasingInFileNames": true, + "target": "es6", + "strict": true, + "typeRoots": [ + "./node_modules/@types" + ], + "jsx": "react", + "module": "commonjs", + "lib": [ + "ESNext", + "dom" + ], + "skipLibCheck": true, + "declaration": false, + "declarationMap": false, + "sourceMap": true, + "removeComments": false, + "allowSyntheticDefaultImports": true, + "baseUrl": "." + }, + "exclude": [ + "src/**/*.test.*", + "src/*.test.*", + "node_modules", + "**/node_modules/*" + ] +} diff --git a/libs/dal/src/repositories/organization/organization.entity.ts b/libs/dal/src/repositories/organization/organization.entity.ts index 2d5790fcec4..23669ad663d 100644 --- a/libs/dal/src/repositories/organization/organization.entity.ts +++ b/libs/dal/src/repositories/organization/organization.entity.ts @@ -30,6 +30,8 @@ export class OrganizationEntity implements IOrganizationEntity { createdAt: string; updatedAt: string; + + externalId?: string; } export type OrganizationDBModel = OrganizationEntity; diff --git a/libs/dal/src/repositories/organization/organization.schema.ts b/libs/dal/src/repositories/organization/organization.schema.ts index 86113eb7936..90dbdac2ad3 100644 --- a/libs/dal/src/repositories/organization/organization.schema.ts +++ b/libs/dal/src/repositories/organization/organization.schema.ts @@ -60,6 +60,7 @@ const organizationSchema = new Schema( default: false, }, }, + externalId: Schema.Types.String, }, schemaOptions ); diff --git a/libs/dal/src/repositories/user/user.entity.ts b/libs/dal/src/repositories/user/user.entity.ts index 3cf131ac94c..d1dce167702 100644 --- a/libs/dal/src/repositories/user/user.entity.ts +++ b/libs/dal/src/repositories/user/user.entity.ts @@ -53,6 +53,8 @@ export class UserEntity implements IUserEntity { servicesHashes?: { intercom?: string }; jobTitle?: JobTitleEnum; + + externalId?: string; } export type UserDBModel = UserEntity; diff --git a/libs/dal/src/repositories/user/user.schema.ts b/libs/dal/src/repositories/user/user.schema.ts index 26541b276c1..8d55b3909cc 100644 --- a/libs/dal/src/repositories/user/user.schema.ts +++ b/libs/dal/src/repositories/user/user.schema.ts @@ -41,6 +41,7 @@ const userSchema = new Schema( intercom: Schema.Types.String, }, jobTitle: Schema.Types.String, + externalId: Schema.Types.String, }, schemaOptions ); diff --git a/libs/shared-web/src/config.ts b/libs/shared-web/src/config.ts index 994f5a2f5e9..b2d6e9aba9a 100644 --- a/libs/shared-web/src/config.ts +++ b/libs/shared-web/src/config.ts @@ -12,15 +12,17 @@ declare global { } const isCypress = (isBrowser() && (window as any).Cypress) || (isBrowser() && (window as any).parent.Cypress); +const isPlaywright = isBrowser() && (window as any).isPlaywright; export const API_ROOT = - window._env_.REACT_APP_API_URL || isCypress + window._env_.REACT_APP_API_URL || isCypress || isPlaywright ? window._env_.REACT_APP_API_URL || process.env.REACT_APP_API_URL || 'http://localhost:1336' : window._env_.REACT_APP_API_URL || process.env.REACT_APP_API_URL || 'http://localhost:3000'; -export const WS_URL = isCypress - ? window._env_.REACT_APP_WS_URL || process.env.REACT_APP_WS_URL || 'http://localhost:1340' - : window._env_.REACT_APP_WS_URL || process.env.REACT_APP_WS_URL || 'http://localhost:3002'; +export const WS_URL = + isCypress || isPlaywright + ? window._env_.REACT_APP_WS_URL || process.env.REACT_APP_WS_URL || 'http://localhost:1340' + : window._env_.REACT_APP_WS_URL || process.env.REACT_APP_WS_URL || 'http://localhost:3002'; export const SENTRY_DSN = window._env_.REACT_APP_SENTRY_DSN || process.env.REACT_APP_SENTRY_DSN; @@ -29,7 +31,7 @@ export const ENV = window._env_.REACT_APP_ENVIRONMENT || process.env.REACT_APP_E const blueprintApiUrlByEnv = ENV === 'production' || ENV === 'prod' ? 'https://api.novu.co' : 'https://dev.api.novu.co'; export const BLUEPRINTS_API_URL = - window._env_.REACT_APP_BLUEPRINTS_API_URL || isCypress + window._env_.REACT_APP_BLUEPRINTS_API_URL || isCypress || isPlaywright ? window._env_.REACT_APP_BLUEPRINTS_API_URL || process.env.REACT_APP_BLUEPRINTS_API_URL || 'http://localhost:1336' : blueprintApiUrlByEnv; @@ -49,9 +51,10 @@ export const INTERCOM_APP_ID = window._env_.REACT_APP_INTERCOM_APP_ID || process export const CONTEXT_PATH = getContextPath(NovuComponentEnum.WEB); -export const WEBHOOK_URL = isCypress - ? window._env_.REACT_APP_WEBHOOK_URL || process.env.REACT_APP_WEBHOOK_URL || 'http://localhost:1341' - : window._env_.REACT_APP_WEBHOOK_URL || process.env.REACT_APP_WEBHOOK_URL || 'http://localhost:3003'; +export const WEBHOOK_URL = + isCypress || isPlaywright + ? window._env_.REACT_APP_WEBHOOK_URL || process.env.REACT_APP_WEBHOOK_URL || 'http://localhost:1341' + : window._env_.REACT_APP_WEBHOOK_URL || process.env.REACT_APP_WEBHOOK_URL || 'http://localhost:3003'; export const MAIL_SERVER_DOMAIN = window._env_.REACT_APP_MAIL_SERVER_DOMAIN || process.env.REACT_APP_MAIL_SERVER_DOMAIN || 'dev.inbound-mail.novu.co'; @@ -60,7 +63,7 @@ export const LAUNCH_DARKLY_CLIENT_SIDE_ID = window._env_.REACT_APP_LAUNCH_DARKLY_CLIENT_SIDE_ID || process.env.REACT_APP_LAUNCH_DARKLY_CLIENT_SIDE_ID; export const FEATURE_FLAGS = Object.values(FeatureFlagsKeysEnum).reduce((acc, key) => { - const defaultValue = isCypress ? 'true' : 'false'; + const defaultValue = isCypress || isPlaywright ? 'true' : 'false'; acc[key] = window._env_[key] || process.env[key] || defaultValue; return acc; diff --git a/libs/shared/src/consts/handlebar-helpers/handlebarHelpers.ts b/libs/shared/src/consts/handlebar-helpers/handlebarHelpers.ts index 5595ffb44bf..99e855a48df 100644 --- a/libs/shared/src/consts/handlebar-helpers/handlebarHelpers.ts +++ b/libs/shared/src/consts/handlebar-helpers/handlebarHelpers.ts @@ -10,6 +10,12 @@ export enum HandlebarHelpersEnum { SORT_BY = 'sortBy', NUMBERFORMAT = 'numberFormat', I18N = 'i18n', + GT = 'gt', + GTE = 'gte', + LT = 'lt', + LTE = 'lte', + EQ = 'eq', + NE = 'ne', } // eslint-disable-next-line @typescript-eslint/naming-convention @@ -25,4 +31,10 @@ export const HandlebarHelpers = { [HandlebarHelpersEnum.SORT_BY]: { description: 'sort an array of objects by a property' }, [HandlebarHelpersEnum.NUMBERFORMAT]: { description: 'format number' }, [HandlebarHelpersEnum.I18N]: { description: 'translate' }, + [HandlebarHelpersEnum.GT]: { description: 'greater than' }, + [HandlebarHelpersEnum.GTE]: { description: 'greater than or equal to' }, + [HandlebarHelpersEnum.LT]: { description: 'lesser than' }, + [HandlebarHelpersEnum.LTE]: { description: 'lesser than or equal to' }, + [HandlebarHelpersEnum.EQ]: { description: 'strict equal' }, + [HandlebarHelpersEnum.NE]: { description: 'strict not equal to' }, }; diff --git a/libs/shared/src/entities/organization/organization.interface.ts b/libs/shared/src/entities/organization/organization.interface.ts index d25d89baf19..f46173c6444 100644 --- a/libs/shared/src/entities/organization/organization.interface.ts +++ b/libs/shared/src/entities/organization/organization.interface.ts @@ -18,4 +18,5 @@ export interface IOrganizationEntity { productUseCases?: ProductUseCases; createdAt: string; updatedAt: string; + externalId?: string; } diff --git a/libs/shared/src/entities/user/user.interface.ts b/libs/shared/src/entities/user/user.interface.ts index 204f7c111ca..e102d331dc6 100644 --- a/libs/shared/src/entities/user/user.interface.ts +++ b/libs/shared/src/entities/user/user.interface.ts @@ -14,4 +14,5 @@ export interface IUserEntity { showOnBoardingTour?: number; servicesHashes?: IServicesHashes; jobTitle?: JobTitleEnum; + externalId?: string; } diff --git a/package.json b/package.json index 8393916d578..4044088f66e 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "nx": "nx", "lint-staged": "lint-staged", "generate:provider": "npx hygen provider new --version 0.24.1", - "lint": "nx run-many --target=lint --all", + "lint": "nx run-many --target=lint --all", "test": "cross-env CI=true lerna run test:watch --parallel", "start:dev": "cross-env TZ=UTC lerna run start:dev --stream --parallel --concurrency=20 --scope=@novu/{api,worker,web,widget,ws,notification-center}", "start:web": "cross-env nx run @novu/web:start", diff --git a/packages/application-generic/src/usecases/compile-template/compile-template.spec.ts b/packages/application-generic/src/usecases/compile-template/compile-template.spec.ts index 4003ac48f29..9b2393168b2 100644 --- a/packages/application-generic/src/usecases/compile-template/compile-template.spec.ts +++ b/packages/application-generic/src/usecases/compile-template/compile-template.spec.ts @@ -209,4 +209,148 @@ describe('Compile Template', function () { expect(result).toEqual('
Not a number
'); }); }); + + describe('gt helper', () => { + const template = `{{#gt steps 5 }}gt block{{else}}else block{{/gt}}`; + it('shoud render gt block', async () => { + const result = await useCase.execute({ + data: { steps: 6 }, + template, + }); + + expect(result).toEqual('gt block'); + }); + + it('shoud render alternative block', async () => { + const result = await useCase.execute({ + data: { steps: 5 }, + template, + }); + + expect(result).toEqual('else block'); + }); + }); + + describe('gte helper', () => { + const template = `{{#gte steps 5 }}gte block{{else}}else block{{/gte}}`; + it('shoud render gte block', async () => { + const result = await useCase.execute({ + data: { steps: 5 }, + template, + }); + + expect(result).toEqual('gte block'); + }); + + it('shoud render alternative block', async () => { + const result = await useCase.execute({ + data: { steps: 4 }, + template, + }); + + expect(result).toEqual('else block'); + }); + }); + + describe('lt helper', () => { + const template = `{{#lt steps 5 }}lt block{{else}}else block{{/lt}}`; + it('shoud render lt block', async () => { + const result = await useCase.execute({ + data: { steps: 4 }, + template, + }); + + expect(result).toEqual('lt block'); + }); + + it('shoud render alternative block', async () => { + const result = await useCase.execute({ + data: { steps: 5 }, + template, + }); + + expect(result).toEqual('else block'); + }); + }); + + describe('lte helper', () => { + const template = `{{#lte steps 5 }}lte block{{else}}else block{{/lte}}`; + it('shoud render lte block', async () => { + const result = await useCase.execute({ + data: { steps: 5 }, + template, + }); + + expect(result).toEqual('lte block'); + }); + + it('shoud render alternative block', async () => { + const result = await useCase.execute({ + data: { steps: 6 }, + template, + }); + + expect(result).toEqual('else block'); + }); + }); + + describe('eq helper', () => { + const template = `{{#eq steps 5 }}eq block{{else}}else block{{/eq}}`; + it('shoud render eq block', async () => { + const result = await useCase.execute({ + data: { steps: 5 }, + template, + }); + + expect(result).toEqual('eq block'); + }); + + it('shoud use strict check and render alternative block', async () => { + const result = await useCase.execute({ + data: { steps: '5' }, + template, + }); + + expect(result).toEqual('else block'); + }); + + it('shoud render alternative block', async () => { + const result = await useCase.execute({ + data: { steps: 6 }, + template, + }); + + expect(result).toEqual('else block'); + }); + }); + + describe('ne helper', () => { + const template = `{{#ne steps 5 }}ne block{{else}}else block{{/ne}}`; + it('shoud render ne block', async () => { + const result = await useCase.execute({ + data: { steps: 6 }, + template, + }); + + expect(result).toEqual('ne block'); + }); + + it('shoud use strict check and render ne block', async () => { + const result = await useCase.execute({ + data: { steps: '5' }, + template, + }); + + expect(result).toEqual('ne block'); + }); + + it('shoud render alternative block', async () => { + const result = await useCase.execute({ + data: { steps: 5 }, + template, + }); + + expect(result).toEqual('else block'); + }); + }); }); diff --git a/packages/application-generic/src/usecases/compile-template/compile-template.usecase.ts b/packages/application-generic/src/usecases/compile-template/compile-template.usecase.ts index 1669e6c0d13..bd78462aa2c 100644 --- a/packages/application-generic/src/usecases/compile-template/compile-template.usecase.ts +++ b/packages/application-generic/src/usecases/compile-template/compile-template.usecase.ts @@ -158,6 +158,60 @@ Handlebars.registerHelper( } ); +Handlebars.registerHelper( + HandlebarHelpersEnum.GT, + function (arg1, arg2, options) { + // eslint-disable-next-line + // @ts-expect-error + return arg1 > arg2 ? options.fn(this) : options.inverse(this); + } +); + +Handlebars.registerHelper( + HandlebarHelpersEnum.GTE, + function (arg1, arg2, options) { + // eslint-disable-next-line + // @ts-expect-error + return arg1 >= arg2 ? options.fn(this) : options.inverse(this); + } +); + +Handlebars.registerHelper( + HandlebarHelpersEnum.LT, + function (arg1, arg2, options) { + // eslint-disable-next-line + // @ts-expect-error + return arg1 < arg2 ? options.fn(this) : options.inverse(this); + } +); + +Handlebars.registerHelper( + HandlebarHelpersEnum.LTE, + function (arg1, arg2, options) { + // eslint-disable-next-line + // @ts-expect-error + return arg1 <= arg2 ? options.fn(this) : options.inverse(this); + } +); + +Handlebars.registerHelper( + HandlebarHelpersEnum.EQ, + function (arg1, arg2, options) { + // eslint-disable-next-line + // @ts-expect-error + return arg1 === arg2 ? options.fn(this) : options.inverse(this); + } +); + +Handlebars.registerHelper( + HandlebarHelpersEnum.NE, + function (arg1, arg2, options) { + // eslint-disable-next-line + // @ts-expect-error + return arg1 !== arg2 ? options.fn(this) : options.inverse(this); + } +); + @Injectable() export class CompileTemplate { async execute(command: CompileTemplateCommand): Promise { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1c025d869c..9a73594a527 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -233,7 +233,7 @@ importers: version: 6.1.13 ts-jest: specifier: 27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@16.11.7)(typescript@4.9.5) @@ -608,7 +608,7 @@ importers: version: 9.2.4 ts-jest: specifier: ^27.0.7 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-loader: specifier: ~9.4.0 version: 9.4.2(typescript@4.9.5)(webpack@5.78.0) @@ -629,13 +629,13 @@ importers: version: 4.6.2(react-dom@17.0.2)(react@17.0.2) '@babel/plugin-proposal-optional-chaining': specifier: ^7.20.7 - version: 7.21.0(@babel/core@7.23.2) + version: 7.21.0(@babel/core@7.24.4) '@babel/plugin-transform-react-display-name': specifier: ^7.18.6 - version: 7.18.6(@babel/core@7.23.2) + version: 7.18.6(@babel/core@7.24.4) '@babel/plugin-transform-runtime': specifier: ^7.23.2 - version: 7.23.2(@babel/core@7.23.2) + version: 7.23.2(@babel/core@7.24.4) '@cypress/react': specifier: ^7.0.3 version: 7.0.3(@types/react@17.0.53)(cypress@13.3.1)(react-dom@17.0.2)(react@17.0.2) @@ -916,6 +916,9 @@ importers: '@novu/ee-billing-web': specifier: ^0.24.1 version: link:../../enterprise/packages/billing-web + '@novu/ee-echo-web': + specifier: ^0.24.1 + version: link:../../enterprise/packages/web/echo '@novu/ee-translation-web': specifier: ^0.24.1 version: link:../../enterprise/packages/translation-web @@ -925,13 +928,13 @@ importers: version: 7.12.1 '@babel/preset-env': specifier: ^7.23.2 - version: 7.23.2(@babel/core@7.23.2) + version: 7.23.2(@babel/core@7.24.4) '@babel/preset-react': specifier: ^7.13.13 - version: 7.18.6(@babel/core@7.23.2) + version: 7.18.6(@babel/core@7.24.4) '@babel/preset-typescript': specifier: ^7.13.0 - version: 7.21.4(@babel/core@7.23.2) + version: 7.21.4(@babel/core@7.24.4) '@babel/runtime': specifier: ^7.20.13 version: 7.21.0 @@ -944,6 +947,9 @@ importers: '@pandacss/dev': specifier: ^0.34.0 version: 0.34.0(jsdom@24.0.0)(typescript@4.9.5) + '@playwright/test': + specifier: ^1.42.1 + version: 1.42.1 '@storybook/addon-actions': specifier: ^7.4.2 version: 7.4.2(@types/react-dom@17.0.19)(@types/react@17.0.53)(react-dom@17.0.2)(react@17.0.2) @@ -961,13 +967,13 @@ importers: version: 7.4.2 '@storybook/preset-create-react-app': specifier: ^7.4.2 - version: 7.4.2(@babel/core@7.23.2)(react-refresh@0.14.0)(react-scripts@5.0.1)(typescript@4.9.5)(webpack-dev-server@4.11.1)(webpack@5.78.0) + version: 7.4.2(@babel/core@7.24.4)(react-refresh@0.14.0)(react-scripts@5.0.1)(typescript@4.9.5)(webpack-dev-server@4.11.1)(webpack@5.78.0) '@storybook/react': specifier: ^7.4.2 version: 7.4.2(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) '@storybook/react-webpack5': specifier: ^7.4.2 - version: 7.4.2(@babel/core@7.23.2)(@swc/core@1.3.49)(@types/react-dom@17.0.19)(@types/react@17.0.53)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-dev-server@4.11.1) + version: 7.4.2(@babel/core@7.24.4)(@swc/core@1.3.49)(@types/react-dom@17.0.19)(@types/react@17.0.53)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-dev-server@4.11.1) '@testing-library/jest-dom': specifier: ^4.2.4 version: 4.2.4 @@ -1154,7 +1160,7 @@ importers: version: 6.3.3 ts-jest: specifier: ^27.0.7 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-loader: specifier: ~9.4.0 version: 9.4.2(typescript@4.9.5)(webpack@5.78.0) @@ -1181,7 +1187,7 @@ importers: version: 11.10.6(@emotion/react@11.10.6)(@types/react@17.0.62)(react@17.0.2) '@mantine/core': specifier: 4.2.12 - version: 4.2.12(@babel/core@7.23.2)(@mantine/hooks@4.2.12)(@types/react@17.0.62)(react-dom@17.0.2)(react@17.0.2) + version: 4.2.12(@babel/core@7.24.4)(@mantine/hooks@4.2.12)(@types/react@17.0.62)(react-dom@17.0.2)(react@17.0.2) '@mantine/hooks': specifier: 4.2.12 version: 4.2.12(react@17.0.2) @@ -1248,25 +1254,25 @@ importers: devDependencies: '@babel/plugin-proposal-optional-chaining': specifier: ^7.20.7 - version: 7.21.0(@babel/core@7.23.2) + version: 7.21.0(@babel/core@7.24.4) '@babel/plugin-transform-react-display-name': specifier: ^7.18.6 - version: 7.22.5(@babel/core@7.23.2) + version: 7.22.5(@babel/core@7.24.4) '@babel/plugin-transform-runtime': specifier: ^7.23.2 - version: 7.23.2(@babel/core@7.23.2) + version: 7.23.2(@babel/core@7.24.4) '@babel/polyfill': specifier: ^7.12.1 version: 7.12.1 '@babel/preset-env': specifier: ^7.23.2 - version: 7.23.2(@babel/core@7.23.2) + version: 7.23.2(@babel/core@7.24.4) '@babel/preset-react': specifier: ^7.13.13 - version: 7.22.15(@babel/core@7.23.2) + version: 7.22.15(@babel/core@7.24.4) '@babel/preset-typescript': specifier: ^7.13.0 - version: 7.21.4(@babel/core@7.23.2) + version: 7.21.4(@babel/core@7.24.4) '@babel/runtime': specifier: ^7.20.13 version: 7.21.0 @@ -2341,6 +2347,67 @@ importers: specifier: 4.9.5 version: 4.9.5 + enterprise/packages/web/echo: + dependencies: + '@mantine/core': + specifier: ^5.7.1 + version: 5.10.5(@emotion/react@11.10.6)(@mantine/hooks@5.10.5)(@types/react@17.0.62)(react-dom@17.0.2)(react@17.0.2) + '@mantine/hooks': + specifier: ^5.7.1 + version: 5.10.5(react@17.0.2) + '@novu/design-system': + specifier: ^0.24.1 + version: link:../../../../libs/design-system + '@novu/shared-web': + specifier: ^0.24.1 + version: link:../../../../libs/shared-web + '@rjsf/core': + specifier: ^5.17.1 + version: 5.17.1(@rjsf/utils@5.17.1)(react@17.0.2) + '@rjsf/validator-ajv8': + specifier: ^5.17.1 + version: 5.17.1(@rjsf/utils@5.17.1) + '@tanstack/react-query': + specifier: ^4.20.4 + version: 4.29.1(react-dom@17.0.2)(react@17.0.2) + react-router-dom: + specifier: 6.2.2 + version: 6.2.2(react-dom@17.0.2)(react@17.0.2) + tslib: + specifier: ^2.3.1 + version: 2.6.2 + devDependencies: + '@types/node': + specifier: ^18.11.12 + version: 18.18.5 + '@types/react': + specifier: ^17.0.0 + version: 17.0.62 + '@types/react-dom': + specifier: ^17.0.0 + version: 17.0.20 + eslint: + specifier: ^8.33.0 + version: 8.51.0 + eslint-plugin-react-hooks: + specifier: ^4.4.0 + version: 4.6.0(eslint@8.51.0) + react: + specifier: ^17.0.1 + version: 17.0.2 + react-dom: + specifier: ^17.0.1 + version: 17.0.2(react@17.0.2) + rimraf: + specifier: ^3.0.2 + version: 3.0.2 + ts-loader: + specifier: ~9.4.0 + version: 9.4.2(typescript@4.9.5)(webpack@5.78.0) + typescript: + specifier: 4.9.5 + version: 4.9.5 + libs/dal: dependencies: '@aws-sdk/client-s3': @@ -2527,7 +2594,7 @@ importers: version: 7.4.2(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) '@storybook/react-webpack5': specifier: ^7.4.2 - version: 7.4.2(@babel/core@7.23.2)(@swc/core@1.3.49)(@types/react-dom@17.0.20)(@types/react@17.0.62)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) + version: 7.4.2(@babel/core@7.24.4)(@swc/core@1.3.49)(@types/react-dom@17.0.20)(@types/react@17.0.62)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) '@storybook/theming': specifier: ^7.4.2 version: 7.4.2(react-dom@17.0.2)(react@17.0.2) @@ -2699,7 +2766,7 @@ importers: version: 0.8.5 ts-jest: specifier: ^27.1.3 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -3313,7 +3380,7 @@ importers: version: 9.2.4 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -3414,7 +3481,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) typedoc: specifier: ^0.24.0 version: 0.24.6(typescript@4.9.5) @@ -3439,10 +3506,10 @@ importers: devDependencies: '@babel/preset-env': specifier: ^7.23.2 - version: 7.23.2(@babel/core@7.23.2) + version: 7.23.2(@babel/core@7.24.4) '@babel/preset-typescript': specifier: ^7.13.0 - version: 7.21.4(@babel/core@7.23.2) + version: 7.21.4(@babel/core@7.24.4) '@types/jest': specifier: ^29.2.3 version: 29.5.0 @@ -3457,7 +3524,7 @@ importers: version: 29.5.0 ts-jest: specifier: ^29.0.3 - version: 29.1.0(@babel/core@7.23.2)(jest@29.5.0)(typescript@4.9.5) + version: 29.1.0(@babel/core@7.24.4)(jest@29.5.0)(typescript@4.9.5) typedoc: specifier: ^0.24.0 version: 0.24.6(typescript@4.9.5) @@ -3509,7 +3576,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -3582,7 +3649,7 @@ importers: version: 0.0.0 ts-jest: specifier: ^29.1.2 - version: 29.1.2(@babel/core@7.23.2)(jest@29.7.0)(typescript@4.9.5) + version: 29.1.2(@babel/core@7.24.4)(jest@29.7.0)(typescript@4.9.5) typedoc: specifier: ^0.24.0 version: 0.24.6(typescript@4.9.5) @@ -3646,13 +3713,13 @@ importers: devDependencies: '@babel/preset-env': specifier: ^7.23.2 - version: 7.23.2(@babel/core@7.23.2) + version: 7.23.2(@babel/core@7.24.4) '@babel/preset-react': specifier: ^7.13.13 - version: 7.22.15(@babel/core@7.23.2) + version: 7.22.15(@babel/core@7.24.4) '@babel/preset-typescript': specifier: ^7.13.0 - version: 7.21.4(@babel/core@7.23.2) + version: 7.21.4(@babel/core@7.24.4) '@storybook/addon-actions': specifier: ^7.4.2 version: 7.4.2(@types/react-dom@17.0.19)(@types/react@17.0.53)(react-dom@17.0.2)(react@17.0.2) @@ -3673,7 +3740,7 @@ importers: version: 7.4.2(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) '@storybook/react-webpack5': specifier: ^7.4.2 - version: 7.4.2(@babel/core@7.23.2)(@swc/core@1.3.49)(@types/react-dom@17.0.19)(@types/react@17.0.53)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-cli@5.1.4) + version: 7.4.2(@babel/core@7.24.4)(@swc/core@1.3.49)(@types/react-dom@17.0.19)(@types/react@17.0.53)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-cli@5.1.4) '@testing-library/dom': specifier: ^9.3.0 version: 9.3.0 @@ -3706,7 +3773,7 @@ importers: version: 8.8.2 babel-loader: specifier: ^8.2.4 - version: 8.3.0(@babel/core@7.23.2)(webpack@5.82.1) + version: 8.3.0(@babel/core@7.24.4)(webpack@5.82.1) compression-webpack-plugin: specifier: ^10.0.0 version: 10.0.0(webpack@5.82.1) @@ -3739,7 +3806,7 @@ importers: version: 5.3.9(@swc/core@1.3.49)(esbuild@0.18.20)(webpack@5.82.1) ts-jest: specifier: ^29.0.3 - version: 29.1.0(@babel/core@7.23.2)(esbuild@0.18.20)(jest@29.5.0)(typescript@4.9.5) + version: 29.1.0(@babel/core@7.24.4)(esbuild@0.18.20)(jest@29.5.0)(typescript@4.9.5) ts-loader: specifier: ~9.4.0 version: 9.4.2(typescript@4.9.5)(webpack@5.82.1) @@ -3973,7 +4040,7 @@ importers: version: 0.0.0 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) typedoc: specifier: ^0.24.0 version: 0.24.6(typescript@4.9.5) @@ -4016,7 +4083,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4065,7 +4132,7 @@ importers: version: 2.8.7 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4111,7 +4178,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4154,7 +4221,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4197,7 +4264,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4246,7 +4313,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4289,7 +4356,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4344,7 +4411,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4399,7 +4466,7 @@ importers: version: 2.8.7 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4445,7 +4512,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4497,7 +4564,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4543,7 +4610,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4595,7 +4662,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4650,7 +4717,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4714,7 +4781,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4736,7 +4803,7 @@ importers: devDependencies: '@babel/preset-env': specifier: ^7.23.2 - version: 7.23.2(@babel/core@7.23.2) + version: 7.23.2(@babel/core@7.24.4) '@istanbuljs/nyc-config-typescript': specifier: ^1.0.1 version: 1.0.2(nyc@15.1.0) @@ -4778,7 +4845,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4824,7 +4891,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4867,7 +4934,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4910,7 +4977,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4956,7 +5023,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -4978,7 +5045,7 @@ importers: devDependencies: '@babel/preset-env': specifier: ^7.23.2 - version: 7.23.2(@babel/core@7.23.2) + version: 7.23.2(@babel/core@7.24.4) '@istanbuljs/nyc-config-typescript': specifier: ^1.0.1 version: 1.0.2(nyc@15.1.0) @@ -5014,7 +5081,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5069,7 +5136,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5115,7 +5182,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5167,7 +5234,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5222,7 +5289,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5283,7 +5350,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5341,7 +5408,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5387,7 +5454,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5445,7 +5512,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5497,7 +5564,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5540,7 +5607,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5583,7 +5650,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5635,7 +5702,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5693,7 +5760,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.7 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5751,7 +5818,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5809,7 +5876,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.7 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5855,7 +5922,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5910,7 +5977,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -5965,7 +6032,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6011,7 +6078,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6066,7 +6133,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6112,7 +6179,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6155,7 +6222,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6198,7 +6265,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6250,7 +6317,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6296,7 +6363,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6339,7 +6406,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6382,7 +6449,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6425,7 +6492,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6477,7 +6544,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6532,7 +6599,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6590,7 +6657,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6636,7 +6703,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6688,7 +6755,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6734,7 +6801,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6756,7 +6823,7 @@ importers: devDependencies: '@babel/preset-env': specifier: ^7.23.2 - version: 7.23.2(@babel/core@7.23.2) + version: 7.23.2(@babel/core@7.24.4) '@istanbuljs/nyc-config-typescript': specifier: ^1.0.1 version: 1.0.2(nyc@15.1.0) @@ -6792,7 +6859,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6847,7 +6914,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6905,7 +6972,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6960,7 +7027,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -6982,7 +7049,7 @@ importers: devDependencies: '@babel/preset-env': specifier: ^7.23.2 - version: 7.23.2(@babel/core@7.23.2) + version: 7.23.2(@babel/core@7.24.4) '@istanbuljs/nyc-config-typescript': specifier: ^1.0.1 version: 1.0.2(nyc@15.1.0) @@ -7018,7 +7085,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -7073,7 +7140,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^27.0.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -7119,7 +7186,7 @@ importers: version: 3.0.2 ts-jest: specifier: ~27.1.5 - version: 27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) + version: 27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5) ts-node: specifier: ~10.9.1 version: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) @@ -9908,7 +9975,6 @@ packages: dependencies: '@babel/highlight': 7.24.2 picocolors: 1.0.0 - dev: true /@babel/compat-data@7.23.2: resolution: {integrity: sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==} @@ -9917,7 +9983,6 @@ packages: /@babel/compat-data@7.24.4: resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} engines: {node: '>=6.9.0'} - dev: true /@babel/core@7.21.4: resolution: {integrity: sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==} @@ -10031,7 +10096,6 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true /@babel/eslint-parser@7.21.3(@babel/core@7.23.2)(eslint@8.51.0): resolution: {integrity: sha512-kfhmPimwo6k4P8zxNs8+T7yR44q1LdpsZdE1NkCsVlfiuTPRfnGgjaF8Qgug9q9Pou17u6wneYF0lDCZJATMFg==} @@ -10074,7 +10138,6 @@ packages: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 - dev: true /@babel/helper-annotate-as-pure@7.18.6: resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} @@ -10116,7 +10179,6 @@ packages: browserslist: 4.23.0 lru-cache: 5.1.1 semver: 6.3.1 - dev: true /@babel/helper-create-class-features-plugin@7.22.15(@babel/core@7.21.4): resolution: {integrity: sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==} @@ -10190,6 +10252,24 @@ packages: semver: 6.3.1 dev: true + /@babel/helper-create-class-features-plugin@7.22.15(@babel/core@7.24.4): + resolution: {integrity: sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-member-expression-to-functions': 7.22.15 + '@babel/helper-optimise-call-expression': 7.22.5 + '@babel/helper-replace-supers': 7.22.20(@babel/core@7.24.4) + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + semver: 6.3.1 + dev: true + /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.22.11): resolution: {integrity: sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==} engines: {node: '>=6.9.0'} @@ -10226,6 +10306,18 @@ packages: semver: 6.3.1 dev: true + /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.24.4): + resolution: {integrity: sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-annotate-as-pure': 7.22.5 + regexpu-core: 5.3.2 + semver: 6.3.1 + dev: true + /@babel/helper-define-polyfill-provider@0.4.3(@babel/core@7.22.11): resolution: {integrity: sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==} peerDependencies: @@ -10269,6 +10361,21 @@ packages: resolve: 1.22.2 transitivePeerDependencies: - supports-color + dev: true + + /@babel/helper-define-polyfill-provider@0.4.3(@babel/core@7.24.4): + resolution: {integrity: sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + debug: 4.3.4(supports-color@8.1.1) + lodash.debounce: 4.0.8 + resolve: 1.22.2 + transitivePeerDependencies: + - supports-color /@babel/helper-environment-visitor@7.22.20: resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} @@ -10334,6 +10441,20 @@ packages: '@babel/helper-validator-identifier': 7.22.20 dev: true + /@babel/helper-module-transforms@7.22.20(@babel/core@7.24.4): + resolution: {integrity: sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/helper-validator-identifier': 7.22.20 + dev: true + /@babel/helper-module-transforms@7.23.0(@babel/core@7.22.11): resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==} engines: {node: '>=6.9.0'} @@ -10375,6 +10496,20 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-validator-identifier': 7.22.20 + /@babel/helper-module-transforms@7.23.0(@babel/core@7.24.4): + resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/helper-validator-identifier': 7.22.20 + dev: true + /@babel/helper-module-transforms@7.23.3(@babel/core@7.24.4): resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} engines: {node: '>=6.9.0'} @@ -10387,7 +10522,6 @@ packages: '@babel/helper-simple-access': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-validator-identifier': 7.22.20 - dev: true /@babel/helper-optimise-call-expression@7.22.5: resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} @@ -10440,6 +10574,18 @@ packages: '@babel/helper-wrap-function': 7.22.20 dev: true + /@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.24.4): + resolution: {integrity: sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-wrap-function': 7.22.20 + dev: true + /@babel/helper-replace-supers@7.22.20(@babel/core@7.21.4): resolution: {integrity: sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==} engines: {node: '>=6.9.0'} @@ -10488,6 +10634,18 @@ packages: '@babel/helper-optimise-call-expression': 7.22.5 dev: true + /@babel/helper-replace-supers@7.22.20(@babel/core@7.24.4): + resolution: {integrity: sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-member-expression-to-functions': 7.22.15 + '@babel/helper-optimise-call-expression': 7.22.5 + dev: true + /@babel/helper-simple-access@7.22.5: resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} @@ -10513,7 +10671,6 @@ packages: /@babel/helper-string-parser@7.24.1: resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-validator-identifier@7.22.20: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} @@ -10531,7 +10688,6 @@ packages: /@babel/helper-validator-option@7.23.5: resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-wrap-function@7.22.20: resolution: {integrity: sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==} @@ -10572,7 +10728,6 @@ packages: '@babel/types': 7.24.0 transitivePeerDependencies: - supports-color - dev: true /@babel/highlight@7.22.13: resolution: {integrity: sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==} @@ -10590,7 +10745,6 @@ packages: chalk: 2.4.2 js-tokens: 4.0.0 picocolors: 1.0.0 - dev: true /@babel/parser@7.22.16: resolution: {integrity: sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==} @@ -10619,7 +10773,6 @@ packages: hasBin: true dependencies: '@babel/types': 7.24.0 - dev: true /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.22.15(@babel/core@7.22.11): resolution: {integrity: sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==} @@ -10651,6 +10804,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.22.15(@babel/core@7.24.4): + resolution: {integrity: sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.22.15(@babel/core@7.22.11): resolution: {integrity: sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==} engines: {node: '>=6.9.0'} @@ -10687,6 +10850,18 @@ packages: '@babel/plugin-transform-optional-chaining': 7.23.0(@babel/core@7.23.2) dev: true + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.22.15(@babel/core@7.24.4): + resolution: {integrity: sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-transform-optional-chaining': 7.23.0(@babel/core@7.24.4) + dev: true + /@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.22.9): resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} engines: {node: '>=6.9.0'} @@ -10771,6 +10946,18 @@ packages: '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.2) + dev: true + + /@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.24.4): + resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.4) /@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.23.2): resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} @@ -10824,6 +11011,15 @@ packages: '@babel/core': 7.23.2 dev: true + /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.4): + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + dev: true + /@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.22.9): resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} engines: {node: '>=4'} @@ -10872,6 +11068,15 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.4): + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.21.4): resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: @@ -10926,6 +11131,15 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.4): + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.22.11): resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} engines: {node: '>=6.9.0'} @@ -10956,6 +11170,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.4): + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-decorators@7.21.0(@babel/core@7.23.2): resolution: {integrity: sha512-tIoPpGBR8UuM4++ccWN3gifhVvQu7ZizuR1fklhRJrd5ewgbkUS+0KVFeWWxELtn18NTLoW32XV7zyOgIAiz+w==} engines: {node: '>=6.9.0'} @@ -11003,6 +11227,15 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.4): + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.22.11): resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} peerDependencies: @@ -11030,6 +11263,15 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.4): + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-flow@7.22.5(@babel/core@7.23.2): resolution: {integrity: sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ==} engines: {node: '>=6.9.0'} @@ -11040,6 +11282,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-flow@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-9RdCl0i+q0QExayk2nOS7853w08yLucnnPML6EN9S8fgMPVtdLDCdx/cOQ/i44Lb9UeQX9A35yaqBBOMMZxPxQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-import-assertions@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==} engines: {node: '>=6.9.0'} @@ -11070,6 +11322,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-import-assertions@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-import-attributes@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==} engines: {node: '>=6.9.0'} @@ -11100,6 +11362,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-import-attributes@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.21.4): resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: @@ -11136,6 +11408,15 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.4): + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.21.4): resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: @@ -11172,6 +11453,15 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.4): + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.21.4): resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==} engines: {node: '>=6.9.0'} @@ -11192,6 +11482,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.21.4): resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: @@ -11228,6 +11528,15 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.4): + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.21.4): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: @@ -11264,6 +11573,15 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.4): + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.21.4): resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: @@ -11300,6 +11618,15 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.4): + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.21.4): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: @@ -11336,6 +11663,15 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.4): + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.21.4): resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: @@ -11372,6 +11708,15 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.4): + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.21.4): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: @@ -11406,6 +11751,15 @@ packages: dependencies: '@babel/core': 7.23.2 '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.4): + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.22.11): resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} @@ -11437,6 +11791,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.4): + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.21.4): resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} @@ -11477,13 +11841,23 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-typescript@7.21.4(@babel/core@7.23.2): + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.4): + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-typescript@7.21.4(@babel/core@7.24.4): resolution: {integrity: sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.22.5 dev: true @@ -11540,6 +11914,17 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.4): + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-arrow-functions@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==} engines: {node: '>=6.9.0'} @@ -11570,6 +11955,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-arrow-functions@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-async-generator-functions@7.23.2(@babel/core@7.22.11): resolution: {integrity: sha512-BBYVGxbDVHfoeXbOwcagAkOQAm9NxoTdMGfTqghu1GrvadSaw6iW3Je6IcL5PNOw8VwjxqBECXy50/iCQSY/lQ==} engines: {node: '>=6.9.0'} @@ -11609,6 +12004,19 @@ packages: '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-async-generator-functions@7.23.2(@babel/core@7.24.4): + resolution: {integrity: sha512-BBYVGxbDVHfoeXbOwcagAkOQAm9NxoTdMGfTqghu1GrvadSaw6iW3Je6IcL5PNOw8VwjxqBECXy50/iCQSY/lQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.4) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-async-to-generator@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==} engines: {node: '>=6.9.0'} @@ -11645,6 +12053,18 @@ packages: '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-async-to-generator@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-block-scoped-functions@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==} engines: {node: '>=6.9.0'} @@ -11675,6 +12095,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-block-scoped-functions@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-block-scoping@7.23.0(@babel/core@7.22.11): resolution: {integrity: sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==} engines: {node: '>=6.9.0'} @@ -11705,6 +12135,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-block-scoping@7.23.0(@babel/core@7.24.4): + resolution: {integrity: sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-class-properties@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==} engines: {node: '>=6.9.0'} @@ -11738,6 +12178,17 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-class-properties@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-class-static-block@7.22.11(@babel/core@7.22.11): resolution: {integrity: sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==} engines: {node: '>=6.9.0'} @@ -11774,6 +12225,18 @@ packages: '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-class-static-block@7.22.11(@babel/core@7.24.4): + resolution: {integrity: sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-classes@7.22.15(@babel/core@7.22.11): resolution: {integrity: sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==} engines: {node: '>=6.9.0'} @@ -11828,6 +12291,24 @@ packages: globals: 11.12.0 dev: true + /@babel/plugin-transform-classes@7.22.15(@babel/core@7.24.4): + resolution: {integrity: sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-optimise-call-expression': 7.22.5 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-replace-supers': 7.22.20(@babel/core@7.24.4) + '@babel/helper-split-export-declaration': 7.22.6 + globals: 11.12.0 + dev: true + /@babel/plugin-transform-computed-properties@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==} engines: {node: '>=6.9.0'} @@ -11861,6 +12342,17 @@ packages: '@babel/template': 7.22.15 dev: true + /@babel/plugin-transform-computed-properties@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/template': 7.22.15 + dev: true + /@babel/plugin-transform-destructuring@7.23.0(@babel/core@7.22.11): resolution: {integrity: sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==} engines: {node: '>=6.9.0'} @@ -11891,6 +12383,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-destructuring@7.23.0(@babel/core@7.24.4): + resolution: {integrity: sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-dotall-regex@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==} engines: {node: '>=6.9.0'} @@ -11924,6 +12426,17 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-dotall-regex@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-duplicate-keys@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==} engines: {node: '>=6.9.0'} @@ -11954,6 +12467,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-duplicate-keys@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-dynamic-import@7.22.11(@babel/core@7.22.11): resolution: {integrity: sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==} engines: {node: '>=6.9.0'} @@ -11987,6 +12510,17 @@ packages: '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-dynamic-import@7.22.11(@babel/core@7.24.4): + resolution: {integrity: sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-exponentiation-operator@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==} engines: {node: '>=6.9.0'} @@ -12020,6 +12554,17 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-exponentiation-operator@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-export-namespace-from@7.22.11(@babel/core@7.22.11): resolution: {integrity: sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==} engines: {node: '>=6.9.0'} @@ -12053,6 +12598,17 @@ packages: '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-export-namespace-from@7.22.11(@babel/core@7.24.4): + resolution: {integrity: sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-flow-strip-types@7.22.5(@babel/core@7.23.2): resolution: {integrity: sha512-tujNbZdxdG0/54g/oua8ISToaXTFBf8EnSb5PgQSciIXWOWKX3S4+JR7ZE9ol8FZwf9kxitzkGQ+QWeov/mCiA==} engines: {node: '>=6.9.0'} @@ -12064,6 +12620,17 @@ packages: '@babel/plugin-syntax-flow': 7.22.5(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-flow-strip-types@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-tujNbZdxdG0/54g/oua8ISToaXTFBf8EnSb5PgQSciIXWOWKX3S4+JR7ZE9ol8FZwf9kxitzkGQ+QWeov/mCiA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-flow': 7.22.5(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-for-of@7.22.15(@babel/core@7.22.11): resolution: {integrity: sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==} engines: {node: '>=6.9.0'} @@ -12094,6 +12661,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-for-of@7.22.15(@babel/core@7.24.4): + resolution: {integrity: sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-function-name@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==} engines: {node: '>=6.9.0'} @@ -12130,6 +12707,18 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-function-name@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-json-strings@7.22.11(@babel/core@7.22.11): resolution: {integrity: sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==} engines: {node: '>=6.9.0'} @@ -12163,6 +12752,17 @@ packages: '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-json-strings@7.22.11(@babel/core@7.24.4): + resolution: {integrity: sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-literals@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==} engines: {node: '>=6.9.0'} @@ -12193,6 +12793,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-literals@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-logical-assignment-operators@7.22.11(@babel/core@7.22.11): resolution: {integrity: sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==} engines: {node: '>=6.9.0'} @@ -12226,6 +12836,17 @@ packages: '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-logical-assignment-operators@7.22.11(@babel/core@7.24.4): + resolution: {integrity: sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-member-expression-literals@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==} engines: {node: '>=6.9.0'} @@ -12256,6 +12877,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-member-expression-literals@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-modules-amd@7.23.0(@babel/core@7.22.11): resolution: {integrity: sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==} engines: {node: '>=6.9.0'} @@ -12289,6 +12920,17 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-modules-amd@7.23.0(@babel/core@7.24.4): + resolution: {integrity: sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-module-transforms': 7.23.0(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-modules-commonjs@7.22.15(@babel/core@7.23.2): resolution: {integrity: sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg==} engines: {node: '>=6.9.0'} @@ -12301,6 +12943,18 @@ packages: '@babel/helper-simple-access': 7.22.5 dev: true + /@babel/plugin-transform-modules-commonjs@7.22.15(@babel/core@7.24.4): + resolution: {integrity: sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-module-transforms': 7.22.20(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-simple-access': 7.22.5 + dev: true + /@babel/plugin-transform-modules-commonjs@7.23.0(@babel/core@7.22.11): resolution: {integrity: sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==} engines: {node: '>=6.9.0'} @@ -12337,6 +12991,18 @@ packages: '@babel/helper-simple-access': 7.22.5 dev: true + /@babel/plugin-transform-modules-commonjs@7.23.0(@babel/core@7.24.4): + resolution: {integrity: sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-module-transforms': 7.23.0(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-simple-access': 7.22.5 + dev: true + /@babel/plugin-transform-modules-systemjs@7.23.0(@babel/core@7.22.11): resolution: {integrity: sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==} engines: {node: '>=6.9.0'} @@ -12376,6 +13042,19 @@ packages: '@babel/helper-validator-identifier': 7.22.20 dev: true + /@babel/plugin-transform-modules-systemjs@7.23.0(@babel/core@7.24.4): + resolution: {integrity: sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-module-transforms': 7.23.0(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-validator-identifier': 7.22.20 + dev: true + /@babel/plugin-transform-modules-umd@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==} engines: {node: '>=6.9.0'} @@ -12409,6 +13088,17 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-modules-umd@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-module-transforms': 7.23.0(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==} engines: {node: '>=6.9.0'} @@ -12442,6 +13132,17 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-new-target@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==} engines: {node: '>=6.9.0'} @@ -12472,6 +13173,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-new-target@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-nullish-coalescing-operator@7.22.11(@babel/core@7.22.11): resolution: {integrity: sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==} engines: {node: '>=6.9.0'} @@ -12505,6 +13216,17 @@ packages: '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-nullish-coalescing-operator@7.22.11(@babel/core@7.24.4): + resolution: {integrity: sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-numeric-separator@7.22.11(@babel/core@7.22.11): resolution: {integrity: sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==} engines: {node: '>=6.9.0'} @@ -12538,6 +13260,17 @@ packages: '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-numeric-separator@7.22.11(@babel/core@7.24.4): + resolution: {integrity: sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-object-rest-spread@7.22.15(@babel/core@7.22.11): resolution: {integrity: sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==} engines: {node: '>=6.9.0'} @@ -12580,6 +13313,20 @@ packages: '@babel/plugin-transform-parameters': 7.22.15(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-object-rest-spread@7.22.15(@babel/core@7.24.4): + resolution: {integrity: sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.23.2 + '@babel/core': 7.24.4 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-transform-parameters': 7.22.15(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-object-super@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==} engines: {node: '>=6.9.0'} @@ -12613,6 +13360,17 @@ packages: '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-object-super@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-replace-supers': 7.22.20(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-optional-catch-binding@7.22.11(@babel/core@7.22.11): resolution: {integrity: sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==} engines: {node: '>=6.9.0'} @@ -12646,6 +13404,17 @@ packages: '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-optional-catch-binding@7.22.11(@babel/core@7.24.4): + resolution: {integrity: sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-optional-chaining@7.23.0(@babel/core@7.22.11): resolution: {integrity: sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==} engines: {node: '>=6.9.0'} @@ -12682,6 +13451,18 @@ packages: '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-optional-chaining@7.23.0(@babel/core@7.24.4): + resolution: {integrity: sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-parameters@7.22.15(@babel/core@7.22.11): resolution: {integrity: sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==} engines: {node: '>=6.9.0'} @@ -12712,6 +13493,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-parameters@7.22.15(@babel/core@7.24.4): + resolution: {integrity: sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-private-methods@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==} engines: {node: '>=6.9.0'} @@ -12745,6 +13536,17 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-private-methods@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-private-property-in-object@7.22.11(@babel/core@7.22.11): resolution: {integrity: sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==} engines: {node: '>=6.9.0'} @@ -12784,6 +13586,19 @@ packages: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-private-property-in-object@7.22.11(@babel/core@7.24.4): + resolution: {integrity: sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-property-literals@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==} engines: {node: '>=6.9.0'} @@ -12814,6 +13629,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-property-literals@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-react-constant-elements@7.21.3(@babel/core@7.23.2): resolution: {integrity: sha512-4DVcFeWe/yDYBLp0kBmOGFJ6N2UYg7coGid1gdxb4co62dy/xISDMaYBXBVXEDhfgMk7qkbcYiGtwd5Q/hwDDQ==} engines: {node: '>=6.9.0'} @@ -12824,13 +13649,13 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-transform-react-display-name@7.18.6(@babel/core@7.23.2): + /@babel/plugin-transform-react-display-name@7.18.6(@babel/core@7.24.4): resolution: {integrity: sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.20.2 /@babel/plugin-transform-react-display-name@7.22.5(@babel/core@7.23.2): @@ -12843,14 +13668,24 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-transform-react-jsx-development@7.18.6(@babel/core@7.23.2): + /@babel/plugin-transform-react-display-name@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-react-jsx-development@7.18.6(@babel/core@7.24.4): resolution: {integrity: sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.2 - '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.23.2) + '@babel/core': 7.24.4 + '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.24.4) dev: true /@babel/plugin-transform-react-jsx-development@7.22.5(@babel/core@7.23.2): @@ -12863,6 +13698,16 @@ packages: '@babel/plugin-transform-react-jsx': 7.22.15(@babel/core@7.23.2) dev: true + /@babel/plugin-transform-react-jsx-development@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/plugin-transform-react-jsx': 7.22.15(@babel/core@7.24.4) + dev: true + /@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.23.2): resolution: {integrity: sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==} engines: {node: '>=6.9.0'} @@ -12883,17 +13728,17 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-transform-react-jsx@7.21.0(@babel/core@7.23.2): + /@babel/plugin-transform-react-jsx@7.21.0(@babel/core@7.24.4): resolution: {integrity: sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.18.6 '@babel/helper-module-imports': 7.22.15 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.23.2) + '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.24.4) '@babel/types': 7.22.19 dev: true @@ -12911,13 +13756,27 @@ packages: '@babel/types': 7.23.0 dev: true - /@babel/plugin-transform-react-pure-annotations@7.18.6(@babel/core@7.23.2): + /@babel/plugin-transform-react-jsx@7.22.15(@babel/core@7.24.4): + resolution: {integrity: sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.24.4) + '@babel/types': 7.23.0 + dev: true + + /@babel/plugin-transform-react-pure-annotations@7.18.6(@babel/core@7.24.4): resolution: {integrity: sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 dev: true @@ -12933,6 +13792,17 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-react-pure-annotations@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-regenerator@7.22.10(@babel/core@7.22.11): resolution: {integrity: sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==} engines: {node: '>=6.9.0'} @@ -12966,6 +13836,17 @@ packages: regenerator-transform: 0.15.2 dev: true + /@babel/plugin-transform-regenerator@7.22.10(@babel/core@7.24.4): + resolution: {integrity: sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + regenerator-transform: 0.15.2 + dev: true + /@babel/plugin-transform-reserved-words@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==} engines: {node: '>=6.9.0'} @@ -12996,6 +13877,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-reserved-words@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-runtime@7.22.9(@babel/core@7.22.9): resolution: {integrity: sha512-9KjBH61AGJetCPYp/IEyLEp47SyybZb0nDRpBvmtEkm+rUIwxdlKpyNHI1TmsGkeuLclJdleQHRZ8XLBnnh8CQ==} engines: {node: '>=6.9.0'} @@ -13028,6 +13919,23 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color + dev: true + + /@babel/plugin-transform-runtime@7.23.2(@babel/core@7.24.4): + resolution: {integrity: sha512-XOntj6icgzMS58jPVtQpiuF6ZFWxQiJavISGx5KGjRj+3gqZr8+N6Kx+N9BApWzgS+DOjIZfXXj0ZesenOWDyA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + babel-plugin-polyfill-corejs2: 0.4.6(@babel/core@7.24.4) + babel-plugin-polyfill-corejs3: 0.8.5(@babel/core@7.24.4) + babel-plugin-polyfill-regenerator: 0.5.3(@babel/core@7.24.4) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color /@babel/plugin-transform-shorthand-properties@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==} @@ -13059,6 +13967,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-shorthand-properties@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-spread@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==} engines: {node: '>=6.9.0'} @@ -13092,6 +14010,17 @@ packages: '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 dev: true + /@babel/plugin-transform-spread@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + dev: true + /@babel/plugin-transform-sticky-regex@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==} engines: {node: '>=6.9.0'} @@ -13122,6 +14051,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-sticky-regex@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-template-literals@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==} engines: {node: '>=6.9.0'} @@ -13152,6 +14091,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-template-literals@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-typeof-symbol@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==} engines: {node: '>=6.9.0'} @@ -13182,6 +14131,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-typeof-symbol@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-typescript@7.21.3(@babel/core@7.21.4): resolution: {integrity: sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw==} engines: {node: '>=6.9.0'} @@ -13205,7 +14164,20 @@ packages: '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.23.2) '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-typescript': 7.21.4(@babel/core@7.23.2) + '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.23.2) + dev: true + + /@babel/plugin-transform-typescript@7.21.3(@babel/core@7.24.4): + resolution: {integrity: sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-typescript': 7.21.4(@babel/core@7.24.4) dev: true /@babel/plugin-transform-typescript@7.22.15(@babel/core@7.23.2): @@ -13251,6 +14223,16 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-unicode-escapes@7.22.10(@babel/core@7.24.4): + resolution: {integrity: sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-unicode-property-regex@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==} engines: {node: '>=6.9.0'} @@ -13284,6 +14266,17 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-unicode-property-regex@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-unicode-regex@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==} engines: {node: '>=6.9.0'} @@ -13317,6 +14310,17 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-unicode-regex@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/plugin-transform-unicode-sets-regex@7.22.5(@babel/core@7.22.11): resolution: {integrity: sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==} engines: {node: '>=6.9.0'} @@ -13350,6 +14354,17 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true + /@babel/plugin-transform-unicode-sets-regex@7.22.5(@babel/core@7.24.4): + resolution: {integrity: sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + /@babel/polyfill@7.12.1: resolution: {integrity: sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==} deprecated: 🚨 This package has been deprecated in favor of separate inclusion of a polyfill and regenerator-runtime (when needed). See the @babel/polyfill docs (https://babeljs.io/docs/en/babel-polyfill) for more information. @@ -13631,6 +14646,97 @@ packages: - supports-color dev: true + /@babel/preset-env@7.23.2(@babel/core@7.24.4): + resolution: {integrity: sha512-BW3gsuDD+rvHL2VO2SjAUNTBe5YrjsTiDyqamPDWY723na3/yPQ65X5oQkFVJZ0o50/2d+svm1rkPoJeR1KxVQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.23.2 + '@babel/core': 7.24.4 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-validator-option': 7.22.15 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.22.15(@babel/core@7.24.4) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.22.15(@babel/core@7.24.4) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.4) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.4) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.4) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.4) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-import-assertions': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-syntax-import-attributes': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.4) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.4) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.4) + '@babel/plugin-transform-arrow-functions': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-async-generator-functions': 7.23.2(@babel/core@7.24.4) + '@babel/plugin-transform-async-to-generator': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-block-scoped-functions': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-block-scoping': 7.23.0(@babel/core@7.24.4) + '@babel/plugin-transform-class-properties': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-class-static-block': 7.22.11(@babel/core@7.24.4) + '@babel/plugin-transform-classes': 7.22.15(@babel/core@7.24.4) + '@babel/plugin-transform-computed-properties': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-destructuring': 7.23.0(@babel/core@7.24.4) + '@babel/plugin-transform-dotall-regex': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-duplicate-keys': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-dynamic-import': 7.22.11(@babel/core@7.24.4) + '@babel/plugin-transform-exponentiation-operator': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-export-namespace-from': 7.22.11(@babel/core@7.24.4) + '@babel/plugin-transform-for-of': 7.22.15(@babel/core@7.24.4) + '@babel/plugin-transform-function-name': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-json-strings': 7.22.11(@babel/core@7.24.4) + '@babel/plugin-transform-literals': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-logical-assignment-operators': 7.22.11(@babel/core@7.24.4) + '@babel/plugin-transform-member-expression-literals': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-modules-amd': 7.23.0(@babel/core@7.24.4) + '@babel/plugin-transform-modules-commonjs': 7.23.0(@babel/core@7.24.4) + '@babel/plugin-transform-modules-systemjs': 7.23.0(@babel/core@7.24.4) + '@babel/plugin-transform-modules-umd': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-new-target': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-nullish-coalescing-operator': 7.22.11(@babel/core@7.24.4) + '@babel/plugin-transform-numeric-separator': 7.22.11(@babel/core@7.24.4) + '@babel/plugin-transform-object-rest-spread': 7.22.15(@babel/core@7.24.4) + '@babel/plugin-transform-object-super': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-optional-catch-binding': 7.22.11(@babel/core@7.24.4) + '@babel/plugin-transform-optional-chaining': 7.23.0(@babel/core@7.24.4) + '@babel/plugin-transform-parameters': 7.22.15(@babel/core@7.24.4) + '@babel/plugin-transform-private-methods': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-private-property-in-object': 7.22.11(@babel/core@7.24.4) + '@babel/plugin-transform-property-literals': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-regenerator': 7.22.10(@babel/core@7.24.4) + '@babel/plugin-transform-reserved-words': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-shorthand-properties': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-spread': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-sticky-regex': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-template-literals': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-typeof-symbol': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-unicode-escapes': 7.22.10(@babel/core@7.24.4) + '@babel/plugin-transform-unicode-property-regex': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-unicode-regex': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-unicode-sets-regex': 7.22.5(@babel/core@7.24.4) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.4) + '@babel/types': 7.23.0 + babel-plugin-polyfill-corejs2: 0.4.6(@babel/core@7.24.4) + babel-plugin-polyfill-corejs3: 0.8.5(@babel/core@7.24.4) + babel-plugin-polyfill-regenerator: 0.5.3(@babel/core@7.24.4) + core-js-compat: 3.32.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/preset-flow@7.22.15(@babel/core@7.23.2): resolution: {integrity: sha512-dB5aIMqpkgbTfN5vDdTRPzjqtWiZcRESNR88QYnoPR+bmdYoluOzMX9tQerTv0XzSgZYctPfO1oc0N5zdog1ew==} engines: {node: '>=6.9.0'} @@ -13643,6 +14749,18 @@ packages: '@babel/plugin-transform-flow-strip-types': 7.22.5(@babel/core@7.23.2) dev: true + /@babel/preset-flow@7.22.15(@babel/core@7.24.4): + resolution: {integrity: sha512-dB5aIMqpkgbTfN5vDdTRPzjqtWiZcRESNR88QYnoPR+bmdYoluOzMX9tQerTv0XzSgZYctPfO1oc0N5zdog1ew==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-validator-option': 7.22.15 + '@babel/plugin-transform-flow-strip-types': 7.22.5(@babel/core@7.24.4) + dev: true + /@babel/preset-modules@0.1.5(@babel/core@7.22.9): resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==} peerDependencies: @@ -13678,19 +14796,30 @@ packages: esutils: 2.0.3 dev: true - /@babel/preset-react@7.18.6(@babel/core@7.23.2): + /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.4): + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/types': 7.23.0 + esutils: 2.0.3 + dev: true + + /@babel/preset-react@7.18.6(@babel/core@7.24.4): resolution: {integrity: sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.20.2 '@babel/helper-validator-option': 7.21.0 - '@babel/plugin-transform-react-display-name': 7.18.6(@babel/core@7.23.2) - '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.23.2) - '@babel/plugin-transform-react-jsx-development': 7.18.6(@babel/core@7.23.2) - '@babel/plugin-transform-react-pure-annotations': 7.18.6(@babel/core@7.23.2) + '@babel/plugin-transform-react-display-name': 7.18.6(@babel/core@7.24.4) + '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.24.4) + '@babel/plugin-transform-react-jsx-development': 7.18.6(@babel/core@7.24.4) + '@babel/plugin-transform-react-pure-annotations': 7.18.6(@babel/core@7.24.4) dev: true /@babel/preset-react@7.22.15(@babel/core@7.23.2): @@ -13708,6 +14837,21 @@ packages: '@babel/plugin-transform-react-pure-annotations': 7.22.5(@babel/core@7.23.2) dev: true + /@babel/preset-react@7.22.15(@babel/core@7.24.4): + resolution: {integrity: sha512-Csy1IJ2uEh/PecCBXXoZGAZBeCATTuePzCSB7dLYWS0vOEj6CNpjxIhW4duWwZodBNueH7QO14WbGn8YyeuN9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-validator-option': 7.22.15 + '@babel/plugin-transform-react-display-name': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-react-jsx': 7.22.15(@babel/core@7.24.4) + '@babel/plugin-transform-react-jsx-development': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-react-pure-annotations': 7.22.5(@babel/core@7.24.4) + dev: true + /@babel/preset-typescript@7.21.4(@babel/core@7.23.2): resolution: {integrity: sha512-sMLNWY37TCdRH/bJ6ZeeOH1nPuanED7Ai9Y/vH31IPqalioJ6ZNFUWONsakhv4r4n+I6gm5lmoE0olkgib/j/A==} engines: {node: '>=6.9.0'} @@ -13722,6 +14866,20 @@ packages: '@babel/plugin-transform-typescript': 7.21.3(@babel/core@7.23.2) dev: true + /@babel/preset-typescript@7.21.4(@babel/core@7.24.4): + resolution: {integrity: sha512-sMLNWY37TCdRH/bJ6ZeeOH1nPuanED7Ai9Y/vH31IPqalioJ6ZNFUWONsakhv4r4n+I6gm5lmoE0olkgib/j/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-validator-option': 7.22.15 + '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-modules-commonjs': 7.22.15(@babel/core@7.24.4) + '@babel/plugin-transform-typescript': 7.21.3(@babel/core@7.24.4) + dev: true + /@babel/preset-typescript@7.23.2(@babel/core@7.23.2): resolution: {integrity: sha512-u4UJc1XsS1GhIGteM8rnGiIvf9rJpiVgMEeCnwlLA7WJPC+jcXWJAGxYmeqs5hOZD8BbAfnV5ezBOxQbb4OUxA==} engines: {node: '>=6.9.0'} @@ -13804,7 +14962,6 @@ packages: '@babel/code-frame': 7.24.2 '@babel/parser': 7.24.4 '@babel/types': 7.24.0 - dev: true /@babel/traverse@7.23.2: resolution: {integrity: sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==} @@ -13839,7 +14996,6 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: true /@babel/types@7.22.19: resolution: {integrity: sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==} @@ -13864,7 +15020,6 @@ packages: '@babel/helper-string-parser': 7.24.1 '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 - dev: true /@bandwidth/messaging@4.1.3: resolution: {integrity: sha512-cc1qLocHGxxqV7YNGOBxt6VhO+iGLfZnIq2htMP/xCgGOHqCtOVqHlQs80AETIMNEClXapShvn4TQrakx2h1/A==} @@ -16301,7 +17456,7 @@ packages: react: 17.0.2 dev: false - /@emotion/react@11.7.1(@babel/core@7.23.2)(@types/react@17.0.62)(react@17.0.2): + /@emotion/react@11.7.1(@babel/core@7.24.4)(@types/react@17.0.62)(react@17.0.2): resolution: {integrity: sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==} peerDependencies: '@babel/core': ^7.0.0 @@ -16313,7 +17468,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 '@babel/runtime': 7.23.2 '@emotion/cache': 11.10.7 '@emotion/serialize': 1.1.1 @@ -18596,7 +19751,6 @@ packages: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/trace-mapping': 0.3.25 - dev: true /@jridgewell/resolve-uri@3.1.0: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} @@ -18613,7 +19767,6 @@ packages: /@jridgewell/set-array@1.2.1: resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} - dev: true /@jridgewell/source-map@0.3.3: resolution: {integrity: sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==} @@ -18644,7 +19797,6 @@ packages: dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 - dev: true /@jridgewell/trace-mapping@0.3.9: resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -19423,7 +20575,7 @@ packages: - debug dev: false - /@mantine/core@4.2.12(@babel/core@7.23.2)(@mantine/hooks@4.2.12)(@types/react@17.0.62)(react-dom@17.0.2)(react@17.0.2): + /@mantine/core@4.2.12(@babel/core@7.24.4)(@mantine/hooks@4.2.12)(@types/react@17.0.62)(react-dom@17.0.2)(react@17.0.2): resolution: {integrity: sha512-PZcVUvcSZiZmLR1moKBJFdFIh6a4C+TE2ao91kzTAlH5Qb8t/V3ONbfPk3swHoYr7OSLJQM8vZ7UD5sFDiq0/g==} peerDependencies: '@mantine/hooks': 4.2.12 @@ -19431,7 +20583,7 @@ packages: react-dom: '>=16.8.0' dependencies: '@mantine/hooks': 4.2.12(react@17.0.2) - '@mantine/styles': 4.2.12(@babel/core@7.23.2)(@types/react@17.0.62)(react-dom@17.0.2)(react@17.0.2) + '@mantine/styles': 4.2.12(@babel/core@7.24.4)(@types/react@17.0.62)(react-dom@17.0.2)(react@17.0.2) '@popperjs/core': 2.11.7 '@radix-ui/react-scroll-area': 0.1.4(react@17.0.2) react: 17.0.2 @@ -19572,14 +20724,14 @@ packages: react-dom: 17.0.2(react@17.0.2) dev: false - /@mantine/styles@4.2.12(@babel/core@7.23.2)(@types/react@17.0.62)(react-dom@17.0.2)(react@17.0.2): + /@mantine/styles@4.2.12(@babel/core@7.24.4)(@types/react@17.0.62)(react-dom@17.0.2)(react@17.0.2): resolution: {integrity: sha512-9q1DzW0UNW/ORMGLHfN2XABOSEm0ZQebhNlLD757R6OQouoLuUf9elUwgGOXSyogMlsAYoy84XbJ3ZbbTm4YCA==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: '@emotion/cache': 11.7.1 - '@emotion/react': 11.7.1(@babel/core@7.23.2)(@types/react@17.0.62)(react@17.0.2) + '@emotion/react': 11.7.1(@babel/core@7.24.4)(@types/react@17.0.62)(react@17.0.2) '@emotion/serialize': 1.0.2 '@emotion/utils': 1.0.0 clsx: 1.2.1 @@ -22800,6 +23952,14 @@ packages: tslib: 2.6.2 dev: true + /@playwright/test@1.42.1: + resolution: {integrity: sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==} + engines: {node: '>=16'} + hasBin: true + dependencies: + playwright: 1.42.1 + dev: true + /@plunk/node@2.0.0: resolution: {integrity: sha512-53lgots3fWGAo1QdS18BdEpJl7A29O1F9rYVn/7DfJ07SpJ1ZlzUeeWGVrWGL7PRRZb4a9Tw7Tt8Wnw0Xorhjg==} dependencies: @@ -29331,13 +30491,13 @@ packages: /@storybook/postinstall@7.4.2: resolution: {integrity: sha512-L9r14KqS87HPyXw0S3pK2X29ckel/4sdBSmy9nVF8n/ADafKE0pSLKB935VL0+88eMx06aT32SMcQoqjubGKWw==} - /@storybook/preset-create-react-app@7.4.2(@babel/core@7.23.2)(react-refresh@0.14.0)(react-scripts@5.0.1)(typescript@4.9.5)(webpack-dev-server@4.11.1)(webpack@5.78.0): + /@storybook/preset-create-react-app@7.4.2(@babel/core@7.24.4)(react-refresh@0.14.0)(react-scripts@5.0.1)(typescript@4.9.5)(webpack-dev-server@4.11.1)(webpack@5.78.0): resolution: {integrity: sha512-rHRaiWmNAFXVHlRBG4iQE0Vsg3n4ZUyRWqddV2NuqZnHYQYUP07Rp0c3TFigGeTqF/gNbj8rTBDawcwpc8VkqQ==} peerDependencies: '@babel/core': '*' react-scripts: '>=5.0.0' dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(react-refresh@0.14.0)(webpack-dev-server@4.11.1)(webpack@5.78.0) '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@4.9.5)(webpack@5.78.0) '@storybook/types': 7.4.2 @@ -29359,7 +30519,7 @@ packages: - webpack-plugin-serve dev: true - /@storybook/preset-react-webpack@7.4.2(@babel/core@7.23.2)(@swc/core@1.3.49)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-cli@5.1.4): + /@storybook/preset-react-webpack@7.4.2(@babel/core@7.24.4)(@swc/core@1.3.49)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-cli@5.1.4): resolution: {integrity: sha512-CWWiwZa3/0zHnc6zLvI9Sgj12gJDTktZO87/gfwq2VfbWqAEUYsKs6NE4Pm0Yg9O4/IG8DHoHIB+bTNlLp/lCA==} engines: {node: '>=16.0.0'} peerDependencies: @@ -29373,9 +30533,9 @@ packages: typescript: optional: true dependencies: - '@babel/core': 7.23.2 - '@babel/preset-flow': 7.22.15(@babel/core@7.23.2) - '@babel/preset-react': 7.22.15(@babel/core@7.23.2) + '@babel/core': 7.24.4 + '@babel/preset-flow': 7.22.15(@babel/core@7.24.4) + '@babel/preset-react': 7.22.15(@babel/core@7.24.4) '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(react-refresh@0.11.0)(webpack-dev-server@4.11.1)(webpack@5.78.0) '@storybook/core-webpack': 7.4.2 '@storybook/docs-tools': 7.4.2 @@ -29408,7 +30568,7 @@ packages: - webpack-plugin-serve dev: true - /@storybook/preset-react-webpack@7.4.2(@babel/core@7.23.2)(@swc/core@1.3.49)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-dev-server@4.11.1): + /@storybook/preset-react-webpack@7.4.2(@babel/core@7.24.4)(@swc/core@1.3.49)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-dev-server@4.11.1): resolution: {integrity: sha512-CWWiwZa3/0zHnc6zLvI9Sgj12gJDTktZO87/gfwq2VfbWqAEUYsKs6NE4Pm0Yg9O4/IG8DHoHIB+bTNlLp/lCA==} engines: {node: '>=16.0.0'} peerDependencies: @@ -29422,9 +30582,9 @@ packages: typescript: optional: true dependencies: - '@babel/core': 7.23.2 - '@babel/preset-flow': 7.22.15(@babel/core@7.23.2) - '@babel/preset-react': 7.22.15(@babel/core@7.23.2) + '@babel/core': 7.24.4 + '@babel/preset-flow': 7.22.15(@babel/core@7.24.4) + '@babel/preset-react': 7.22.15(@babel/core@7.24.4) '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(react-refresh@0.11.0)(webpack-dev-server@4.11.1)(webpack@5.78.0) '@storybook/core-webpack': 7.4.2 '@storybook/docs-tools': 7.4.2 @@ -29545,7 +30705,7 @@ packages: react: 17.0.2 react-dom: 17.0.2(react@17.0.2) - /@storybook/react-webpack5@7.4.2(@babel/core@7.23.2)(@swc/core@1.3.49)(@types/react-dom@17.0.19)(@types/react@17.0.53)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-cli@5.1.4): + /@storybook/react-webpack5@7.4.2(@babel/core@7.24.4)(@swc/core@1.3.49)(@types/react-dom@17.0.19)(@types/react@17.0.53)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-cli@5.1.4): resolution: {integrity: sha512-pnl11MYKM3jRmHQz2dSnEDfDiApdH7ys3zH/FjImsTK6S8etMKlxGnZ58Puxj05qvrBRgpxnQSL+ZazfrEX/6w==} engines: {node: '>=16.0.0'} peerDependencies: @@ -29559,9 +30719,9 @@ packages: typescript: optional: true dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 '@storybook/builder-webpack5': 7.4.2(@types/react-dom@17.0.19)(@types/react@17.0.53)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-cli@5.1.4) - '@storybook/preset-react-webpack': 7.4.2(@babel/core@7.23.2)(@swc/core@1.3.49)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-cli@5.1.4) + '@storybook/preset-react-webpack': 7.4.2(@babel/core@7.24.4)(@swc/core@1.3.49)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-cli@5.1.4) '@storybook/react': 7.4.2(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) '@types/node': 16.11.7 react: 17.0.2 @@ -29585,7 +30745,7 @@ packages: - webpack-plugin-serve dev: true - /@storybook/react-webpack5@7.4.2(@babel/core@7.23.2)(@swc/core@1.3.49)(@types/react-dom@17.0.19)(@types/react@17.0.53)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-dev-server@4.11.1): + /@storybook/react-webpack5@7.4.2(@babel/core@7.24.4)(@swc/core@1.3.49)(@types/react-dom@17.0.19)(@types/react@17.0.53)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-dev-server@4.11.1): resolution: {integrity: sha512-pnl11MYKM3jRmHQz2dSnEDfDiApdH7ys3zH/FjImsTK6S8etMKlxGnZ58Puxj05qvrBRgpxnQSL+ZazfrEX/6w==} engines: {node: '>=16.0.0'} peerDependencies: @@ -29599,9 +30759,9 @@ packages: typescript: optional: true dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 '@storybook/builder-webpack5': 7.4.2(@types/react-dom@17.0.19)(@types/react@17.0.53)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-cli@5.1.4) - '@storybook/preset-react-webpack': 7.4.2(@babel/core@7.23.2)(@swc/core@1.3.49)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-dev-server@4.11.1) + '@storybook/preset-react-webpack': 7.4.2(@babel/core@7.24.4)(@swc/core@1.3.49)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-dev-server@4.11.1) '@storybook/react': 7.4.2(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) '@types/node': 16.11.7 react: 17.0.2 @@ -29625,7 +30785,7 @@ packages: - webpack-plugin-serve dev: true - /@storybook/react-webpack5@7.4.2(@babel/core@7.23.2)(@swc/core@1.3.49)(@types/react-dom@17.0.20)(@types/react@17.0.62)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): + /@storybook/react-webpack5@7.4.2(@babel/core@7.24.4)(@swc/core@1.3.49)(@types/react-dom@17.0.20)(@types/react@17.0.62)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5): resolution: {integrity: sha512-pnl11MYKM3jRmHQz2dSnEDfDiApdH7ys3zH/FjImsTK6S8etMKlxGnZ58Puxj05qvrBRgpxnQSL+ZazfrEX/6w==} engines: {node: '>=16.0.0'} peerDependencies: @@ -29639,9 +30799,9 @@ packages: typescript: optional: true dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 '@storybook/builder-webpack5': 7.4.2(@types/react-dom@17.0.20)(@types/react@17.0.62)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) - '@storybook/preset-react-webpack': 7.4.2(@babel/core@7.23.2)(@swc/core@1.3.49)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-cli@5.1.4) + '@storybook/preset-react-webpack': 7.4.2(@babel/core@7.24.4)(@swc/core@1.3.49)(esbuild@0.18.20)(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5)(webpack-cli@5.1.4) '@storybook/react': 7.4.2(react-dom@17.0.2)(react@17.0.2)(typescript@4.9.5) '@types/node': 16.11.7 react: 17.0.2 @@ -30153,7 +31313,7 @@ packages: resolution: {integrity: sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==} engines: {node: '>=14'} dependencies: - '@babel/code-frame': 7.22.13 + '@babel/code-frame': 7.24.2 '@babel/runtime': 7.23.2 '@types/aria-query': 5.0.2 aria-query: 5.1.3 @@ -30443,7 +31603,7 @@ packages: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 20.5.1 + '@types/node': 14.18.42 /@types/bonjour@3.5.10: resolution: {integrity: sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==} @@ -30792,7 +31952,7 @@ packages: /@types/express-serve-static-core@4.17.33: resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} dependencies: - '@types/node': 20.5.1 + '@types/node': 14.18.42 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 @@ -31210,6 +32370,8 @@ packages: /@types/node@20.5.1: resolution: {integrity: sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==} requiresBuild: true + dev: true + optional: true /@types/nodemailer@6.4.11: resolution: {integrity: sha512-Ld2c0frwpGT4VseuoeboCXQ7UJIkK3X7Lx/4YsZEiUHtHsthWAOCYtf6PAiLhMtfwV0cWJRabLBS3+LD8x6Nrw==} @@ -31432,7 +32594,7 @@ packages: resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==} dependencies: '@types/mime': 3.0.1 - '@types/node': 20.5.1 + '@types/node': 14.18.42 /@types/shimmer@1.0.5: resolution: {integrity: sha512-9Hp0ObzwwO57DpLFF0InUjUm/II8GmKAvzbefxQTihCb7KI6yc9yzf0nLc4mVdby5N4DRCgQM2wCup9KTieeww==} @@ -31699,7 +32861,7 @@ packages: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.5.0 + '@eslint-community/regexpp': 4.9.1 '@typescript-eslint/parser': 5.58.0(eslint@8.51.0)(typescript@4.9.5) '@typescript-eslint/scope-manager': 5.58.0 '@typescript-eslint/type-utils': 5.58.0(eslint@8.51.0)(typescript@4.9.5) @@ -34164,7 +35326,7 @@ packages: /axios@0.21.4: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.5(debug@4.3.4) transitivePeerDependencies: - debug dev: false @@ -34172,7 +35334,7 @@ packages: /axios@0.26.1: resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.2 transitivePeerDependencies: - debug dev: false @@ -34180,7 +35342,7 @@ packages: /axios@0.28.0(debug@4.3.4): resolution: {integrity: sha512-Tu7NYoGY4Yoc7I+Npf9HhUMtEEpV7ZiLH9yndTCoNhcpBH0kwcvFbzYN9/u5QKI5A6uefjsNNWaz5olJVYS62Q==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.5(debug@4.3.4) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -34190,7 +35352,7 @@ packages: /axios@1.1.3: resolution: {integrity: sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.5(debug@4.3.4) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -34200,7 +35362,7 @@ packages: /axios@1.6.0: resolution: {integrity: sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.2 form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -34210,7 +35372,7 @@ packages: /axios@1.6.2: resolution: {integrity: sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.2 form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -34219,7 +35381,7 @@ packages: /axios@1.6.7: resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==} dependencies: - follow-redirects: 1.15.5 + follow-redirects: 1.15.5(debug@4.3.4) form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -34328,14 +35490,14 @@ packages: webpack: 5.78.0(@swc/core@1.3.49)(esbuild@0.18.20)(webpack-cli@5.1.4) dev: true - /babel-loader@8.3.0(@babel/core@7.23.2)(webpack@5.82.1): + /babel-loader@8.3.0(@babel/core@7.24.4)(webpack@5.82.1): resolution: {integrity: sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==} engines: {node: '>= 8.9'} peerDependencies: '@babel/core': ^7.0.0 webpack: '>=2' dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 find-cache-dir: 3.3.2 loader-utils: 2.0.4 make-dir: 3.1.0 @@ -34499,6 +35661,19 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color + dev: true + + /babel-plugin-polyfill-corejs2@0.4.6(@babel/core@7.24.4): + resolution: {integrity: sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/compat-data': 7.23.2 + '@babel/core': 7.24.4 + '@babel/helper-define-polyfill-provider': 0.4.3(@babel/core@7.24.4) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color /babel-plugin-polyfill-corejs3@0.8.5(@babel/core@7.22.11): resolution: {integrity: sha512-Q6CdATeAvbScWPNLB8lzSO7fgUVBkQt6zLgNlfyeCr/EQaEQR+bWiBYYPYAFyE528BMjRhL+1QBMOI4jc/c5TA==} @@ -34534,6 +35709,18 @@ packages: core-js-compat: 3.32.2 transitivePeerDependencies: - supports-color + dev: true + + /babel-plugin-polyfill-corejs3@0.8.5(@babel/core@7.24.4): + resolution: {integrity: sha512-Q6CdATeAvbScWPNLB8lzSO7fgUVBkQt6zLgNlfyeCr/EQaEQR+bWiBYYPYAFyE528BMjRhL+1QBMOI4jc/c5TA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-define-polyfill-provider': 0.4.3(@babel/core@7.24.4) + core-js-compat: 3.32.2 + transitivePeerDependencies: + - supports-color /babel-plugin-polyfill-regenerator@0.5.3(@babel/core@7.22.11): resolution: {integrity: sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==} @@ -34566,6 +35753,17 @@ packages: '@babel/helper-define-polyfill-provider': 0.4.3(@babel/core@7.23.2) transitivePeerDependencies: - supports-color + dev: true + + /babel-plugin-polyfill-regenerator@0.5.3(@babel/core@7.24.4): + resolution: {integrity: sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-define-polyfill-provider': 0.4.3(@babel/core@7.24.4) + transitivePeerDependencies: + - supports-color /babel-plugin-react-docgen@4.2.1: resolution: {integrity: sha512-UQ0NmGHj/HAqi5Bew8WvNfCk8wSsmdgNd8ZdMjBCICtyCJCq9LiqgqvjCYe570/Wg7AQArSq1VQ60Dd/CHN7mQ==} @@ -37848,7 +39046,6 @@ packages: dependencies: ms: 2.1.3 supports-color: 5.5.0 - dev: true /debug@3.2.7(supports-color@8.1.1): resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} @@ -39143,7 +40340,7 @@ packages: /eslint-import-resolver-node@0.3.7: resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} dependencies: - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) is-core-module: 2.13.0 resolve: 1.22.2 transitivePeerDependencies: @@ -39158,7 +40355,7 @@ packages: webpack: '>=1.11.0' dependencies: array.prototype.find: 2.2.2 - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) enhanced-resolve: 0.9.1 eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.58.0)(eslint-import-resolver-webpack@0.13.7)(eslint@8.38.0) find-root: 1.1.0 @@ -39196,7 +40393,7 @@ packages: optional: true dependencies: '@typescript-eslint/parser': 5.58.0(eslint@8.38.0)(typescript@4.9.5) - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) eslint: 8.38.0 eslint-import-resolver-node: 0.3.7 eslint-import-resolver-webpack: 0.13.7(eslint-plugin-import@2.28.1)(webpack@5.78.0) @@ -39226,7 +40423,7 @@ packages: optional: true dependencies: '@typescript-eslint/parser': 5.58.0(eslint@8.51.0)(typescript@4.9.5) - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) eslint: 8.51.0 eslint-import-resolver-node: 0.3.7 eslint-import-resolver-webpack: 0.13.7(eslint-plugin-import@2.28.1)(webpack@5.78.0) @@ -39261,8 +40458,8 @@ packages: '@babel/plugin-transform-react-jsx': ^7.14.9 eslint: ^8.1.0 dependencies: - '@babel/plugin-syntax-flow': 7.22.5(@babel/core@7.23.2) - '@babel/plugin-transform-react-jsx': 7.22.15(@babel/core@7.23.2) + '@babel/plugin-syntax-flow': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-react-jsx': 7.22.15(@babel/core@7.24.4) eslint: 8.51.0 lodash: 4.17.21 string-natural-compare: 3.0.1 @@ -39307,7 +40504,7 @@ packages: array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) doctrine: 2.1.0 eslint: 8.38.0 eslint-import-resolver-node: 0.3.7 @@ -39342,7 +40539,7 @@ packages: array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) doctrine: 2.1.0 eslint: 8.51.0 eslint-import-resolver-node: 0.3.7 @@ -39425,7 +40622,7 @@ packages: damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 eslint: 8.51.0 - has: 1.0.3 + has: 1.0.4 jsx-ast-utils: 3.3.3 language-tags: 1.0.5 minimatch: 3.1.2 @@ -40759,7 +41956,7 @@ packages: tslib: 2.6.2 dev: false - /follow-redirects@1.15.2(debug@4.3.4): + /follow-redirects@1.15.2: resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} peerDependencies: @@ -40767,10 +41964,8 @@ packages: peerDependenciesMeta: debug: optional: true - dependencies: - debug: 4.3.4(supports-color@8.1.1) - /follow-redirects@1.15.5: + /follow-redirects@1.15.5(debug@4.3.4): resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} engines: {node: '>=4.0'} peerDependencies: @@ -40778,6 +41973,8 @@ packages: peerDependenciesMeta: debug: optional: true + dependencies: + debug: 4.3.4(supports-color@8.1.1) /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -41125,6 +42322,14 @@ packages: dev: true optional: true + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -42566,7 +43771,7 @@ packages: engines: {node: '>=8.0.0'} dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.2 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -44153,7 +45358,7 @@ packages: pretty-format: 27.5.1 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.1(@types/node@16.11.7)(typescript@4.9.5) + ts-node: 10.9.1(@types/node@14.18.42)(typescript@4.9.5) transitivePeerDependencies: - bufferutil - canvas @@ -48687,7 +49892,7 @@ packages: hasBin: true requiresBuild: true dependencies: - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) iconv-lite: 0.6.3 sax: 1.2.4 transitivePeerDependencies: @@ -50985,6 +52190,22 @@ packages: find-up: 3.0.0 dev: true + /playwright-core@1.42.1: + resolution: {integrity: sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==} + engines: {node: '>=16'} + hasBin: true + dev: true + + /playwright@1.42.1: + resolution: {integrity: sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==} + engines: {node: '>=16'} + hasBin: true + dependencies: + playwright-core: 1.42.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /please-upgrade-node@3.2.0: resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} dependencies: @@ -51063,7 +52284,7 @@ packages: engines: {node: '>= 0.12.0'} dependencies: async: 2.6.4 - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7(supports-color@5.5.0) mkdirp: 0.5.6 transitivePeerDependencies: - supports-color @@ -54936,11 +56157,11 @@ packages: peerDependencies: rollup: ^2.0.0 dependencies: - '@babel/code-frame': 7.22.13 + '@babel/code-frame': 7.21.4 jest-worker: 26.6.2 rollup: 2.79.1 serialize-javascript: 4.0.0 - terser: 5.22.0 + terser: 5.16.9 dev: true /rollup-plugin-terser@7.0.2(rollup@3.20.2): @@ -57716,7 +58937,7 @@ packages: tslib: 1.14.1 dev: true - /ts-jest@27.1.5(@babel/core@7.23.2)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5): + /ts-jest@27.1.5(@babel/core@7.24.4)(@types/jest@27.5.2)(jest@27.5.1)(typescript@4.9.5): resolution: {integrity: sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} hasBin: true @@ -57737,7 +58958,7 @@ packages: esbuild: optional: true dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 '@types/jest': 27.5.2 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -57751,7 +58972,7 @@ packages: yargs-parser: 20.2.9 dev: true - /ts-jest@27.1.5(@babel/core@7.23.2)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5): + /ts-jest@27.1.5(@babel/core@7.24.4)(@types/jest@29.5.1)(jest@27.5.1)(typescript@4.9.5): resolution: {integrity: sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} hasBin: true @@ -57772,7 +58993,7 @@ packages: esbuild: optional: true dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 '@types/jest': 29.5.1 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -57786,7 +59007,7 @@ packages: yargs-parser: 20.2.9 dev: true - /ts-jest@27.1.5(@babel/core@7.23.2)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5): + /ts-jest@27.1.5(@babel/core@7.24.4)(@types/jest@29.5.2)(jest@27.5.1)(typescript@4.9.5): resolution: {integrity: sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} hasBin: true @@ -57807,7 +59028,7 @@ packages: esbuild: optional: true dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 '@types/jest': 29.5.2 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -57821,7 +59042,7 @@ packages: yargs-parser: 20.2.9 dev: true - /ts-jest@29.1.0(@babel/core@7.23.2)(esbuild@0.18.20)(jest@29.5.0)(typescript@4.9.5): + /ts-jest@29.1.0(@babel/core@7.24.4)(esbuild@0.18.20)(jest@29.5.0)(typescript@4.9.5): resolution: {integrity: sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -57842,7 +59063,7 @@ packages: esbuild: optional: true dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 bs-logger: 0.2.6 esbuild: 0.18.20 fast-json-stable-stringify: 2.1.0 @@ -57856,7 +59077,7 @@ packages: yargs-parser: 21.1.1 dev: true - /ts-jest@29.1.0(@babel/core@7.23.2)(jest@29.5.0)(typescript@4.9.5): + /ts-jest@29.1.0(@babel/core@7.24.4)(jest@29.5.0)(typescript@4.9.5): resolution: {integrity: sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -57877,7 +59098,7 @@ packages: esbuild: optional: true dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 jest: 29.5.0(@types/node@14.18.42)(ts-node@10.9.1) @@ -57890,7 +59111,7 @@ packages: yargs-parser: 21.1.1 dev: true - /ts-jest@29.1.2(@babel/core@7.23.2)(jest@29.7.0)(typescript@4.9.5): + /ts-jest@29.1.2(@babel/core@7.24.4)(jest@29.7.0)(typescript@4.9.5): resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -57911,7 +59132,7 @@ packages: esbuild: optional: true dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.24.4 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 jest: 29.7.0(@types/node@14.18.42)(ts-node@10.9.1) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8118e6c86c2..ed81fa8c04c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -11,3 +11,4 @@ packages: # all packages in enterprise modules - 'enterprise/packages/*' - 'enterprise/packages/libs/*' + - 'enterprise/packages/web/*'