diff --git a/.dockerignore b/.dockerignore deleted file mode 100755 index 92f7e4b42..000000000 --- a/.dockerignore +++ /dev/null @@ -1,10 +0,0 @@ -node_modules -npm-debug.log -README.md -LICENSE -.babelrc -.eslintignore -.eslintrc.json -.gitignore -.npmignore -commitlint.config.js diff --git a/.env b/.env deleted file mode 100644 index aaacfc1e2..000000000 --- a/.env +++ /dev/null @@ -1,44 +0,0 @@ -NODE_ENV='production' -NODE_PATH=./src -BASE_URL='' -LMS_BASE_URL='' -ECOMMERCE_BASE_URL='' -LOGIN_URL='' -LOGOUT_URL='' -CSRF_TOKEN_API_PATH='' -REFRESH_ACCESS_TOKEN_ENDPOINT='' -DATA_API_BASE_URL='' -SEGMENT_KEY='' -FEATURE_FLAGS={} -ACCESS_TOKEN_COOKIE_NAME='' -NEW_RELIC_APP_ID='' -NEW_RELIC_LICENSE_KEY='' -SITE_NAME='' -MARKETING_SITE_BASE_URL='' -SUPPORT_URL='' -CONTACT_URL='' -OPEN_SOURCE_URL='' -TERMS_OF_SERVICE_URL='' -PRIVACY_POLICY_URL='' -FACEBOOK_URL='' -TWITTER_URL='' -YOU_TUBE_URL='' -LINKED_IN_URL='' -REDDIT_URL='' -APPLE_APP_STORE_URL='' -GOOGLE_PLAY_URL='' -ENTERPRISE_MARKETING_URL='' -ENTERPRISE_MARKETING_UTM_SOURCE='' -ENTERPRISE_MARKETING_UTM_CAMPAIGN='' -ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='' -LEARNING_BASE_URL='' -HOTJAR_APP_ID='' -HOTJAR_VERSION='6' -HOTJAR_DEBUG='' -ACCOUNT_SETTINGS_URL='' -ACCOUNT_PROFILE_URL='' -ENABLE_NOTICES='' -CAREER_LINK_URL='' -ENABLE_EDX_PERSONAL_DASHBOARD=false -ENABLE_PROGRAMS=false -NON_BROWSABLE_COURSES=false diff --git a/.env.development b/.env.development deleted file mode 100644 index 067214769..000000000 --- a/.env.development +++ /dev/null @@ -1,50 +0,0 @@ -NODE_ENV='development' -PORT=1996 -BASE_URL='localhost:1996' -LMS_BASE_URL='http://localhost:18000' -ECOMMERCE_BASE_URL='http://localhost:18130' -LOGIN_URL='http://localhost:18000/login' -LOGOUT_URL='http://localhost:18000/logout' -LOGO_URL=https://edx-cdn.org/v3/default/logo.svg -LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg -LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg -FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico -CSRF_TOKEN_API_PATH='/csrf/api/v1/token' -REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh' -ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' -USER_INFO_COOKIE_NAME='edx-user-info' -SITE_NAME=localhost -DATA_API_BASE_URL='http://localhost:8000' -// LMS_CLIENT_ID should match the lms DOT client application id your LMS containe -LMS_CLIENT_ID='login-service-client-id' -SEGMENT_KEY='' -FEATURE_FLAGS={} -MARKETING_SITE_BASE_URL='http://localhost:18000' -SUPPORT_URL='' -CONTACT_URL='http://localhost:18000/contact' -OPEN_SOURCE_URL='http://localhost:18000/openedx' -TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service' -PRIVACY_POLICY_URL='http://localhost:18000/privacy-policy' -FACEBOOK_URL='https://www.facebook.com' -TWITTER_URL='https://twitter.com' -YOU_TUBE_URL='https://www.youtube.com' -LINKED_IN_URL='https://www.linkedin.com' -REDDIT_URL='https://www.reddit.com' -APPLE_APP_STORE_URL='https://www.apple.com/ios/app-store/' -GOOGLE_PLAY_URL='https://play.google.com/store' -ENTERPRISE_MARKETING_URL='http://example.com' -ENTERPRISE_MARKETING_UTM_SOURCE='example.com' -ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral' -ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer' -LEARNING_BASE_URL='http://localhost:2000' -SESSION_COOKIE_DOMAIN='localhost' -HOTJAR_APP_ID='' -HOTJAR_VERSION='6' -HOTJAR_DEBUG='' -ACCOUNT_SETTINGS_URL='http://localhost:1997' -ACCOUNT_PROFILE_URL='http://localhost:1995' -ENABLE_NOTICES='' -CAREER_LINK_URL='' -ENABLE_EDX_PERSONAL_DASHBOARD=false -ENABLE_PROGRAMS=false -NON_BROWSABLE_COURSES=false diff --git a/.env.test b/.env.test deleted file mode 100644 index 0b22f2a95..000000000 --- a/.env.test +++ /dev/null @@ -1,49 +0,0 @@ -NODE_ENV='test' -PORT=1996 -BASE_URL='localhost:1996' -LMS_BASE_URL='http://localhost:18000' -ECOMMERCE_BASE_URL='http://localhost:18130' -LOGIN_URL='http://localhost:18000/login' -LOGOUT_URL='http://localhost:18000/logout' -LOGO_URL=https://edx-cdn.org/v3/default/logo.svg -LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg -LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg -FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico -CSRF_TOKEN_API_PATH='/csrf/api/v1/token' -REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh' -ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' -USER_INFO_COOKIE_NAME='edx-user-info' -SITE_NAME=localhost -DATA_API_BASE_URL='http://localhost:8000' -// LMS_CLIENT_ID should match the lms DOT client application id your LMS containe -LMS_CLIENT_ID='login-service-client-id' -SEGMENT_KEY='' -FEATURE_FLAGS={} -MARKETING_SITE_BASE_URL='http://localhost:18000' -SUPPORT_URL='' -CONTACT_URL='http://localhost:18000/contact' -OPEN_SOURCE_URL='http://localhost:18000/openedx' -TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service' -PRIVACY_POLICY_URL='http://localhost:18000/privacy-policy' -FACEBOOK_URL='https://www.facebook.com' -TWITTER_URL='https://twitter.com' -YOU_TUBE_URL='https://www.youtube.com' -LINKED_IN_URL='https://www.linkedin.com' -REDDIT_URL='https://www.reddit.com' -APPLE_APP_STORE_URL='https://www.apple.com/ios/app-store/' -GOOGLE_PLAY_URL='https://play.google.com/store' -ENTERPRISE_MARKETING_URL='http://example.com' -ENTERPRISE_MARKETING_UTM_SOURCE='example.com' -ENTERPRISE_MARKETING_UTM_CAMPAIGN='example.com Referral' -ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer' -LEARNING_BASE_URL='http://localhost:2000' -HOTJAR_APP_ID='hot-jar-app-id' -HOTJAR_VERSION='6' -HOTJAR_DEBUG='' -ACCOUNT_SETTINGS_URL='http://account-settings-url.test' -ACCOUNT_PROFILE_URL='http://account-profile-url.test' -ENABLE_NOTICES='' -CAREER_LINK_URL='' -ENABLE_EDX_PERSONAL_DASHBOARD=true -ENABLE_PROGRAMS=false -NON_BROWSABLE_COURSES=false diff --git a/.eslintignore b/.eslintignore deleted file mode 100755 index fcea47f62..000000000 --- a/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -coverage/* -dist/ -node_modules/ -src/postcss.config.js -src/segment.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 09e31fce2..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,22 +0,0 @@ -const { createConfig } = require('@openedx/frontend-build'); - -const config = createConfig('eslint', { - rules: { - 'import/no-named-as-default': 'off', - 'import/no-named-as-default-member': 'off', - 'import/no-self-import': 'off', - 'import/no-import-module-exports': 'off', - 'spaced-comment': ['error', 'always', { 'block': { 'exceptions': ['*'] } }], - }, -}); - -config.settings = { - "import/resolver": { - node: { - paths: ["src", "node_modules"], - extensions: [".js", ".jsx"], - }, - }, -}; - -module.exports = config; diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 44f4f7bae..000000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.snap linguist-generated=false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62e663c50..a30cd01e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,9 @@ jobs: - name: Build run: npm run build + - name: Build (CI) + run: npm run build:ci + - name: Run Coverage uses: codecov/codecov-action@v5 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..642cbfee3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,32 @@ +name: Release CI +on: + push: + branches: + - frontend-base + +permissions: + id-token: write # Required for OIDC + contents: write # For Semantic Release tagging + +jobs: + release: + name: Release + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' + + - name: Install dependencies + run: npm ci + + - name: Release to npm/Github + run: npx semantic-release@25 + env: + GITHUB_TOKEN: ${{ secrets.OPENEDX_SEMANTIC_RELEASE_GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index c68a920f1..c6b5b093e 100755 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,21 @@ -.DS_Store -.eslintcache -env.config.* node_modules npm-debug.log coverage - +module.config.js dist/ -public/samples/ +packages/ +/.turbo +/turbo.json +/*.tgz -### pyenv ### -.python-version +### i18n ### +src/i18n/transifex_input.json +src/i18n/messages.ts +src/i18n/messages/ +src/i18n/site-messages/index.ts -### Emacs ### +### Editors ### +.DS_Store *~ -*.swo -*.swp - -### Development environments ### -.idea -.vscode - -# Local package dependencies -module.config.js - -### transifex ### -src/i18n/transifex_input.json -temp -src/i18n/messages \ No newline at end of file +/temp +/.vscode diff --git a/.npmignore b/.npmignore index 559d68006..bb212e05a 100755 --- a/.npmignore +++ b/.npmignore @@ -1,12 +1,6 @@ -.eslintignore -.eslintrc.json -.gitignore -docker-compose.yml -Dockerfile -Makefile -npm-debug.log - -config -coverage +__mocks__ node_modules -public +*.test.js +*.test.jsx +*.test.ts +*.test.tsx diff --git a/.nvmrc b/.nvmrc index 209e3ef4b..a45fd52cc 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20 +24 diff --git a/.releaserc b/.releaserc new file mode 100644 index 000000000..24f22ae89 --- /dev/null +++ b/.releaserc @@ -0,0 +1,13 @@ +{ + "branches": [ + "placeholder", + { "name": "frontend-base", "prerelease": "alpha", "channel": "latest" } + ], + "tagFormat": "v${version}", + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/npm", + "@semantic-release/github" + ] +} diff --git a/Makefile b/Makefile index e4db73fe1..5ad76b930 100755 --- a/Makefile +++ b/Makefile @@ -2,13 +2,7 @@ npm-install-%: ## install specified % npm package npm install $* --save-dev git add package.json -intl_imports = ./node_modules/.bin/intl-imports.js -transifex_utils = ./node_modules/.bin/transifex-utils.js -i18n = ./src/i18n -transifex_input = $(i18n)/transifex_input.json - -# This directory must match .babelrc . -transifex_temp = ./temp/babel-plugin-formatjs +TURBO = TURBO_TELEMETRY_DISABLED=1 turbo --dangerously-disable-package-manager-check NPM_TESTS=build i18n_extract lint test @@ -24,33 +18,55 @@ test.npm.%: validate-no-uncommitted-package-lock-changes requirements: ## install ci requirements npm ci -i18n.extract: - # Pulling display strings from .jsx files into .json files... - rm -rf $(transifex_temp) - npm run-script i18n_extract +# turbo.site.json is the standalone turbo config for this package. It is +# renamed to avoid conflicts with turbo v2's workspace validation, which +# rejects root task syntax (//#) and requires "extends" in package-level +# turbo.json files, such as when running in a site repository. The targets +# below copy it into place before running turbo and clean up after. +turbo.json: turbo.site.json + cp $< $@ + +# NPM doesn't bin-link workspace packages during install, so it must be done manually. +bin-link: + [ -f packages/frontend-base/package.json ] && npm rebuild --ignore-scripts @openedx/frontend-base || true + +build-packages: turbo.json + $(TURBO) run build; rm -f turbo.json + $(MAKE) bin-link + +clean-packages: turbo.json + $(TURBO) run clean; rm -f turbo.json -i18n.concat: - # Gathering JSON messages into one file... - $(transifex_utils) $(transifex_temp) $(transifex_input) +dev-packages: turbo.json + $(TURBO) run watch:build dev:site; rm -f turbo.json -extract_translations: | requirements i18n.extract i18n.concat +dev-site: bin-link + npm run dev -# Despite the name, we actually need this target to detect changes in the incoming translated message files as well. -detect_changed_source_translations: - # Checking for changed translations... - git diff --exit-code $(i18n) +clean: + rm -rf dist + +build: + tsc --project tsconfig.build.json + find src -type f \( -name '*.scss' -o \( \( -name '*.png' -o -name '*.svg' \) -path '*/assets/*' \) \) -exec sh -c '\ + for f in "$$@"; do \ + d="dist/$${f#src/}"; \ + mkdir -p "$$(dirname "$$d")"; \ + cp "$$f" "$$d"; \ + done' sh {} + + tsc-alias -p tsconfig.build.json + +build-ci: + SITE_CONFIG_PATH=site.config.ci.tsx openedx build + +i18n.extract: + # Pulling display strings from .jsx files into .json files... + npm run-script i18n_extract -pull_translations: - rm -rf src/i18n/messages - mkdir src/i18n/messages - cd src/i18n/messages \ - && atlas pull $(ATLAS_OPTIONS) \ - translations/frontend-platform/src/i18n/messages:frontend-platform \ - translations/paragon/src/i18n/messages:paragon \ - translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \ - translations/frontend-app-learner-dashboard/src/i18n/messages:frontend-app-learner-dashboard +extract_translations: | requirements i18n.extract - $(intl_imports) frontend-platform paragon frontend-component-footer frontend-app-learner-dashboard +pull_translations: | requirements + npm run translations:pull -- --atlas-options="$(ATLAS_OPTIONS)" # This target is used by CI. validate-no-uncommitted-package-lock-changes: diff --git a/README.rst b/README.rst index 832f3cc39..d20410c33 100644 --- a/README.rst +++ b/README.rst @@ -18,7 +18,7 @@ frontend-app-learner-dashboard The Learner Home app is a microfrontend (MFE) course listing experience for the Open edX Learning Management System (LMS). This experience was designed to provide a clean and functional interface to allow learners to view all of their open enrollments, as well as take relevant actions on those enrollments. It also serves as host to a number of exposed -"widget" containers to provide upsell and discovery widgets as sidebar/footer components. +"widget" containers to provide upsell and discovery widgets as sidebar components. Quickstart ---------- @@ -30,31 +30,10 @@ To start the MFE and enable the feature in LMS: From there, simply load the configured address/port. You should be prompted to log into your LMS if you are not already, and then redirected to your home page. -Plugins +Widgets ------- -This MFE can be customized using `Frontend Plugin Framework `_. - -The parts of this MFE that can be customized in that manner are documented `here `_. - -Contributing ------------- - -A core goal of this app is to provide a clean experimentation interface. To promote this end, we have provided a -silo'ed code directory at ``src/widgets`` in which contributors should add their custom widget components. In order to -ensure our ability to maintain the code stability of the app, the code for these widgets should be strictly contained -within the bounds of that directory. - -Once written, the widgets can be configured into one of our widget containers at ``src/containers/WidgetContainers``. -This can include conditional logic, as well as Optimizely triggers. It is important to note that our integration tests -will isolate and ignore these containers, and thus testing your widget is the response of the creator/maintainer of the -widget itself. - -Some guidelines for writing widgets: - -* Code for the widget should be strictly confined to the ``src/widgets`` directory. -* You can load data from the redux store, but should not add or modify fields in that structure. -* Network events should be managed in component hooks, though can use our ``data/constants/requests:requestStates`` for - ease of tracking the request states. +This MFE can be customized with widgets. The parts of this MFE that can be customized in that manner are documented +`here `_. License ------- diff --git a/app.d.ts b/app.d.ts new file mode 100644 index 000000000..d0bf24c72 --- /dev/null +++ b/app.d.ts @@ -0,0 +1,10 @@ +/// + +declare module 'site.config' { + export default SiteConfig; +} + +declare module '*.svg' { + const content: string; + export default content; +} diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 000000000..ba714ec43 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,3 @@ +const { createConfig } = require('@openedx/frontend-base/tools'); + +module.exports = createConfig('babel'); diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..16496fef3 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,22 @@ +// @ts-check + +const { createLintConfig } = require('@openedx/frontend-base/tools'); + +module.exports = createLintConfig( + { + files: [ + 'src/**/*', + 'site.config.*', + ], + }, + { + ignores: [ + 'coverage/*', + 'dist/*', + 'documentation/*', + 'node_modules/*', + '**/__mocks__/*', + '**/__snapshots__/*', + ], + }, +); diff --git a/example.env.config.js b/example.env.config.js deleted file mode 100644 index 9637d1985..000000000 --- a/example.env.config.js +++ /dev/null @@ -1,72 +0,0 @@ -/* -Learner Dashboard is now able to handle JS-based configuration! - -For the time being, the `.env.*` files are still made available when cloning down this repo or pulling from -the master branch. To switch to using `env.config.js`, make a copy of `example.env.config.js` and configure as needed. - -For testing with Jest Snapshot, there is a mock in `/src/setupTest.jsx` for `getConfig` that will need to be -uncommented. - -Note: having both .env and env.config.js files will follow a predictable order, in which non-empty values in the -JS-based config will overwrite the .env environment variables. - -frontend-platform's getConfig loads configuration in the following sequence: -- .env file config -- optional handlers (commonly used to merge MFE-specific config in via additional process.env variables) -- env.config.js file config -- runtime config -*/ - -module.exports = { - NODE_ENV: 'development', - NODE_PATH: './src', - PORT: 1996, - BASE_URL: 'localhost:1996', - LMS_BASE_URL: 'http://localhost:18000', - ECOMMERCE_BASE_URL: 'http://localhost:18130', - LOGIN_URL: 'http://localhost:18000/login', - LOGOUT_URL: 'http://localhost:18000/logout', - LOGO_URL: 'https://edx-cdn.org/v3/default/logo.svg', - LOGO_TRADEMARK_URL: 'https://edx-cdn.org/v3/default/logo-trademark.svg', - LOGO_WHITE_URL: 'https://edx-cdn.org/v3/default/logo-white.svg', - FAVICON_URL: 'https://edx-cdn.org/v3/default/favicon.ico', - CSRF_TOKEN_API_PATH: '/csrf/api/v1/token', - REFRESH_ACCESS_TOKEN_ENDPOINT: 'http://localhost:18000/login_refresh', - ACCESS_TOKEN_COOKIE_NAME: 'edx-jwt-cookie-header-payload', - USER_INFO_COOKIE_NAME: 'edx-user-info', - SITE_NAME: 'localhost', - DATA_API_BASE_URL: 'http://localhost:8000', - // LMS_CLIENT_ID should match the lms DOT client application in your LMS container - LMS_CLIENT_ID: 'login-service-client-id', - SEGMENT_KEY: '', - FEATURE_FLAGS: {}, - MARKETING_SITE_BASE_URL: 'http://localhost:18000', - SUPPORT_URL: 'http://localhost:18000/support', - CONTACT_URL: 'http://localhost:18000/contact', - OPEN_SOURCE_URL: 'http://localhost:18000/openedx', - TERMS_OF_SERVICE_URL: 'http://localhost:18000/terms-of-service', - PRIVACY_POLICY_URL: 'http://localhost:18000/privacy-policy', - FACEBOOK_URL: 'https://www.facebook.com', - TWITTER_URL: 'https://twitter.com', - YOU_TUBE_URL: 'https://www.youtube.com', - LINKED_IN_URL: 'https://www.linkedin.com', - REDDIT_URL: 'https://www.reddit.com', - APPLE_APP_STORE_URL: 'https://www.apple.com/ios/app-store/', - GOOGLE_PLAY_URL: 'https://play.google.com/store', - ENTERPRISE_MARKETING_URL: 'http://example.com', - ENTERPRISE_MARKETING_UTM_SOURCE: 'example.com', - ENTERPRISE_MARKETING_UTM_CAMPAIGN: 'example.com Referral', - ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM: 'Footer', - LEARNING_BASE_URL: 'http://localhost:2000', - SESSION_COOKIE_DOMAIN: 'localhost', - HOTJAR_APP_ID: '', - HOTJAR_VERSION: 6, - HOTJAR_DEBUG: '', - NEW_RELIC_APP_ID: '', - NEW_RELIC_LICENSE_KEY: '', - ACCOUNT_SETTINGS_URL: 'http://localhost:1997', - ACCOUNT_PROFILE_URL: 'http://localhost:1995', - ENABLE_NOTICES: '', - CAREER_LINK_URL: '', - EXPERIMENT_08_23_VAN_PAINTED_DOOR: true, -}; diff --git a/jest.config.js b/jest.config.js index c5a014ef0..0f268cabf 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,18 +1,21 @@ -const { createConfig } = require('@openedx/frontend-build'); +const { createConfig } = require('@openedx/frontend-base/tools'); -module.exports = createConfig('jest', { +const config = createConfig('test', { setupFilesAfterEnv: [ 'jest-expect-message', '/src/setupTest.jsx', ], - modulePaths: ['/src/'], coveragePathIgnorePatterns: [ 'src/segment.js', - 'src/postcss.config.js', 'testUtils', // don't unit test jest mocking tools - 'src/data/services/lms/fakeData', // don't unit test mock data - 'src/test', // don't unit test integration test utils + 'src/__mocks__', ], + moduleNameMapper: { + '\\.svg$': '/src/__mocks__/svg.js', + '\\.png$': '/src/__mocks__/file.js', + '^@src/(.*)$': '/src/$1', + }, testTimeout: 120000, - testEnvironment: 'jsdom', }); + +module.exports = config; diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 000000000..6e65a941d --- /dev/null +++ b/nodemon.json @@ -0,0 +1,6 @@ +{ + "watch": [ + "src" + ], + "ext": "js,jsx,ts,tsx,scss,png,svg" +} diff --git a/package-lock.json b/package-lock.json index d0bc45d0f..e515909c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,156 +1,104 @@ { - "name": "@edx/frontend-app-learner-dashboard", - "version": "0.0.1", + "name": "@openedx/frontend-app-learner-dashboard", + "version": "0.0.0-dev", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@edx/frontend-app-learner-dashboard", - "version": "0.0.1", + "name": "@openedx/frontend-app-learner-dashboard", + "version": "0.0.0-dev", "license": "AGPL-3.0", + "workspaces": [ + "packages/*" + ], "dependencies": { - "@edx/brand": "npm:@openedx/brand-openedx@^1.2.2", - "@edx/frontend-component-footer": "^14.6.0", - "@edx/frontend-component-header": "^6.2.0", - "@edx/frontend-enterprise-hotjar": "7.2.0", - "@edx/frontend-platform": "^8.3.1", "@edx/openedx-atlas": "^0.7.0", - "@edx/react-unit-test-utils": "^4.0.0", "@fortawesome/fontawesome-svg-core": "^1.2.36", "@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.2.0", - "@openedx/frontend-plugin-framework": "^1.7.0", - "@openedx/paragon": "^22.16.0", - "@redux-devtools/extension": "3.3.0", - "@reduxjs/toolkit": "^2.0.0", "classnames": "^2.3.1", - "core-js": "3.42.0", - "filesize": "^10.0.0", "font-awesome": "4.7.0", - "history": "5.3.0", "lodash": "^4.17.21", "moment": "^2.29.4", "prop-types": "15.8.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", "react-helmet": "^6.1.0", - "react-intl": "6.8.9", - "react-redux": "^7.2.4", - "react-router-dom": "6.29.0", - "react-share": "^4.4.0", - "redux": "4.2.1", - "redux-logger": "3.0.6", - "redux-thunk": "2.4.2", - "regenerator-runtime": "^0.14.0", - "reselect": "^4.0.0", - "universal-cookie": "^4.0.4", - "util": "^0.12.4" + "react-share": "^5.2.2" }, "devDependencies": { - "@edx/browserslist-config": "^1.3.0", - "@edx/reactifex": "^2.1.1", - "@openedx/frontend-build": "^14.3.3", + "@edx/browserslist-config": "^1.5.0", "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^16.2.0", - "copy-webpack-plugin": "^12.0.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/jest": "^29.5.14", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-expect-message": "^1.1.3", "jest-when": "^3.6.0", + "nodemon": "^3.1.14", "react-dev-utils": "^12.0.0", - "react-test-renderer": "^18.3.1", - "redux-mock-store": "^1.5.4" + "tsc-alias": "^1.8.16", + "turbo": "^2.8.20" + }, + "peerDependencies": { + "@openedx/frontend-base": "^1.0.0-alpha || 0.0.0-dev", + "@openedx/paragon": "^23", + "@tanstack/react-query": "^5", + "@types/react": "^18", + "@types/react-dom": "^18", + "react": "^18", + "react-dom": "^18", + "react-router": "^6", + "react-router-dom": "^6" } }, "node_modules/@adobe/css-tools": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz", - "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, "license": "MIT" }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/cli": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.24.8.tgz", - "integrity": "sha512-isdp+G6DpRyKc+3Gqxy2rjzgF7Zj9K0mzLNnxz+E/fgeag8qT3vVulX4gY9dGO1q0y+0lUv6V3a+uhUzMzrwXg==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "commander": "^6.2.0", - "convert-source-map": "^2.0.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.2.0", - "make-dir": "^2.1.0", - "slash": "^2.0.0" - }, - "bin": { - "babel": "bin/babel.js", - "babel-external-helpers": "bin/babel-external-helpers.js" - }, - "engines": { - "node": ">=6.9.0" - }, - "optionalDependencies": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", - "chokidar": "^3.4.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", - "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.9", - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-module-transforms": "^7.24.9", - "@babel/helpers": "^7.24.8", - "@babel/parser": "^7.24.8", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.9", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -165,34 +113,16 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/eslint-parser": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.22.9.tgz", - "integrity": "sha512-xdMkt39/nviO/4vpVdrEYPwXCsYIXSSAr6mC7WQsNIlGnuxKyKE7GZjalcnbSWiC4OXGNNN3UQPeHfjSC6sTDA==", - "license": "MIT", - "dependencies": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.11.0", - "eslint": "^7.5.0 || ^8.0.0" - } - }, "node_modules/@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -200,25 +130,26 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", - "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -228,17 +159,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", - "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.27.0", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "engines": { @@ -249,13 +181,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.0.tgz", - "integrity": "sha512-fO8l08T76v48BhpNRW/nQ0MxfnSdoSKUJBMjubOAYffsVuGG5qOfMq7N6Es7UJvi7Y8goXXo07EfcHZXDPuELQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.2.0", + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "engines": { @@ -266,56 +199,67 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", - "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -325,35 +269,37 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -363,14 +309,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -380,79 +327,81 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", - "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "license": "MIT", "dependencies": { - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.0" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -462,13 +411,14 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", - "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -477,13 +427,14 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -492,31 +443,14 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -525,41 +459,39 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.13.0" } }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { @@ -567,6 +499,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.9.0" }, @@ -625,37 +558,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", - "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -665,12 +575,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -704,12 +614,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -821,12 +731,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", - "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -840,6 +750,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -852,12 +763,13 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -867,14 +779,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", - "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.26.8" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -884,14 +797,15 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", - "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -901,12 +815,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", - "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -916,12 +831,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.0.tgz", - "integrity": "sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -931,13 +847,14 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -947,13 +864,14 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", - "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -963,17 +881,18 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "globals": "^11.1.0" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -983,13 +902,14 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -999,12 +919,14 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1014,13 +936,14 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1030,12 +953,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1044,13 +968,48 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1060,12 +1019,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", - "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1075,12 +1035,13 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1090,13 +1051,14 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz", - "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1106,14 +1068,15 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1123,12 +1086,13 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1138,12 +1102,13 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1153,12 +1118,13 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1168,12 +1134,13 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1183,13 +1150,14 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1199,13 +1167,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", - "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1215,15 +1184,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1233,13 +1203,14 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1249,13 +1220,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1265,12 +1237,13 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1280,12 +1253,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.26.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", - "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1295,12 +1269,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1310,14 +1285,17 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1327,13 +1305,14 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1343,12 +1322,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1358,13 +1338,14 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1374,12 +1355,13 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1389,13 +1371,14 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1405,14 +1388,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1422,12 +1406,13 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1436,13 +1421,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.9.tgz", - "integrity": "sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow==", + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1451,13 +1437,18 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", - "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz", + "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-jsx": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1466,17 +1457,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", - "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/plugin-transform-react-jsx": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1485,13 +1473,15 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", - "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1500,14 +1490,14 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", - "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1516,29 +1506,31 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.0.tgz", - "integrity": "sha512-LX/vCajUJQDqE7Aum/ELUMZAY19+cDpghxrnyt5I1tV6X5PyC86AOoWXWFYFeIvauyeSA6/ktn4tQVn/3ZifsA==", + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "regenerator-transform": "^0.15.2" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1548,12 +1540,13 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1563,13 +1556,14 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1579,12 +1573,13 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1594,12 +1589,13 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz", - "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1609,12 +1605,13 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.0.tgz", - "integrity": "sha512-+LLkxA9rKJpNoGsbLnAgOCdESl73vwYn+V6b+5wHbrE7OGKVDPHIQvbFSzqE6rwqaCw2RE+zdJrlLkcf8YOA0w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1624,16 +1621,17 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", - "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.27.0", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-syntax-typescript": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1643,12 +1641,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1658,13 +1657,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1674,13 +1674,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1690,13 +1691,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1706,91 +1708,81 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.8.tgz", - "integrity": "sha512-vObvMZB6hNWuDxhSaEPTKCwcqkAIuDtE+bQGn4XMXne1DSLzFVY8Vmj1bm+mUQXYNN8NmaQEO+r8MMbzPr1jBQ==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.24.8", - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-option": "^7.24.8", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", + "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.7", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.24.7", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoped-functions": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.24.7", - "@babel/plugin-transform-class-properties": "^7.24.7", - "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.24.8", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-dotall-regex": "^7.24.7", - "@babel/plugin-transform-duplicate-keys": "^7.24.7", - "@babel/plugin-transform-dynamic-import": "^7.24.7", - "@babel/plugin-transform-exponentiation-operator": "^7.24.7", - "@babel/plugin-transform-export-namespace-from": "^7.24.7", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.24.7", - "@babel/plugin-transform-json-strings": "^7.24.7", - "@babel/plugin-transform-literals": "^7.24.7", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-member-expression-literals": "^7.24.7", - "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-modules-systemjs": "^7.24.7", - "@babel/plugin-transform-modules-umd": "^7.24.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-new-target": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-object-super": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-property-literals": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-reserved-words": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.8", - "@babel/plugin-transform-unicode-escapes": "^7.24.7", - "@babel/plugin-transform-unicode-property-regex": "^7.24.7", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.37.1", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "engines": { @@ -1805,6 +1797,7 @@ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -1815,17 +1808,18 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz", - "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", + "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-transform-react-display-name": "^7.25.9", - "@babel/plugin-transform-react-jsx": "^7.25.9", - "@babel/plugin-transform-react-jsx-development": "^7.25.9", - "@babel/plugin-transform-react-pure-annotations": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.28.0", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1835,16 +1829,17 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.0.tgz", - "integrity": "sha512-vxaPFfJtHhgeOVXRKuHpHPAOgymmy8V8I65T1q53R7GCZlefKeCaTyDs3zOPHTTbmquvNlQYC5klEvWsBAtrBQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", - "@babel/plugin-transform-typescript": "^7.27.0" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1854,70 +1849,67 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.0.tgz", - "integrity": "sha512-UWjX6t+v+0ckwZ50Y5ShZLnlk95pP5MyW/pon9tiYzl3+18pkTHTFNTKr7rQbfRXPkowt2QAn30o1b6oswszew==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.0.tgz", + "integrity": "sha512-TgUkdp71C9pIbBcHudc+gXZnihEDOjUAmXO1VO4HHGES7QLZcShR0stfKIxLSNIYx2fqhmJChOjm/wkF8wv4gA==", "license": "MIT", + "peer": true, "dependencies": { - "core-js-pure": "^3.30.2", - "regenerator-runtime": "^0.14.0" + "core-js-pure": "^3.48.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1929,16 +1921,247 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "license": "MIT" }, - "node_modules/@cospired/i18n-iso-languages": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@cospired/i18n-iso-languages/-/i18n-iso-languages-4.2.0.tgz", - "integrity": "sha512-vy8cq1176MTxVwB1X9niQjcIYOH29F8Huxtx8hLmT5Uz3l1ztGDGri8KN/4zE7LV2mCT7JrcAoNV/I9yb+lNUw==", - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node_modules/@bufbuild/protobuf": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", + "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)", + "peer": true + }, + "node_modules/@bundled-es-modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-Rk453EklPUPC3NRWc3VUNI/SSUjdBaFoaQvFRmNBNtMHVtOFD5AntiWg5kEE1hqcPqedYFDzxE3ZcMYPcA195w==", + "license": "ISC", + "peer": true, + "dependencies": { + "deepmerge": "^4.3.1" } }, - "node_modules/@csstools/cascade-layer-name-parser": { + "node_modules/@bundled-es-modules/glob": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/glob/-/glob-10.4.2.tgz", + "integrity": "sha512-740y5ofkzydsFao5EXJrGilcIL6EFEw/cmPf2uhTw9J6G1YOhiIFjNFCHdpgEiiH5VlU3G0SARSjlFlimRRSMA==", + "hasInstallScript": true, + "license": "ISC", + "peer": true, + "dependencies": { + "buffer": "^6.0.3", + "events": "^3.3.0", + "glob": "^10.4.2", + "patch-package": "^8.0.0", + "path": "^0.12.7", + "stream": "^0.0.3", + "string_decoder": "^1.3.0", + "url": "^0.11.3" + } + }, + "node_modules/@bundled-es-modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "peer": true + }, + "node_modules/@bundled-es-modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@bundled-es-modules/glob/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@bundled-es-modules/glob/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "peer": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@bundled-es-modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@bundled-es-modules/memfs": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/memfs/-/memfs-4.17.0.tgz", + "integrity": "sha512-ykdrkEmQr9BV804yd37ikXfNnvxrwYfY9Z2/EtMHFEFadEjsQXJ1zL9bVZrKNLDtm91UdUOEHso6Aweg93K6xQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "assert": "^2.1.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "memfs": "^4.17.0", + "path": "^0.12.7", + "stream": "^0.0.3", + "util": "^0.12.5" + } + }, + "node_modules/@bundled-es-modules/memfs/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@bundled-es-modules/memfs/node_modules/memfs": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.56.11.tgz", + "integrity": "sha512-/GodtwVeKVIHZKLUSr2ZdOxKBC5hHki4JNCU22DoCGPEHr5o2PD5U721zvESKyWwCfTfavFl9WZYgA13OAYK0g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/fs-core": "4.56.11", + "@jsonjoy.com/fs-fsa": "4.56.11", + "@jsonjoy.com/fs-node": "4.56.11", + "@jsonjoy.com/fs-node-builtins": "4.56.11", + "@jsonjoy.com/fs-node-to-fsa": "4.56.11", + "@jsonjoy.com/fs-node-utils": "4.56.11", + "@jsonjoy.com/fs-print": "4.56.11", + "@jsonjoy.com/fs-snapshot": "4.56.11", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@bundled-es-modules/postcss-calc-ast-parser": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/postcss-calc-ast-parser/-/postcss-calc-ast-parser-0.1.6.tgz", + "integrity": "sha512-y65TM5zF+uaxo9OeekJ3rxwTINlQvrkbZLogYvQYVoLtxm4xEiHfZ7e/MyiWbStYyWZVZkVqsaVU6F4SUK5XUA==", + "license": "ISC", + "peer": true, + "dependencies": { + "postcss-calc-ast-parser": "^0.1.4" + } + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.2.0.tgz", + "integrity": "sha512-ssJFvn/UXhQQeICw3SR/fZPmYVj+JM2mP+Lx7bZ51cOeHaMWOKp3AUMuyM3QR82aFFXTfcAp67P5GpPjGmbZWQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@chevrotain/gast": "11.2.0", + "@chevrotain/types": "11.2.0", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.2.0.tgz", + "integrity": "sha512-c+KoD6eSI1xjAZZoNUW+V0l13UEn+a4ShmUrjIKs1BeEWCji0Kwhmqn5FSx1K4BhWL7IQKlV7wLR4r8lLArORQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@chevrotain/types": "11.2.0", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.2.0.tgz", + "integrity": "sha512-lG73pBFqbXODTbXhdZwv0oyUaI+3Irm+uOv5/W79lI3g5hasYaJnVJOm3H2NkhA0Ef4XLBU4Scr7TJDJwgFkAw==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@chevrotain/types": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.2.0.tgz", + "integrity": "sha512-vBMSj/lz/LqolbGQEHB0tlpW5BnljHVtp+kzjQfQU+5BtGMTuZCPVgaAjtKvQYXnHb/8i/02Kii00y0tsuwfsw==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@chevrotain/utils": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.2.0.tgz", + "integrity": "sha512-+7whECg4yNWHottjvr2To2BRxL4XJVjIyyv5J4+bJ0iMOVU8j/8n1qPDLZS/90W/BObDR8VNL46lFbzY/Hosmw==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@csstools/cascade-layer-name-parser": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.13.tgz", "integrity": "sha512-MX0yLTwtZzr82sQ0zOjqimpZbzjMaK/h2pmlrLK7DCzlmiZLYFpoO94WmN1akRVo6ll/TdpHb53vihHLUMyvng==", @@ -1953,6 +2176,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18" }, @@ -1976,6 +2200,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18" }, @@ -1998,6 +2223,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18" } @@ -2017,6 +2243,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18" }, @@ -2030,476 +2257,202 @@ "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" } }, - "node_modules/@edx/brand": { - "name": "@openedx/brand-openedx", - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@openedx/brand-openedx/-/brand-openedx-1.2.3.tgz", - "integrity": "sha512-Dn9CtpC8fovh++Xi4NF5NJoeR9yU2yXZnV9IujxIyGd/dn0Phq5t6dzJVfupwq09mpDnzJv7egA8Znz/3ljO+w==", - "license": "GPL-3.0-or-later" - }, "node_modules/@edx/browserslist-config": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@edx/browserslist-config/-/browserslist-config-1.5.0.tgz", - "integrity": "sha512-d2ggwi5j4DOBJOwhWZxBWQSDR0DhT4ke/1PbzRauICdFkuOyax+PsFjK8GUh443K2OaQpy9PGfiCzZ1Yg37AUA==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@edx/browserslist-config/-/browserslist-config-1.5.1.tgz", + "integrity": "sha512-r2zinEBFUqmh3iLkAb1RYwKDA0sQXjkP8OSl8dkE3Y+DnJwFIb1Yr1diY34vSwSQO5bB15OeLplFqQkbbPNpbA==", + "dev": true, "license": "AGPL-3.0" }, - "node_modules/@edx/eslint-config": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@edx/eslint-config/-/eslint-config-4.3.0.tgz", - "integrity": "sha512-4W9wFG4ALr3xocakCsncgJbK67RHfSmDwHDXKHReFtjxl/FRkxhS6qayz189oChqfANieeV3zRCLaq44bLf+/A==", - "license": "MIT", - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.62.0", - "@typescript-eslint/parser": "^5.62.0", - "eslint": "^7.32.0 || ^8.2.0", - "eslint-config-airbnb": "^18.0.1 || ^19.0.0", - "eslint-config-airbnb-typescript": "^17.0.0", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-react": "^7.18.0", - "eslint-plugin-react-hooks": "^1.7.0 || ^4.0.0" + "node_modules/@edx/new-relic-source-map-webpack-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@edx/new-relic-source-map-webpack-plugin/-/new-relic-source-map-webpack-plugin-2.1.0.tgz", + "integrity": "sha512-OrlvtdsPcWuOm6NBWfUxFE06qdPiu2bf9nU4I9t8Lu7WW6NsosAB5hxm5U+MBMZP2AuVl3FAt0k0lZsu3+ri8Q==", + "license": "AGPL-3.0", + "peer": true, + "dependencies": { + "@newrelic/publish-sourcemap": "^5.0.1" } }, - "node_modules/@edx/frontend-component-footer": { - "version": "14.9.0", - "resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-14.9.0.tgz", - "integrity": "sha512-eI3ffSvAKDoP1K/vBuQrCL7ca0U7hJqRlGjBq62dSAlsgOS59t+9T1ZkIWCSoRUzHLv4fS+taiBeCsIQVV356Q==", + "node_modules/@edx/openedx-atlas": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@edx/openedx-atlas/-/openedx-atlas-0.7.0.tgz", + "integrity": "sha512-jqv0IV1pHsSn9+RO8Rdsr8jm3SOd84CCzzmo2QC9yvh1MK1+p4YDURQLpmmgKJ0JzE5Cb6ImhnNL/ogpJ2wetQ==", "license": "AGPL-3.0", - "dependencies": { - "@fortawesome/fontawesome-svg-core": "6.7.2", - "@fortawesome/free-brands-svg-icons": "6.7.2", - "@fortawesome/free-regular-svg-icons": "6.7.2", - "@fortawesome/free-solid-svg-icons": "6.7.2", - "@fortawesome/react-fontawesome": "0.2.2", - "@openedx/frontend-plugin-framework": "^1.7.0", - "classnames": "^2.5.1", - "jest-environment-jsdom": "^29.7.0", - "lodash": "^4.17.21", - "ts-jest": "^29.1.2" - }, - "peerDependencies": { - "@edx/frontend-platform": "^7.0.0 || ^8.0.0", - "@openedx/paragon": ">= 21.11.3 < 24.0.0", - "prop-types": "^15.5.10", - "react": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0" + "bin": { + "atlas": "atlas" } }, - "node_modules/@edx/frontend-component-footer/node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", - "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "license": "MIT", - "engines": { - "node": ">=6" + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@edx/frontend-component-footer/node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", - "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "license": "MIT", + "peer": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.7.2" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@edx/frontend-component-footer/node_modules/@fortawesome/free-brands-svg-icons": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.7.2.tgz", - "integrity": "sha512-zu0evbcRTgjKfrr77/2XX+bU+kuGfjm0LbajJHVIgBWNIDzrhpRxiCPNT8DW5AdmSsq7Mcf9D1bH0aSeSUSM+Q==", - "license": "(CC-BY-4.0 AND MIT)", - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.7.2" + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "license": "MIT", + "peer": true, "engines": { - "node": ">=6" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@edx/frontend-component-footer/node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", - "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", - "license": "(CC-BY-4.0 AND MIT)", + "node_modules/@eslint/compat": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.4.1.tgz", + "integrity": "sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.7.2" + "@eslint/core": "^0.17.0" }, "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^8.40 || 9" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/@edx/frontend-component-header": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-6.4.0.tgz", - "integrity": "sha512-RNV3XRXhhN9QlhAoP26CjzoRIPlLSYDp3PZCnK6g6kIHgxC9dCpu2PTZdxV2AVChqVuxtZK5zLbk9yeAtf4U/A==", - "license": "AGPL-3.0", + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@fortawesome/fontawesome-svg-core": "6.6.0", - "@fortawesome/free-brands-svg-icons": "6.6.0", - "@fortawesome/free-regular-svg-icons": "6.6.0", - "@fortawesome/free-solid-svg-icons": "6.6.0", - "@fortawesome/react-fontawesome": "^0.2.0", - "@openedx/frontend-plugin-framework": "^1.7.0", - "axios-mock-adapter": "1.22.0", - "babel-polyfill": "6.26.0", - "classnames": "^2.5.1", - "jest-environment-jsdom": "^29.7.0", - "react-responsive": "8.2.0", - "react-transition-group": "4.4.5" + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" }, - "peerDependencies": { - "@edx/frontend-platform": "^7.0.0 || ^8.0.0", - "@openedx/paragon": ">= 22.0.0 < 24.0.0", - "prop-types": "^15.5.10", - "react": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react-router-dom": "^6.14.2" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@edx/frontend-component-header/node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", - "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT", - "engines": { - "node": ">=6" - } + "peer": true }, - "node_modules/@edx/frontend-component-header/node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", - "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", + "peer": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.6.0" - }, - "engines": { - "node": ">=6" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@edx/frontend-component-header/node_modules/@fortawesome/free-brands-svg-icons": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz", - "integrity": "sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==", - "license": "(CC-BY-4.0 AND MIT)", + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "peer": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.6.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=6" + "node": "*" } }, - "node_modules/@edx/frontend-component-header/node_modules/@fortawesome/free-regular-svg-icons": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz", - "integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==", - "license": "(CC-BY-4.0 AND MIT)", + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.6.0" + "@eslint/core": "^0.17.0" }, "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@edx/frontend-component-header/node_modules/@fortawesome/free-solid-svg-icons": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", - "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", - "license": "(CC-BY-4.0 AND MIT)", + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.6.0" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": ">=6" - } - }, - "node_modules/@edx/frontend-enterprise-hotjar": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@edx/frontend-enterprise-hotjar/-/frontend-enterprise-hotjar-7.2.0.tgz", - "integrity": "sha512-orE/Tp1x+hFSlr5PSJEcJ+7q6Htx7vk0DcBK4oGTKdFMrXhSpnyS+yba6CrQCQ92JiunufYw5CcR/nDjTYk5mg==", - "license": "AGPL-3.0", - "peerDependencies": { - "react": "^16.12.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.12.0 || ^17.0.0 || ^18.0.0", - "react-router-dom": "^6.0.0" - } - }, - "node_modules/@edx/frontend-platform": { - "version": "8.3.9", - "resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-8.3.9.tgz", - "integrity": "sha512-hviTsYHvGJmIf3z57MeGYin2Egh3ildA01usXCfnh2VskrtKFo32NU4x8/7GJiEoGa+oYCSUrO6LMqdKcXQ/vA==", - "license": "AGPL-3.0", - "dependencies": { - "@cospired/i18n-iso-languages": "4.2.0", - "@formatjs/intl-pluralrules": "4.3.3", - "@formatjs/intl-relativetimeformat": "10.0.1", - "axios": "1.9.0", - "axios-cache-interceptor": "1.8.0", - "form-urlencoded": "4.1.4", - "glob": "7.2.3", - "history": "4.10.1", - "i18n-iso-countries": "4.3.1", - "jwt-decode": "3.1.2", - "localforage": "1.10.0", - "localforage-memoryStorageDriver": "0.9.2", - "lodash.camelcase": "4.3.0", - "lodash.memoize": "4.1.2", - "lodash.merge": "4.6.2", - "lodash.snakecase": "4.1.1", - "pubsub-js": "1.9.5", - "react-intl": "6.8.9", - "universal-cookie": "4.0.4" - }, - "bin": { - "intl-imports.js": "i18n/scripts/intl-imports.js", - "transifex-utils.js": "i18n/scripts/transifex-utils.js" - }, - "peerDependencies": { - "@openedx/frontend-build": ">= 14.0.0", - "@openedx/paragon": ">= 21.5.7 < 24.0.0", - "prop-types": ">=15.7.2 <16.0.0", - "react": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react-redux": "^7.1.1 || ^8.1.1", - "react-router-dom": "^6.0.0", - "redux": "^4.0.4" - }, - "peerDependenciesMeta": { - "@openedx/frontend-build": { - "optional": true, - "reason": "This package is only a peer dependency to ensure using a minimum compatible version that provides env.config and PARAGON_THEME support. It is not needed at runtime, and may be omitted with `--omit=optional`." - } - } - }, - "node_modules/@edx/frontend-platform/node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "node_modules/@edx/new-relic-source-map-webpack-plugin": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@edx/new-relic-source-map-webpack-plugin/-/new-relic-source-map-webpack-plugin-2.1.0.tgz", - "integrity": "sha512-OrlvtdsPcWuOm6NBWfUxFE06qdPiu2bf9nU4I9t8Lu7WW6NsosAB5hxm5U+MBMZP2AuVl3FAt0k0lZsu3+ri8Q==", - "license": "AGPL-3.0", - "dependencies": { - "@newrelic/publish-sourcemap": "^5.0.1" - } - }, - "node_modules/@edx/openedx-atlas": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@edx/openedx-atlas/-/openedx-atlas-0.7.0.tgz", - "integrity": "sha512-jqv0IV1pHsSn9+RO8Rdsr8jm3SOd84CCzzmo2QC9yvh1MK1+p4YDURQLpmmgKJ0JzE5Cb6ImhnNL/ogpJ2wetQ==", - "license": "AGPL-3.0", - "bin": { - "atlas": "atlas" - } - }, - "node_modules/@edx/react-unit-test-utils": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@edx/react-unit-test-utils/-/react-unit-test-utils-4.0.0.tgz", - "integrity": "sha512-QlVYhYD9L2bzx1eAtf8BbCJr00ek9rrHrG+/pW2bVSt+t0uvKHQpX1CNdMrDePv18DsMeC7IOB00t8ZIn4mi7w==", - "license": "AGPL-3.0", - "dependencies": { - "@edx/browserslist-config": "^1.1.1", - "@reduxjs/toolkit": "^1.5.1", - "@testing-library/dom": "^10.4.0", - "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^16.2.0", - "classnames": "^2.2.6", - "core-js": "3.6.5", - "lodash": "^4.17.21", - "react-dev-utils": "^12.0.1", - "react-test-renderer": "^18.3.1" - }, - "peerDependencies": { - "@edx/frontend-platform": "^8.3.1", - "@openedx/frontend-build": "^14.3.0", - "@openedx/paragon": "^22.0.0 || ^23.0.0", - "react": "^18.0.0" - } - }, - "node_modules/@edx/react-unit-test-utils/node_modules/@reduxjs/toolkit": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz", - "integrity": "sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==", - "license": "MIT", - "dependencies": { - "immer": "^9.0.21", - "redux": "^4.2.1", - "redux-thunk": "^2.4.2", - "reselect": "^4.1.8" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18", - "react-redux": "^7.2.1 || ^8.0.2" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-redux": { - "optional": true - } - } - }, - "node_modules/@edx/react-unit-test-utils/node_modules/core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/@edx/react-unit-test-utils/node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/@edx/reactifex": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@edx/reactifex/-/reactifex-2.2.0.tgz", - "integrity": "sha512-vyGDtx3BwCr6Gjbm4y6gJ8Bzc2TOSNBlBa2hMerz59HoXaot14MihxxiDU+JDNybGLLcKDBiK511bOi/77i1lw==", - "dev": true, - "license": "MIT", - "dependencies": { - "axios": "^0.21.1", - "yargs": "^17.1.1" - }, - "bin": { - "edx_reactifex": "main.js" - } - }, - "node_modules/@edx/reactifex/node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, - "node_modules/@edx/typescript-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@edx/typescript-config/-/typescript-config-1.1.0.tgz", - "integrity": "sha512-HF+7dsSgA2YQ6f/qV4HnrEYBoIhIdxVQZgDyYk/YGvaVGqT6IFuaHnYUP7ImpCUMOUmx/Jl7EyuVeaMe2LrMcA==", - "license": "MIT", - "peerDependencies": { - "typescript": "^4.9.4" - } - }, - "node_modules/@emnapi/core": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", - "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.0.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", - "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", - "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", + "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", "license": "MIT", + "peer": true, "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.3", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2509,28 +2462,46 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" + "license": "Python-2.0", + "peer": true }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT", + "peer": true + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "peer": true, "dependencies": { - "type-fest": "^0.20.2" - }, + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "license": "MIT", + "peer": true, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", + "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -2538,47 +2509,73 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "*" } }, "node_modules/@eslint/js": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", - "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", + "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", "license": "MIT", + "peer": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@formatjs/cli": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/@formatjs/cli/-/cli-6.6.4.tgz", - "integrity": "sha512-VSDPsT7AO/mtth1rEBwl97Us5dMgqZpI8v7QJXakB4f90pDJsqHBdBeTbjHYrlFr2WvBLVo3/6mGPw9DeX7PUg==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@formatjs/cli/-/cli-6.13.0.tgz", + "integrity": "sha512-bl4+FNg7S6RPNa9cSAE8HqdXu84n7LpzDdkDAPqS0sk58XNbY/1Le6GdWqCKzELWX+FhI58gyZtZecmWsZ+Bhg==", "license": "MIT", + "peer": true, "bin": { "formatjs": "bin/formatjs" }, "engines": { - "node": ">= 16" + "node": ">= 20.12.0" }, "peerDependencies": { - "@glimmer/env": "^0.1.7", - "@glimmer/reference": "^0.94.0", - "@glimmer/syntax": "^0.94.9", - "@glimmer/validator": "^0.94.0", - "@vue/compiler-core": "^3.5.12", - "content-tag": "^3.0.0", - "ember-template-recast": "^6.1.5", - "vue": "^3.5.12" + "@glimmer/syntax": "^0.84.3 || ^0.95.0", + "@vue/compiler-core": "3.5.27", + "content-tag": "^4.1.0", + "vue": "3.5.27" }, "peerDependenciesMeta": { "@glimmer/env": { @@ -2599,111 +2596,54 @@ "content-tag": { "optional": true }, - "ember-template-recast": { - "optional": true - }, "vue": { "optional": true } } }, "node_modules/@formatjs/ecma402-abstract": { - "version": "1.11.4", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz", - "integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==", - "license": "MIT", - "dependencies": { - "@formatjs/intl-localematcher": "0.2.25", - "tslib": "^2.1.0" - } - }, - "node_modules/@formatjs/fast-memoize": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz", - "integrity": "sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==", - "license": "MIT", - "dependencies": { - "tslib": "2" - } - }, - "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz", - "integrity": "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==", - "license": "MIT", - "dependencies": { - "@formatjs/ecma402-abstract": "2.3.4", - "@formatjs/icu-skeleton-parser": "1.8.14", - "tslib": "^2.8.0" - } - }, - "node_modules/@formatjs/icu-messageformat-parser/node_modules/@formatjs/ecma402-abstract": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz", - "integrity": "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz", + "integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/fast-memoize": "2.2.7", - "@formatjs/intl-localematcher": "0.6.1", + "@formatjs/intl-localematcher": "0.6.2", "decimal.js": "^10.4.3", "tslib": "^2.8.0" } }, - "node_modules/@formatjs/icu-messageformat-parser/node_modules/@formatjs/fast-memoize": { + "node_modules/@formatjs/fast-memoize": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.8.0" } }, - "node_modules/@formatjs/icu-messageformat-parser/node_modules/@formatjs/intl-localematcher": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz", - "integrity": "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==", + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz", + "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==", "license": "MIT", + "peer": true, "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", + "@formatjs/icu-skeleton-parser": "1.8.16", "tslib": "^2.8.0" } }, "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.14", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz", - "integrity": "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==", - "license": "MIT", - "dependencies": { - "@formatjs/ecma402-abstract": "2.3.4", - "tslib": "^2.8.0" - } - }, - "node_modules/@formatjs/icu-skeleton-parser/node_modules/@formatjs/ecma402-abstract": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz", - "integrity": "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==", - "license": "MIT", - "dependencies": { - "@formatjs/fast-memoize": "2.2.7", - "@formatjs/intl-localematcher": "0.6.1", - "decimal.js": "^10.4.3", - "tslib": "^2.8.0" - } - }, - "node_modules/@formatjs/icu-skeleton-parser/node_modules/@formatjs/fast-memoize": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", - "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", - "license": "MIT", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@formatjs/icu-skeleton-parser/node_modules/@formatjs/intl-localematcher": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz", - "integrity": "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==", + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz", + "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==", "license": "MIT", + "peer": true, "dependencies": { + "@formatjs/ecma402-abstract": "2.3.6", "tslib": "^2.8.0" } }, @@ -2712,6 +2652,7 @@ "resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.10.15.tgz", "integrity": "sha512-i6+xVqT+6KCz7nBfk4ybMXmbKO36tKvbMKtgFz9KV+8idYFyFbfwKooYk8kGjyA5+T5f1kEPQM5IDLXucTAQ9g==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/ecma402-abstract": "2.2.4", "@formatjs/fast-memoize": "2.2.3", @@ -2735,6 +2676,7 @@ "resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.8.5.tgz", "integrity": "sha512-85b+GdAKCsleS6cqVxf/Aw/uBd+20EM0wDpgaxzHo3RIR3bxF4xCJqH/Grbzx8CXurTgDDZHPdPdwJC+May41w==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/ecma402-abstract": "2.2.4", "@formatjs/intl-localematcher": "0.5.8", @@ -2746,17 +2688,29 @@ "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.4.tgz", "integrity": "sha512-lFyiQDVvSbQOpU+WFd//ILolGj4UgA/qXrKeZxdV14uKiAUiPAtX6XAn7WBCRi7Mx6I7EybM9E5yYn4BIpZWYg==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/fast-memoize": "2.2.3", "@formatjs/intl-localematcher": "0.5.8", "tslib": "2" } }, - "node_modules/@formatjs/intl-displaynames/node_modules/@formatjs/intl-localematcher": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.8.tgz", + "node_modules/@formatjs/intl-displaynames/node_modules/@formatjs/fast-memoize": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz", + "integrity": "sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "2" + } + }, + "node_modules/@formatjs/intl-displaynames/node_modules/@formatjs/intl-localematcher": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.8.tgz", "integrity": "sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "2" } @@ -2766,6 +2720,7 @@ "resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.7.5.tgz", "integrity": "sha512-Wzes10SMNeYgnxYiKsda4rnHP3Q3II4XT2tZyOgnH5fWuHDtIkceuWlRQNsvrI3uiwP4hLqp2XdQTCsfkhXulg==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/ecma402-abstract": "2.2.4", "@formatjs/intl-localematcher": "0.5.8", @@ -2777,50 +2732,41 @@ "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.4.tgz", "integrity": "sha512-lFyiQDVvSbQOpU+WFd//ILolGj4UgA/qXrKeZxdV14uKiAUiPAtX6XAn7WBCRi7Mx6I7EybM9E5yYn4BIpZWYg==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/fast-memoize": "2.2.3", "@formatjs/intl-localematcher": "0.5.8", "tslib": "2" } }, + "node_modules/@formatjs/intl-listformat/node_modules/@formatjs/fast-memoize": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz", + "integrity": "sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "2" + } + }, "node_modules/@formatjs/intl-listformat/node_modules/@formatjs/intl-localematcher": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.8.tgz", "integrity": "sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "2" } }, "node_modules/@formatjs/intl-localematcher": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz", - "integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@formatjs/intl-pluralrules": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@formatjs/intl-pluralrules/-/intl-pluralrules-4.3.3.tgz", - "integrity": "sha512-NLZN8gf2qLpCuc0m565IbKLNUarEGOzk0mkdTkE4XTuNCofzoQTurW6lL3fmDlneAoYl2FiTdHa5q4o2vZF50g==", - "license": "MIT", - "dependencies": { - "@formatjs/ecma402-abstract": "1.11.4", - "@formatjs/intl-localematcher": "0.2.25", - "tslib": "^2.1.0" - } - }, - "node_modules/@formatjs/intl-relativetimeformat": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-10.0.1.tgz", - "integrity": "sha512-AABPQtPjFilXegQsnmVHrSlzjFNUffAEk5DgowY6b7WSwDI7g2W6QgW903/lbZ58emhphAbgHdtKeUBXqTiLpw==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz", + "integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==", "license": "MIT", + "peer": true, "dependencies": { - "@formatjs/ecma402-abstract": "1.11.4", - "@formatjs/intl-localematcher": "0.2.25", - "tslib": "^2.1.0" + "tslib": "^2.8.0" } }, "node_modules/@formatjs/intl/node_modules/@formatjs/ecma402-abstract": { @@ -2828,17 +2774,29 @@ "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.4.tgz", "integrity": "sha512-lFyiQDVvSbQOpU+WFd//ILolGj4UgA/qXrKeZxdV14uKiAUiPAtX6XAn7WBCRi7Mx6I7EybM9E5yYn4BIpZWYg==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/fast-memoize": "2.2.3", "@formatjs/intl-localematcher": "0.5.8", "tslib": "2" } }, + "node_modules/@formatjs/intl/node_modules/@formatjs/fast-memoize": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz", + "integrity": "sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "2" + } + }, "node_modules/@formatjs/intl/node_modules/@formatjs/icu-messageformat-parser": { "version": "2.9.4", "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.4.tgz", "integrity": "sha512-Tbvp5a9IWuxUcpWNIW6GlMQYEc4rwNHR259uUFoKWNN1jM9obf9Ul0e+7r7MvFOBNcN+13K7NuKCKqQiAn1QEg==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/ecma402-abstract": "2.2.4", "@formatjs/icu-skeleton-parser": "1.8.8", @@ -2850,6 +2808,7 @@ "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.8.tgz", "integrity": "sha512-vHwK3piXwamFcx5YQdCdJxUQ1WdTl6ANclt5xba5zLGDv5Bsur7qz8AD7BevaKxITwpgDeU0u8My3AIibW9ywA==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/ecma402-abstract": "2.2.4", "tslib": "2" @@ -2860,21 +2819,22 @@ "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.8.tgz", "integrity": "sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "2" } }, "node_modules/@formatjs/ts-transformer": { - "version": "3.13.34", - "resolved": "https://registry.npmjs.org/@formatjs/ts-transformer/-/ts-transformer-3.13.34.tgz", - "integrity": "sha512-N1n7dA+6dfHn/LDQXrUPOC90Z+7dsDB5cQIJeIysVwGhk8PRyYo2eaotDszZMjQp8+hvs98kJyekn1X7mK1yHQ==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/@formatjs/ts-transformer/-/ts-transformer-3.14.2.tgz", + "integrity": "sha512-c47ij+2Xi4jMDO3Hz01BDF3yB4575Gkoq24sFzVw1K1kpHvITsFfdlXQbhxScBwJi2gBhMpuZ++XsTUZ9O0Law==", "license": "MIT", + "peer": true, "dependencies": { - "@formatjs/icu-messageformat-parser": "2.11.2", - "@types/json-stable-stringify": "^1.1.0", + "@formatjs/icu-messageformat-parser": "2.11.4", "@types/node": "^22.0.0", "chalk": "^4.1.2", - "json-stable-stringify": "^1.1.1", + "json-stable-stringify": "^1.3.0", "tslib": "^2.8.0", "typescript": "^5.6.0" }, @@ -2887,19 +2847,6 @@ } } }, - "node_modules/@formatjs/ts-transformer/node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "0.2.36", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", @@ -2936,27 +2883,6 @@ "node": ">=6" } }, - "node_modules/@fortawesome/free-regular-svg-icons": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz", - "integrity": "sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==", - "license": "(CC-BY-4.0 AND MIT)", - "dependencies": { - "@fortawesome/fontawesome-common-types": "6.7.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-regular-svg-icons/node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", - "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/@fortawesome/free-solid-svg-icons": { "version": "5.15.4", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", @@ -2971,43 +2897,41 @@ } }, "node_modules/@fortawesome/react-fontawesome": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", - "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.6.tgz", + "integrity": "sha512-mtBFIi1UsYQo7rYonYFkjgYKGoL8T+fEH6NGUpvuqtY3ytMsAoDaPo5rk25KuMtKDipY4bGYM/CkmCHA1N3FUg==", + "deprecated": "v0.2.x is no longer supported. Unless you are still using FontAwesome 5, please update to v3.1.1 or greater.", "license": "MIT", "dependencies": { "prop-types": "^15.8.1" }, "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~1 || ~6", - "react": ">=16.3" + "@fortawesome/fontawesome-svg-core": "~1 || ~6 || ~7", + "react": "^16.3 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@fullhuman/postcss-purgecss": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-5.0.0.tgz", - "integrity": "sha512-onDS/b/2pMRzqSoj4qOs2tYFmOpaspjTAgvACIHMPiicu1ptajiBruTrjBzTKdxWdX0ldaBb7wj8nEaTLyFkJw==", - "license": "MIT", - "dependencies": { - "purgecss": "^5.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -3015,6 +2939,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=12.22" }, @@ -3023,161 +2948,814 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "license": "BSD-3-Clause" - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "license": "MIT", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" + "node": ">=18.18" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jest/console/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "license": "MIT", + "peer": true, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "funding": { + "url": "https://opencollective.com/libvips" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, "engines": { - "node": ">=10" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "url": "https://opencollective.com/libvips" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" } }, - "node_modules/@jest/core/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } }, - "node_modules/@jest/core/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "peer": true, + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/core/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -3282,252 +3860,643 @@ "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", + "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-core": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.56.11.tgz", + "integrity": "sha512-wThHjzUp01ImIjfCwhs+UnFkeGPFAymwLEkOtenHewaKe2pTP12p6r1UuwikA9NEvNf9Vlck92r8fb8n/MWM5w==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.56.11", + "@jsonjoy.com/fs-node-utils": "4.56.11", + "thingies": "^2.5.0" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "license": "BSD-3-Clause", + "node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.56.11.tgz", + "integrity": "sha512-ZYlF3XbMayyp97xEN8ZvYutU99PCHjM64mMZvnCseXkCJXJDVLAwlF8Q/7q/xiWQRsv3pQBj1WXHd9eEyYcaCQ==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" + "@jsonjoy.com/fs-core": "4.56.11", + "@jsonjoy.com/fs-node-builtins": "4.56.11", + "@jsonjoy.com/fs-node-utils": "4.56.11", + "thingies": "^2.5.0" }, "engines": { - "node": ">=10" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jest/reporters/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/@jsonjoy.com/fs-node": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.56.11.tgz", + "integrity": "sha512-D65YrnP6wRuZyEWoSFnBJSr5zARVpVBGctnhie4rCsMuGXNzX7IHKaOt85/Aj7SSoG1N2+/xlNjWmkLvZ2H3Tg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/fs-core": "4.56.11", + "@jsonjoy.com/fs-node-builtins": "4.56.11", + "@jsonjoy.com/fs-node-utils": "4.56.11", + "@jsonjoy.com/fs-print": "4.56.11", + "@jsonjoy.com/fs-snapshot": "4.56.11", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" }, "engines": { - "node": ">=10" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jest/reporters/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", + "node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.56.11.tgz", + "integrity": "sha512-CNmt3a0zMCIhniFLXtzPWuUxXFU+U+2VyQiIrgt/rRVeEJNrMQUABaRbVxR0Ouw1LyR9RjaEkPM6nYpED+y43A==", + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "license": "MIT", + "node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.56.11.tgz", + "integrity": "sha512-5OzGdvJDgZVo+xXWEYo72u81zpOWlxlbG4d4nL+hSiW+LKlua/dldNgPrpWxtvhgyntmdFQad2UTxFyGjJAGhA==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jsonjoy.com/fs-fsa": "4.56.11", + "@jsonjoy.com/fs-node-builtins": "4.56.11", + "@jsonjoy.com/fs-node-utils": "4.56.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "license": "MIT", + "node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.56.11.tgz", + "integrity": "sha512-JADOZFDA3wRfsuxkT0+MYc4F9hJO2PYDaY66kRTG6NqGX3+bqmKu66YFYAbII/tEmQWPZeHoClUB23rtQM9UPg==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@jsonjoy.com/fs-node-builtins": "4.56.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "license": "MIT", + "node_modules/@jsonjoy.com/fs-print": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.56.11.tgz", + "integrity": "sha512-rnaKRgCRIn8JGTjxhS0JPE38YM3Pj/H7SW4/tglhIPbfKEkky7dpPayNKV2qy25SZSL15oFVgH/62dMZ/z7cyA==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "@jsonjoy.com/fs-node-utils": "4.56.11", + "tree-dump": "^1.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "license": "MIT", + "node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.56.11", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.56.11.tgz", + "integrity": "sha512-IIldPX+cIRQuUol9fQzSS3hqyECxVpYMJQMqdU3dCKZFRzEl1rkIkw4P6y7Oh493sI7YdxZlKr/yWdzEWZ1wGQ==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.56.11", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jest/test-sequencer/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "license": "MIT", + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jest/transform/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, "engines": { - "node": ">=8" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", + "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "license": "MIT", + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", + "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.2.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.2", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", + "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "peer": true, "engines": { - "node": ">=6.0.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "license": "MIT", + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", + "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT" - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz", - "integrity": "sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==", "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.0", - "@emnapi/runtime": "^1.4.0", - "@tybys/wasm-util": "^0.9.0" - } + "peer": true }, "node_modules/@newrelic/publish-sourcemap": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/@newrelic/publish-sourcemap/-/publish-sourcemap-5.1.4.tgz", "integrity": "sha512-35Nm26FxnVi7Nrfrl7nMHObIrHlkCStIPul/fQnru7RBlZIZiYKeGA9eEKBuho1ccoFK783w12nuntSV6wohlg==", "license": "New Relic proprietary", + "peer": true, "dependencies": { "superagent": "^10.1.0", "yargs": "^16.0.3" @@ -3538,82 +4507,12 @@ "publish-sourcemap": "scripts/publish-cli.js" } }, - "node_modules/@newrelic/publish-sourcemap/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/@newrelic/publish-sourcemap/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@newrelic/publish-sourcemap/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@newrelic/publish-sourcemap/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents.3", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", - "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", - "license": "MIT", - "optional": true - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "license": "MIT", - "dependencies": { - "eslint-scope": "5.1.1" - } - }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -3656,231 +4555,113 @@ "node": ">= 8" } }, - "node_modules/@openedx/frontend-build": { - "version": "14.6.0", - "resolved": "https://registry.npmjs.org/@openedx/frontend-build/-/frontend-build-14.6.0.tgz", - "integrity": "sha512-lQn/IYC2xZxmYtm9AsrEXm7o3Ei0voZNTKFDGiBbZARsh27b2/e8hx3ToJ4B3rwxnSHLNYbxVYqAWSiAXG06dw==", + "node_modules/@openedx/frontend-base": { + "version": "1.0.0-alpha.42", + "resolved": "https://registry.npmjs.org/@openedx/frontend-base/-/frontend-base-1.0.0-alpha.42.tgz", + "integrity": "sha512-3qpoazcIqYc9xqujG/MBs4dNpWGOcGC26+UdTcr5Q2+S08NfEyqg0kUOJTVKh6JjW1F3/RuUGDvrxKtmOWgs2g==", "license": "AGPL-3.0", + "peer": true, "dependencies": { - "@babel/cli": "7.24.8", - "@babel/core": "7.24.9", - "@babel/eslint-parser": "7.22.9", - "@babel/plugin-proposal-class-properties": "7.18.6", - "@babel/plugin-proposal-object-rest-spread": "7.20.7", - "@babel/plugin-syntax-dynamic-import": "7.8.3", - "@babel/preset-env": "7.24.8", - "@babel/preset-react": "7.26.3", - "@edx/eslint-config": "^4.3.0", + "@babel/core": "^7.24.9", + "@babel/preset-env": "^7.24.8", + "@babel/preset-react": "^7.26.3", + "@babel/preset-typescript": "^7.24.7", "@edx/new-relic-source-map-webpack-plugin": "2.1.0", - "@edx/typescript-config": "1.1.0", + "@eslint/compat": "^1.2.1", + "@eslint/js": "^9.13.0", "@formatjs/cli": "^6.0.3", - "@fullhuman/postcss-purgecss": "5.0.0", - "@pmmmwh/react-refresh-webpack-plugin": "0.5.15", - "@svgr/webpack": "8.1.0", - "@types/jest": "29.5.12", - "@typescript-eslint/eslint-plugin": "^5.58.0", - "@typescript-eslint/parser": "^5.58.0", - "autoprefixer": "10.4.20", - "babel-jest": "29.7.0", - "babel-loader": "9.2.1", - "babel-plugin-formatjs": "^10.4.0", - "babel-plugin-transform-imports": "2.0.0", - "babel-polyfill": "6.26.0", - "chalk": "4.1.2", - "clean-webpack-plugin": "4.0.0", - "css-loader": "5.2.7", - "cssnano": "6.0.3", - "dotenv": "8.6.0", - "dotenv-webpack": "8.0.1", - "eslint": "8.44.0", - "eslint-config-airbnb": "19.0.4", - "eslint-config-airbnb-typescript": "^17.0.0", - "eslint-import-resolver-typescript": "^4.2.1", - "eslint-plugin-formatjs": "^4.12.2", - "eslint-plugin-import": "2.31.0", - "eslint-plugin-jsx-a11y": "6.7.1", - "eslint-plugin-react": "7.33.2", - "eslint-plugin-react-hooks": "4.6.1", + "@formatjs/ts-transformer": "^3.13.14", + "@pmmmwh/react-refresh-webpack-plugin": "^0.6.0", + "@stylistic/eslint-plugin": "^2.9.0", + "@tanstack/react-query-devtools": "^5.99.0", + "@types/eslint__js": "^8.42.3", + "@types/gradient-string": "^1.1.6", + "autoprefixer": "^10.4.20", + "axios": "^1.7.9", + "axios-cache-interceptor": "^1.6.0", + "babel-jest": "^29.7.0", + "babel-plugin-formatjs": "^10.5.16", + "chalk": "^4.1.2", + "classnames": "^2.5.1", + "clean-webpack-plugin": "^4.0.0", + "compression": "^1.7.4", + "css-loader": "^7.1.2", + "cssnano": "^6.1.2", + "eslint": "^9.13.0", + "eslint-plugin-formatjs": "^5.1.3", + "eslint-plugin-jest": "^28.8.3", + "eslint-plugin-jsx-a11y": "^6.10.1", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.0.0", "express": "^4.18.2", "file-loader": "6.2.0", - "html-webpack-plugin": "5.6.3", + "fork-ts-checker-webpack-plugin": "^9.0.2", + "form-urlencoded": "^6.1.5", + "glob": "^7.2.3", + "globals": "^15.11.0", + "gradient-string": "^2.0.2", + "html-webpack-plugin": "5.6.7", "identity-obj-proxy": "3.0.0", "image-minimizer-webpack-plugin": "3.8.3", - "jest": "29.7.0", - "jest-environment-jsdom": "29.7.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jest-localstorage-mock": "^2.4.26", + "jwt-decode": "^3.1.2", + "localforage": "^1.10.0", + "localforage-memoryStorageDriver": "^0.9.2", + "lodash": "^4.18.1", "mini-css-extract-plugin": "1.6.2", - "parse5": "7.1.2", - "postcss": "8.4.49", + "parse5": "7.3.0", + "postcss": "^8.4.47", "postcss-custom-media": "10.0.8", "postcss-loader": "7.3.4", - "postcss-rtlcss": "5.1.2", + "postcss-rtlcss": "^5.5.0", + "prop-types": "^15.8.1", "react-dev-utils": "12.0.1", - "react-refresh": "0.16.0", - "resolve-url-loader": "5.0.0", - "sass": "1.85.1", - "sass-loader": "13.3.3", - "sharp": "0.32.6", + "react-focus-on": "^3.10.2", + "react-intl": "^6.6.6", + "react-refresh": "0.18.0", + "react-refresh-typescript": "^2.0.9", + "react-responsive": "^10.0.0", + "react-transition-group": "^4.4.5", + "resolve-url-loader": "^5.0.0", + "sass-embedded": "^1.87.0", + "sass-loader": "^16.0.2", + "sharp": "^0.34.0", "source-map-loader": "4.0.2", - "style-loader": "3.3.4", - "ts-jest": "29.1.4", + "style-loader": "^4.0.0", + "ts-loader": "^9.5.7", "tsconfig-paths-webpack-plugin": "^4.2.0", - "typescript": "4.9.5", - "url-loader": "4.1.1", + "typescript": "^5.6.3", + "typescript-eslint": "^8.11.0", + "universal-cookie": "^8.0.1", + "url-loader": "^4.1.1", + "uuid": "^11.0.2", "webpack": "^5.97.1", "webpack-bundle-analyzer": "^4.10.1", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1", + "webpack-dev-server": "^5.1.0", "webpack-merge": "^5.10.0", - "webpack-remove-empty-scripts": "1.0.4" - }, - "bin": { - "fedx-scripts": "bin/fedx-scripts.js" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@openedx/frontend-build/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@openedx/frontend-build/node_modules/ts-jest": { - "version": "29.1.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.4.tgz", - "integrity": "sha512-YiHwDhSvCiItoAgsKtoLFCuakDzDsJ1DLDnSouTaTmdOcOwIkSzbLXduaQ6M5DRVhuZC/NYaaZ/mtHbWMv/S6Q==", - "license": "MIT", - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" + "webpack-remove-empty-scripts": "1.1.1" }, "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/@openedx/frontend-plugin-framework": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@openedx/frontend-plugin-framework/-/frontend-plugin-framework-1.7.0.tgz", - "integrity": "sha512-8tGkuHvtzhbqb9dU4sXUtR0K44+Hjh1uGR6DvhZAt9wSKQC1v4RBk34ef8DFzQhoNQa/Jtn6BJuta4Un6MmHmw==", - "license": "AGPL-3.0", - "dependencies": { - "@edx/brand": "npm:@openedx/brand-openedx@^1.2.2", - "classnames": "^2.3.2", - "core-js": "3.37.1", - "react-redux": "8.1.1", - "redux": "4.2.1" - }, - "peerDependencies": { - "@edx/frontend-platform": "^7.0.0 || ^8.0.0", - "@openedx/paragon": "^21.0.0 || ^22.0.0 || ^23.0.0", - "prop-types": "^15.8.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0", - "react-error-boundary": "^4.0.11" - } - }, - "node_modules/@openedx/frontend-plugin-framework/node_modules/core-js": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", - "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/@openedx/frontend-plugin-framework/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, - "node_modules/@openedx/frontend-plugin-framework/node_modules/react-redux": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.1.tgz", - "integrity": "sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" + "openedx": "dist/tools/cli/openedx.js" }, "peerDependencies": { - "@types/react": "^16.8 || ^17.0 || ^18.0", - "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0", - "react-native": ">=0.59", - "redux": "^4 || ^5.0.0-beta.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - }, - "redux": { - "optional": true - } + "@openedx/paragon": "^23.20.0", + "@tanstack/react-query": "^5.81.2", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router": "^6.26.1", + "react-router-dom": "^6.26.1" } }, "node_modules/@openedx/paragon": { - "version": "22.18.1", - "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-22.18.1.tgz", - "integrity": "sha512-h/nZkViIONjEwTdFOyTvXYjqT//flrHjUwh6iI6MasRcgPkngoGwQF+EVjT/vT3bxjX0mtB2QV+nwICaR8Ghgw==", + "version": "23.20.0", + "resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-23.20.0.tgz", + "integrity": "sha512-2LOzROn0mkNpeaHjaGMnwgJYIY7CZSHFh7iYq3VB7IViMGo6eD+LboPoY5T48pnYVdDTTgiN3YLTI0h+mt5CwQ==", "license": "Apache-2.0", + "peer": true, "workspaces": [ "example", "component-generator", @@ -3889,20 +4670,33 @@ "dependent-usage-analyzer" ], "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.1.1", - "@fortawesome/react-fontawesome": "^0.1.18", "@popperjs/core": "^2.11.4", + "@tokens-studio/sd-transforms": "^1.2.4", + "axios": "^0.30.2", "bootstrap": "^4.6.2", "chalk": "^4.1.2", "child_process": "^1.0.2", + "chroma-js": "^2.4.2", "classnames": "^2.3.1", + "cli-progress": "^3.12.0", + "commander": "^9.4.1", "email-prop-type": "^3.0.0", - "file-selector": "^0.6.0", - "font-awesome": "^4.7.0", + "file-selector": "^0.10.0", "glob": "^8.0.3", "inquirer": "^8.2.5", + "js-toml": "^1.0.0", "lodash.uniqby": "^4.7.0", + "log-update": "^4.0.0", + "lz-string": "^1.5.0", "mailto-link": "^2.0.0", + "minimist": "^1.2.8", + "ora": "^5.4.1", + "postcss": "^8.4.21", + "postcss-combine-duplicated-selectors": "^10.0.3", + "postcss-custom-media": "^9.1.2", + "postcss-import": "^15.1.0", + "postcss-map": "^0.11.0", + "postcss-minify": "^1.1.0", "prop-types": "^15.8.1", "react-bootstrap": "^1.6.5", "react-colorful": "^5.6.1", @@ -3912,9 +4706,11 @@ "react-loading-skeleton": "^3.1.0", "react-popper": "^2.2.5", "react-proptype-conditional-require": "^1.0.4", - "react-responsive": "^8.2.0", + "react-responsive": "^10.0.0", "react-table": "^7.7.0", "react-transition-group": "^4.4.2", + "sass": "^1.58.3", + "style-dictionary": "^4.4.0", "tabbable": "^5.3.3", "uncontrollable": "^7.2.1", "uuid": "^9.0.0" @@ -3928,45 +4724,31 @@ "react-intl": "^5.25.1 || ^6.4.0" } }, - "node_modules/@openedx/paragon/node_modules/@fortawesome/fontawesome-common-types": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", - "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@openedx/paragon/node_modules/@fortawesome/fontawesome-svg-core": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", - "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "node_modules/@openedx/paragon/node_modules/axios": { + "version": "0.30.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.30.3.tgz", + "integrity": "sha512-5/tmEb6TmE/ax3mdXBc/Mi6YdPGxQsv+0p5YlciXWt3PHIn0VamqCXhRMtScnwY3lbgSXLneOuXAKUhgmSRpwg==", "license": "MIT", + "peer": true, "dependencies": { - "@fortawesome/fontawesome-common-types": "6.7.2" - }, - "engines": { - "node": ">=6" + "follow-redirects": "^1.15.4", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" } }, - "node_modules/@openedx/paragon/node_modules/@fortawesome/react-fontawesome": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz", - "integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==", + "node_modules/@openedx/paragon/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT", - "dependencies": { - "prop-types": "^15.8.1" - }, - "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "~1 || ~6", - "react": ">=16.x" - } + "peer": true }, "node_modules/@openedx/paragon/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -3975,8 +4757,9 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", + "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3992,10 +4775,11 @@ } }, "node_modules/@openedx/paragon/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "license": "ISC", + "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -4003,27 +4787,72 @@ "node": ">=10" } }, + "node_modules/@openedx/paragon/node_modules/postcss-custom-media": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-9.1.5.tgz", + "integrity": "sha512-GStyWMz7Qbo/Gtw1xVspzVSX8eipgNg4lpsO3CAeY4/A1mzok+RV6MCv3fg62trWijh/lYEj6vps4o8JcBBpDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@csstools/cascade-layer-name-parser": "^1.0.2", + "@csstools/css-parser-algorithms": "^2.2.0", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/media-query-list-parser": "^2.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@openedx/paragon/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@paralleldrive/cuid2": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", - "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", "license": "MIT", + "peer": true, "dependencies": { "@noble/hashes": "^1.1.5" } }, "node_modules/@parcel/watcher": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", "hasInstallScript": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.3", "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">= 10.0.0" @@ -4033,25 +4862,25 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" } }, "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", "cpu": [ "arm64" ], @@ -4060,6 +4889,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -4069,9 +4899,9 @@ } }, "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", - "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", "cpu": [ "arm64" ], @@ -4080,6 +4910,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -4089,9 +4920,9 @@ } }, "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", "cpu": [ "x64" ], @@ -4100,6 +4931,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -4109,9 +4941,9 @@ } }, "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", "cpu": [ "x64" ], @@ -4120,6 +4952,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -4129,9 +4962,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", "cpu": [ "arm" ], @@ -4140,6 +4973,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -4149,9 +4983,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", "cpu": [ "arm" ], @@ -4160,6 +4994,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -4169,9 +5004,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", "cpu": [ "arm64" ], @@ -4180,6 +5015,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -4189,9 +5025,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", "cpu": [ "arm64" ], @@ -4200,6 +5036,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -4209,9 +5046,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", - "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", "cpu": [ "x64" ], @@ -4220,6 +5057,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -4229,9 +5067,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", "cpu": [ "x64" ], @@ -4240,6 +5078,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -4249,9 +5088,9 @@ } }, "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", "cpu": [ "arm64" ], @@ -4260,6 +5099,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -4269,9 +5109,9 @@ } }, "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", "cpu": [ "ia32" ], @@ -4280,6 +5120,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -4289,9 +5130,9 @@ } }, "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", "cpu": [ "x64" ], @@ -4300,6 +5141,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -4308,32 +5150,202 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz", + "integrity": "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz", + "integrity": "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz", + "integrity": "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz", + "integrity": "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-rsa": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz", + "integrity": "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz", + "integrity": "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pfx": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz", + "integrity": "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "license": "MIT", + "peer": true, + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz", + "integrity": "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz", + "integrity": "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz", - "integrity": "sha512-LFWllMA55pzB9D34w/wXUCf8+c+IYKuJDgxiZ3qMhl64KRMBHYM1I3VdGaD2BV5FNPV2/S2596bppxHbv2ZydQ==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.6.2.tgz", + "integrity": "sha512-IhIAD5n4XvGHuL9nAgWfsBR0TdxtjrUWETYKCBHxauYXEv+b+ctEbs9neEgPC7Ecgzv4bpZTBwesAoGDeFymzA==", "license": "MIT", + "peer": true, "dependencies": { - "ansi-html": "^0.0.9", + "anser": "^2.1.1", "core-js-pure": "^3.23.3", "error-stack-parser": "^2.0.6", "html-entities": "^2.1.0", - "loader-utils": "^2.0.4", "schema-utils": "^4.2.0", "source-map": "^0.7.3" }, "engines": { - "node": ">= 10.13" + "node": ">=18.12" }, "peerDependencies": { - "@types/webpack": "4.x || 5.x", + "@types/webpack": "5.x", "react-refresh": ">=0.10.0 <1.0.0", "sockjs-client": "^1.4.0", - "type-fest": ">=0.17.0 <5.0.0", - "webpack": ">=4.43.0 <6.0.0", - "webpack-dev-server": "3.x || 4.x || 5.x", + "type-fest": ">=0.17.0 <6.0.0", + "webpack": "^5.0.0", + "webpack-dev-server": "^4.8.0 || 5.x", "webpack-hot-middleware": "2.x", - "webpack-plugin-serve": "0.x || 1.x" + "webpack-plugin-serve": "1.x" }, "peerDependenciesMeta": { "@types/webpack": { @@ -4360,83 +5372,26 @@ "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, - "node_modules/@redux-devtools/extension": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@redux-devtools/extension/-/extension-3.3.0.tgz", - "integrity": "sha512-X34S/rC8S/M1BIrkYD1mJ5f8vlH0BDqxXrs96cvxSBo4FhMdbhU+GUGsmNYov1xjSyLMHgo8NYrUG8bNX7525g==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.2", - "immutable": "^4.3.4" - }, - "peerDependencies": { - "redux": "^3.1.0 || ^4.0.0 || ^5.0.0" - } - }, - "node_modules/@reduxjs/toolkit": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz", - "integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==", - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@standard-schema/utils": "^0.3.0", - "immer": "^10.0.3", - "redux": "^5.0.1", - "redux-thunk": "^3.1.0", - "reselect": "^5.1.0" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", - "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-redux": { - "optional": true - } - } - }, - "node_modules/@reduxjs/toolkit/node_modules/redux": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" - }, - "node_modules/@reduxjs/toolkit/node_modules/redux-thunk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", - "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", - "license": "MIT", - "peerDependencies": { - "redux": "^5.0.0" - } - }, - "node_modules/@reduxjs/toolkit/node_modules/reselect": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", - "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", - "license": "MIT" - }, "node_modules/@remix-run/router": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz", - "integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw==", + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", "license": "MIT", + "peer": true, "engines": { "node": ">=14.0.0" } @@ -4446,6 +5401,7 @@ "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==", "license": "MIT", + "peer": true, "peerDependencies": { "react": ">=16.3.2" } @@ -4455,6 +5411,7 @@ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", "license": "MIT", + "peer": true, "dependencies": { "dequal": "^2.0.3" }, @@ -4462,31 +5419,12 @@ "react": ">=16.8.0" } }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "license": "MIT" - }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "license": "MIT" }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -4502,291 +5440,101 @@ "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@standard-schema/spec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", - "license": "MIT" - }, - "node_modules/@standard-schema/utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", - "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", - "license": "MIT" - }, - "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", - "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", - "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", - "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", - "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", - "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", - "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@svgr/babel-preset": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", - "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "node_modules/@stylistic/eslint-plugin": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.13.0.tgz", + "integrity": "sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==", "license": "MIT", + "peer": true, "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", - "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", - "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", - "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", - "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", - "@svgr/babel-plugin-transform-svg-component": "8.0.0" + "@typescript-eslint/utils": "^8.13.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" }, "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "eslint": ">=8.40.0" } }, - "node_modules/@svgr/core": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", - "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "node_modules/@tanstack/query-core": { + "version": "5.99.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.99.0.tgz", + "integrity": "sha512-3Jv3WQG0BCcH7G+7lf/bP8QyBfJOXeY+T08Rin3GZ1bshvwlbPt7NrDHMEzGdKIOmOzvIQmxjk28YEQX60k7pQ==", "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^8.1.3", - "snake-case": "^3.0.4" - }, - "engines": { - "node": ">=14" - }, + "peer": true, "funding": { "type": "github", - "url": "https://github.com/sponsors/gregberge" + "url": "https://github.com/sponsors/tannerlinsley" } }, - "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", - "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "node_modules/@tanstack/query-devtools": { + "version": "5.99.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.99.0.tgz", + "integrity": "sha512-m4ufXaJ8FjWXw7xDtyzE/6fkZAyQFg9WrbMrUpt8ZecRJx58jiFOZ2lxZMphZdIpAnIeto/S8stbwLKLusyckQ==", "license": "MIT", - "dependencies": { - "@babel/types": "^7.21.3", - "entities": "^4.4.0" - }, - "engines": { - "node": ">=14" - }, + "peer": true, "funding": { "type": "github", - "url": "https://github.com/sponsors/gregberge" + "url": "https://github.com/sponsors/tannerlinsley" } }, - "node_modules/@svgr/plugin-jsx": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", - "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "node_modules/@tanstack/react-query": { + "version": "5.99.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.99.0.tgz", + "integrity": "sha512-OY2bCqPemT1LlqJ8Y2CUau4KELnIhhG9Ol3ZndPbdnB095pRbPo1cHuXTndg8iIwtoHTgwZjyaDnQ0xD0mYwAw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "@svgr/hast-util-to-babel-ast": "8.0.0", - "svg-parser": "^2.0.4" - }, - "engines": { - "node": ">=14" + "@tanstack/query-core": "5.99.0" }, "funding": { "type": "github", - "url": "https://github.com/sponsors/gregberge" + "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@svgr/core": "*" + "react": "^18 || ^19" } }, - "node_modules/@svgr/plugin-svgo": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", - "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "node_modules/@tanstack/react-query-devtools": { + "version": "5.99.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.99.0.tgz", + "integrity": "sha512-CqqX7LCU9yOfCY/vBURSx2YSD83ryfX+QkfkaKionTfg1s2Hdm572Ro99gW3QPoJjzvsj1HM4pnN4nbDy3MXKA==", "license": "MIT", + "peer": true, "dependencies": { - "cosmiconfig": "^8.1.3", - "deepmerge": "^4.3.1", - "svgo": "^3.0.2" - }, - "engines": { - "node": ">=14" + "@tanstack/query-devtools": "5.99.0" }, "funding": { "type": "github", - "url": "https://github.com/sponsors/gregberge" + "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@svgr/core": "*" - } - }, - "node_modules/@svgr/webpack": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", - "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@babel/plugin-transform-react-constant-elements": "^7.21.3", - "@babel/preset-env": "^7.20.2", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.21.0", - "@svgr/core": "8.1.0", - "@svgr/plugin-jsx": "8.1.0", - "@svgr/plugin-svgo": "8.1.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" + "@tanstack/react-query": "^5.99.0", + "react": "^18 || ^19" } }, "node_modules/@testing-library/dom": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", - "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", - "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", + "picocolors": "1.1.1", "pretty-format": "^27.0.2" }, "engines": { @@ -4794,17 +5542,17 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", - "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, "license": "MIT", "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", - "chalk": "^3.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", - "lodash": "^4.17.21", + "picocolors": "^1.1.1", "redent": "^3.0.0" }, "engines": { @@ -4813,29 +5561,18 @@ "yarn": ">=1" } }, - "node_modules/@testing-library/jest-dom/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, "license": "MIT" }, "node_modules/@testing-library/react": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", - "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5" @@ -4859,6 +5596,48 @@ } } }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tokens-studio/sd-transforms": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@tokens-studio/sd-transforms/-/sd-transforms-1.3.0.tgz", + "integrity": "sha512-zVbiYjTGWpSuwzZwiuvcWf79CQEcTMKSxrOaQJ0zHXFxEmrpETWeIRxv2IO8rtMos/cS8mvnDwPngoHQOMs1SA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@bundled-es-modules/deepmerge": "^4.3.1", + "@bundled-es-modules/postcss-calc-ast-parser": "^0.1.6", + "@tokens-studio/types": "^0.5.1", + "colorjs.io": "^0.5.2", + "expr-eval-fork": "^2.0.2", + "is-mergeable-object": "^1.1.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "style-dictionary": "^4.3.0 || ^5.0.0-rc.0" + } + }, + "node_modules/@tokens-studio/types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@tokens-studio/types/-/types-0.5.2.tgz", + "integrity": "sha512-rzMcZP0bj2E5jaa7Fj0LGgYHysoCrbrxILVbT0ohsCUH5uCHY/u6J7Qw/TE0n6gR9Js/c9ZO9T8mOoz0HdLMbA==", + "license": "MIT", + "peer": true + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -4868,30 +5647,97 @@ "node": ">= 10" } }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } + "node_modules/@turbo/darwin-64": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@turbo/darwin-64/-/darwin-64-2.9.3.tgz", + "integrity": "sha512-P8foouaP+y/p+hhEGBoZpzMbpVvUMwPjDpcy6wN7EYfvvyISD1USuV27qWkczecihwuPJzQ1lDBuL8ERcavTyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@tybys/wasm-util": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", - "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "node_modules/@turbo/darwin-arm64": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@turbo/darwin-arm64/-/darwin-arm64-2.9.3.tgz", + "integrity": "sha512-SIzEkvtNdzdI50FJDaIQ6kQGqgSSdFPcdn0wqmmONN6iGKjy6hsT+EH99GP65FsfV7DLZTh2NmtTIRl2kdoz5Q==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } + "os": [ + "darwin" + ] + }, + "node_modules/@turbo/linux-64": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@turbo/linux-64/-/linux-64-2.9.3.tgz", + "integrity": "sha512-pLRwFmcHHNBvsCySLS6OFabr/07kDT2pxEt/k6eBf/3asiVQZKJ7Rk88AafQx2aYA641qek4RsXvYO3JYpiBug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@turbo/linux-arm64": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@turbo/linux-arm64/-/linux-arm64-2.9.3.tgz", + "integrity": "sha512-gy6ApUroC2Nzv+qjGtE/uPNkhHAFU4c8God+zd5Aiv9L9uBgHlxVJpHT3XWl5xwlJZ2KWuMrlHTaS5kmNB+q1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@turbo/windows-64": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@turbo/windows-64/-/windows-64-2.9.3.tgz", + "integrity": "sha512-d0YelTX6hAsB7kIEtGB3PzIzSfAg3yDoUlHwuwJc3adBXUsyUIs0YLG+1NNtuhcDOUGnWQeKUoJ2pGWvbpRj7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@turbo/windows-arm64": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/@turbo/windows-arm64/-/windows-arm64-2.9.3.tgz", + "integrity": "sha512-/08CwpKJl3oRY8nOlh2YgilZVJDHsr60XTNxRhuDeuFXONpUZ5X+Nv65izbG/xBew9qxcJFbDX9/sAmAX+ITcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "license": "MIT" + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -4920,6 +5766,7 @@ "resolved": "https://registry.npmjs.org/@types/babel__helper-plugin-utils/-/babel__helper-plugin-utils-7.10.3.tgz", "integrity": "sha512-FcLBBPXInqKfULB2nvOBskQPcnSMZ0s1Y2q76u9H1NPPWaLcTeq38xBeKfF/RBUECK333qeaqRdYoPSwW7rTNQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/babel__core": "*" } @@ -4935,19 +5782,20 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "license": "MIT", "dependencies": { - "@babel/types": "^7.20.7" + "@babel/types": "^7.28.2" } }, "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "license": "MIT", + "peer": true, "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -4958,6 +5806,7 @@ "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -4967,6 +5816,7 @@ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -4976,72 +5826,70 @@ "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "license": "MIT", + "peer": true, "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" } }, - "node_modules/@types/cookie": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", - "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==", - "license": "MIT" - }, "node_modules/@types/eslint": { - "version": "8.56.12", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", - "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, + "node_modules/@types/eslint__js": { + "version": "8.42.3", + "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", + "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint": "*" + } + }, "node_modules/@types/eslint-scope": { "version": "3.7.7", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "license": "MIT" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT", + "peer": true }, "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", - "@types/serve-static": "*" + "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", - "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/express/node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -5054,6 +5902,7 @@ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", "license": "MIT", + "peer": true, "dependencies": { "@types/minimatch": "*", "@types/node": "*" @@ -5068,33 +5917,49 @@ "@types/node": "*" } }, + "node_modules/@types/gradient-string": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@types/gradient-string/-/gradient-string-1.1.6.tgz", + "integrity": "sha512-LkaYxluY4G5wR1M4AKQUal2q61Di1yVVCw42ImFTuaIoQVgmV0WP1xUaLB8zwb47mp82vWTpePI9JmrjEnJ7nQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/tinycolor2": "*" + } + }, "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", - "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz", + "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", "license": "MIT", + "peer": true, "dependencies": { - "@types/react": "*", "hoist-non-react-statics": "^3.3.0" + }, + "peerDependencies": { + "@types/react": "*" } }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "license": "MIT" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT", + "peer": true }, "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -5103,7 +5968,8 @@ "version": "2.2.37", "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.37.tgz", "integrity": "sha512-IwpIMieE55oGWiXkQPSBY1nw1nFs6bsKXTFskNY8sdS17K24vyEBRQZEwlRS7ZmXCWnJcQtbxWzly+cODWGs2A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", @@ -5130,9 +5996,10 @@ } }, "node_modules/@types/jest": { - "version": "29.5.12", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", - "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, "license": "MIT", "dependencies": { "expect": "^29.0.0", @@ -5143,6 +6010,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -5155,6 +6023,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -5169,6 +6038,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, "license": "MIT" }, "node_modules/@types/jsdom": { @@ -5188,48 +6058,29 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, - "node_modules/@types/json-stable-stringify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.1.0.tgz", - "integrity": "sha512-ESTsHWB72QQq+pjUFIbEz9uSCZppD31YrVkbt2rnUciTYEvcwN6uZIhX5JZeBHqRlFJ41x/7MewCs7E2Qux6Cg==", - "license": "MIT" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "license": "MIT" - }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/node": { - "version": "22.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "version": "22.19.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.13.tgz", + "integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, - "node_modules/@types/node-forge": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", - "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -5237,49 +6088,52 @@ "license": "MIT" }, "node_modules/@types/picomatch": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.4.tgz", - "integrity": "sha512-0so8lU8O5zatZS/2Fi4zrwks+vZv7e0dygrgEZXljODXBig97l4cPQD+9LabXfGJOWwoRkTVz6Q4edZvD12UOA==", - "license": "MIT" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-3.0.2.tgz", + "integrity": "sha512-n0i8TD3UDB7paoMMxA3Y65vUncFJXjcUf7lQY7YyKGl6031FNjfsLs6pdLFCy2GNFxItPJG8GvvpbZc2skH7WA==", + "license": "MIT", + "peer": true }, "node_modules/@types/prop-types": { - "version": "15.7.14", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "license": "MIT" + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT", + "peer": true }, "node_modules/@types/qs": { - "version": "6.9.18", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", - "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", - "license": "MIT" + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "license": "MIT", + "peer": true }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/react": { - "version": "18.3.20", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz", - "integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==", + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, - "node_modules/@types/react-redux": { - "version": "7.1.34", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.34.tgz", - "integrity": "sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==", + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "license": "MIT", - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" + "peer": true, + "peerDependencies": { + "@types/react": "^18.0.0" } }, "node_modules/@types/react-transition-group": { @@ -5287,29 +6141,25 @@ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "*" } }, "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "license": "MIT" - }, - "node_modules/@types/semver": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", - "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", - "license": "MIT" + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "license": "MIT", + "peer": true }, "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "license": "MIT", + "peer": true, "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, @@ -5318,19 +6168,32 @@ "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "license": "MIT", + "peer": true, "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "license": "MIT", + "peer": true, "dependencies": { "@types/http-errors": "*", "@types/node": "*", - "@types/send": "*" + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" } }, "node_modules/@types/sockjs": { @@ -5338,6 +6201,7 @@ "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -5348,37 +6212,40 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "license": "MIT" }, + "node_modules/@types/tinycolor2": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz", + "integrity": "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==", + "license": "MIT", + "peer": true + }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "license": "MIT" }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==", - "license": "MIT" - }, "node_modules/@types/warning": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -5391,470 +6258,267 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "license": "MIT", + "peer": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "node": ">= 4" } }, - "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "license": "MIT", + "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.0.tgz", - "integrity": "sha512-vIWAU56r2lZAmUsljp6m9+hrTlwNkZH6pqnSPff2WxzofV+jWRSHLmZRUS+g+VE+LlyPByifmGGHpJmhWetatg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.0.tgz", - "integrity": "sha512-+bShFLgtdwuNteQbKq3X230754AouNMXSLDZ56EssgDyckDt6Ld7wRaJjZF0pY671HnY2pk9/amO4amAFzfN1A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.0.tgz", - "integrity": "sha512-HJjXb3aIptDZQ0saSmk2S4W1pWNVZ2iNpAbNGZOfsUXbi8xwCmHdVjErNS92hRp7djuDLup1OLrzOMtTdw5BmA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.0.tgz", - "integrity": "sha512-NF3lk7KHulLD97UE+MHjH0mrOjeZG8Hz10h48YcFz2V0rlxBdRSRcMbGer8iH/1mIlLqxtvXJfGLUr4SMj0XZg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.0.tgz", - "integrity": "sha512-Gn1c/t24irDgU8yYj4vVG6qHplwUM42ti9/zYWgfmFjoXCH6L4Ab9hh6HuO7bfDSvGDRGWQt1IVaBpgbKHdh3Q==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.0.tgz", - "integrity": "sha512-XRrVXRIUP++qyqAqgiXUpOv0GP3cHx7aA7NrzVFf6Cc8FoYuwtnmT+vctfSo4wRZN71MNU4xq2BEFxI4qvSerg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.0.tgz", - "integrity": "sha512-Sligg+vTDAYTXkUtgviPjGEFIh57pkvlfdyRw21i9gkjp/eCNOAi2o5e7qLGTkoYdJHZJs5wVMViPEmAbw2/Tg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.0.tgz", - "integrity": "sha512-Apek8/x+7Rg33zUJlQV44Bvq8/t1brfulk0veNJrk9wprF89bCYFMUHF7zQYcpf2u+m1+qs3mYQrBd43fGXhMA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.0.tgz", - "integrity": "sha512-kBale8CFX5clfV9VmI9EwKw2ZACMEx1ecjV92F9SeWTUoxl9d+LGzS6zMSX3kGYqcfJB3NXMwLCTwIDBLG1y4g==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.0.tgz", - "integrity": "sha512-s/Q33xQjeFHSCvGl1sZztFZF6xhv7coMvFz6wa/x/ZlEArjiQoMMwGa/Aieq1Kp/6+S13iU3/IJF0ga6/451ow==", - "cpu": [ - "riscv64" - ], + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.0.tgz", - "integrity": "sha512-7PuNXAo97ydaxVNrIYJzPipvINJafDpB8pt5CoZHfu8BmqcU6d7kl6/SABTnqNffNkd6Cfhuo70jvGB2P7oJ/Q==", - "cpu": [ - "s390x" - ], + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.0.tgz", - "integrity": "sha512-fNosEzDMYItA4It+R0tioHwKlEfx/3TkkJdP2x9B5o9R946NDC4ZZj5ZjA+Y4NQD2V/imB3QPAKmeh3vHQGQyA==", - "cpu": [ - "x64" - ], + "node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.0.tgz", - "integrity": "sha512-gHIw42dmnVcw7osjNPRybaXhONhggWkkzqiOZzXco1q3OKkn4KsbDylATeemnq3TP+L1BrzSqzl0H9UTJ6ji+w==", - "cpu": [ - "x64" - ], + "node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.0.tgz", - "integrity": "sha512-yq7POusv63/yTkNTaNsnXU/SAcBzckHyk1oYrDXqjS1m/goaWAaU9J9HrsovgTHkljxTcDd6PMAsJ5WZVBuGEQ==", - "cpu": [ - "wasm32" - ], + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", "license": "MIT", - "optional": true, + "peer": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.9" + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { - "node": ">=14.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.0.tgz", - "integrity": "sha512-/IPZPbdri9jglHonwB3F7EpQZvBK3ObH+g4ma/KDrqTEAECwvgE10Unvo0ox3LQFR/iMMAkVY+sGNMrMiIV/QQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.0.tgz", - "integrity": "sha512-NGVKbHEdrLuJdpcuGqV5zXO3v8t4CWOs0qeCGjO47RiwwufOi/yYcrtxtCzZAaMPBrffHL7c6tJ1Hxr17cPUGg==", - "cpu": [ - "ia32" - ], + "node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.0.tgz", - "integrity": "sha512-Jf14pKofg58DIwcZv4Wt9AyVVe7bSJP8ODz+EP9nG/rho08FQzan0VOJk1g6/BNE1RkoYd+lRTWK+/BgH12qoQ==", - "cpu": [ - "x64" - ], + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -5864,25 +6528,29 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -5893,13 +6561,15 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -5912,6 +6582,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", + "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -5921,6 +6592,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -5929,13 +6601,15 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -5952,6 +6626,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -5965,6 +6640,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -5977,6 +6653,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -5991,6 +6668,7 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", + "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -6001,6 +6679,7 @@ "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "license": "MIT", + "peer": true, "engines": { "node": ">=14.15.0" }, @@ -6014,6 +6693,7 @@ "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", "license": "MIT", + "peer": true, "engines": { "node": ">=14.15.0" }, @@ -6027,6 +6707,7 @@ "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=14.15.0" }, @@ -6044,13 +6725,34 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/@zip.js/zip.js": { + "version": "2.8.23", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.23.tgz", + "integrity": "sha512-RB+RLnxPJFPrGvQ9rgO+4JOcsob6lD32OcF0QE0yg24oeW9q8KnTTNlugcDaIveEcCbclobJcZP+fLQ++sH0bw==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=18.0.0" + } }, "node_modules/abab": { "version": "2.0.6", @@ -6064,6 +6766,7 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", + "peer": true, "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -6072,10 +6775,20 @@ "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -6094,19 +6807,33 @@ "acorn-walk": "^8.0.2" } }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "license": "MIT", + "peer": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -6129,6 +6856,7 @@ "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", "license": "MIT", + "peer": true, "dependencies": { "loader-utils": "^2.0.0", "regex-parser": "^2.2.11" @@ -6150,9 +6878,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -6170,6 +6898,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "license": "MIT", + "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -6183,10 +6912,11 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6202,7 +6932,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/ajv-keywords": { "version": "3.5.2", @@ -6213,6 +6944,13 @@ "ajv": "^6.9.1" } }, + "node_modules/anser": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/anser/-/anser-2.3.5.tgz", + "integrity": "sha512-vcZjxvvVoxTeR5XBNJB38oTu/7eDCZlwdz32N1eNgpyPF7j/Z7Idf+CUwQOkKKpJ7RJyjxgLHCM7vdIK0iCNMQ==", + "license": "MIT", + "peer": true + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -6228,18 +6966,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-html": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.9.tgz", - "integrity": "sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==", - "engines": [ - "node >= 0.8.0" - ], - "license": "Apache-2.0", - "bin": { - "ansi-html": "bin/ansi-html" - } - }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -6248,6 +6974,7 @@ "node >= 0.8.0" ], "license": "Apache-2.0", + "peer": true, "bin": { "ansi-html": "bin/ansi-html" } @@ -6277,16 +7004,13 @@ } }, "node_modules/ansis": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-1.5.2.tgz", - "integrity": "sha512-T3vUABrcgSj/HXv27P+A/JxGk5b/ydx0JjN3lgjBTC2iZUFxQGjh43zCzLSbU4C1QTgmx9oaPeWNJFM+auI8qw==", + "version": "4.0.0-node10", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0-node10.tgz", + "integrity": "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==", "license": "ISC", + "peer": true, "engines": { - "node": ">=12.13" - }, - "funding": { - "type": "patreon", - "url": "https://patreon.com/biodiscus" + "node": ">=10" } }, "node_modules/anymatch": { @@ -6298,8 +7022,20 @@ "normalize-path": "^3.0.0", "picomatch": "^2.0.4" }, - "engines": { - "node": ">= 8" + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/argparse": { @@ -6312,10 +7048,11 @@ } }, "node_modules/aria-hidden": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", - "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.0.0" }, @@ -6327,6 +7064,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, "license": "Apache-2.0", "dependencies": { "dequal": "^2.0.3" @@ -6337,6 +7075,7 @@ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" @@ -6352,20 +7091,24 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "license": "MIT", + "peer": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -6375,12 +7118,16 @@ } }, "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", "license": "MIT", + "peer": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, "node_modules/array-uniq": { @@ -6388,23 +7135,24 @@ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "license": "MIT", + "peer": true, "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", + "es-abstract": "^1.23.2", "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6418,6 +7166,7 @@ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -6436,6 +7185,7 @@ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -6454,6 +7204,7 @@ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -6470,6 +7221,7 @@ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "license": "MIT", + "peer": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", @@ -6490,31 +7242,68 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT" + "license": "MIT", + "peer": true + }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } }, "node_modules/assert-ok": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-ok/-/assert-ok-1.0.0.tgz", "integrity": "sha512-lCvYmCpMl8c1tp9ynExhoDEk0gGW43SVVC3RE1VYrrVKhNMy8GHfdiwZdoIM6a605s56bUAbENQxtOC0uZp3wg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", - "license": "ISC" + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "license": "MIT", + "peer": true }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "license": "MIT" + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" } @@ -6539,14 +7328,15 @@ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=4" } }, "node_modules/autoprefixer": { - "version": "10.4.20", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", - "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", "funding": [ { "type": "opencollective", @@ -6562,12 +7352,12 @@ } ], "license": "MIT", + "peer": true, "dependencies": { - "browserslist": "^4.23.3", - "caniuse-lite": "^1.0.30001646", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.1", + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -6585,6 +7375,7 @@ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "license": "MIT", + "peer": true, "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -6596,34 +7387,39 @@ } }, "node_modules/axe-core": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", - "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", + "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", "license": "MPL-2.0", + "peer": true, "engines": { "node": ">=4" } }, "node_modules/axios": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", - "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", "license": "MIT", + "peer": true, "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "node_modules/axios-cache-interceptor": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/axios-cache-interceptor/-/axios-cache-interceptor-1.8.0.tgz", - "integrity": "sha512-cTNnPGJyQkxnWp0EWvE3NRvgURU5cWw/Qx3dIhXyHSM4Ip0c7EEe0I3an0Jwa549m1CAOg57ibj27YRNLmQCcg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/axios-cache-interceptor/-/axios-cache-interceptor-1.12.0.tgz", + "integrity": "sha512-15XuJkdeJmQo/HY2b0xx3zim8DMx7Nu+G8R4z6OG2VZLtbIDnsfn4qZsLLvkPfK4SVNRzXnoG4jPR7dqdQznRA==", "license": "MIT", + "peer": true, "dependencies": { - "cache-parser": "1.2.5", - "fast-defer": "1.1.8", - "object-code": "1.3.3" + "cache-parser": "^1.2.6", + "fast-defer": "^1.1.9", + "http-vary": "^1.0.3", + "object-code": "^2.0.0", + "try": "^1.0.3" }, "engines": { "node": ">=12" @@ -6635,34 +7431,16 @@ "axios": "^1" } }, - "node_modules/axios-mock-adapter": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.22.0.tgz", - "integrity": "sha512-dmI0KbkyAhntUR05YY96qg2H6gg0XMl2+qTW0xmYg6Up+BFBAJYRLROMXRdDEL06/Wqwa0TJThAYvFtSFdRCZw==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "is-buffer": "^2.0.5" - }, - "peerDependencies": { - "axios": ">= 0.17.0" - } - }, "node_modules/axobject-query": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.4.tgz", - "integrity": "sha512-aPTElBrbifBU1krmZxGZOlBkslORe7Ll7+BDnI50Wy4LgOt69luMgevkDfTq1O/ZgprooPCtWpjCwKSZw/iZ4A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">= 0.4" } }, - "node_modules/b4a": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", - "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", - "license": "Apache-2.0" - }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -6684,81 +7462,26 @@ "@babel/core": "^7.8.0" } }, - "node_modules/babel-jest/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-loader": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", - "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", - "license": "MIT", - "dependencies": { - "find-cache-dir": "^4.0.0", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5" - } - }, "node_modules/babel-plugin-formatjs": { - "version": "10.5.37", - "resolved": "https://registry.npmjs.org/babel-plugin-formatjs/-/babel-plugin-formatjs-10.5.37.tgz", - "integrity": "sha512-Q8DuVc5EIRxGdP5AHpqL+UE3UDDYsuuk//ya1rlFgeTkIFRUl/Bohf9d0Ef1gmu95NMswU7MUeFbV+ZZ/7qqnQ==", + "version": "10.5.41", + "resolved": "https://registry.npmjs.org/babel-plugin-formatjs/-/babel-plugin-formatjs-10.5.41.tgz", + "integrity": "sha512-ZpozYGek+Bdyl52LgzW1MhPYBRKbROdbHBuBz7KAO88Ht7GcyCBiwJxpAjVDb0YBA9LGKUemGQOLdEDkRCe2hg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.26.10", "@babel/helper-plugin-utils": "^7.26.5", "@babel/plugin-syntax-jsx": "^7.25.9", "@babel/traverse": "^7.26.10", "@babel/types": "^7.26.10", - "@formatjs/icu-messageformat-parser": "2.11.2", - "@formatjs/ts-transformer": "3.13.34", + "@formatjs/icu-messageformat-parser": "2.11.4", + "@formatjs/ts-transformer": "3.14.2", "@types/babel__core": "^7.20.5", "@types/babel__helper-plugin-utils": "^7.10.3", "@types/babel__traverse": "^7.20.6", "tslib": "^2.8.0" } }, - "node_modules/babel-plugin-formatjs/node_modules/@babel/core": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.10", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.10", - "@babel/types": "^7.26.10", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -6791,13 +7514,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", - "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", + "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.4", + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" }, "peerDependencies": { @@ -6805,69 +7529,36 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz", + "integrity": "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" + "@babel/helper-define-polyfill-provider": "^0.6.6", + "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", - "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", + "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.4" + "@babel/helper-define-polyfill-provider": "^0.6.6" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/babel-plugin-transform-imports": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-imports/-/babel-plugin-transform-imports-2.0.0.tgz", - "integrity": "sha512-65ewumYJ85QiXdcB/jmiU0y0jg6eL6CdnDqQAqQ8JMOKh1E52VPG3NJzbVKWcgovUR5GBH8IWpCXQ7I8Q3wjgw==", - "license": "ISC", - "dependencies": { - "@babel/types": "^7.4", - "is-valid-path": "^0.1.1" - } - }, - "node_modules/babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha512-F2rZGQnAdaHWQ8YAoeRbukc7HS9QgdgeyJ0rQDd485v9opwuPvjpPFcOOT/WmkKTdgy9ESgSPXDcTNpzrGr6iQ==", - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" - } - }, - "node_modules/babel-polyfill/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true, - "license": "MIT" - }, - "node_modules/babel-polyfill/node_modules/regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==", - "license": "MIT" - }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "license": "MIT", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", @@ -6887,7 +7578,7 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "node_modules/babel-preset-jest": { @@ -6906,106 +7597,13 @@ "@babel/core": "^7.0.0" } }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", - "license": "MIT", - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/babel-runtime/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true, - "license": "MIT" - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "license": "MIT" - }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/bare-events": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", - "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", - "license": "Apache-2.0", - "optional": true - }, - "node_modules/bare-fs": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.2.tgz", - "integrity": "sha512-8wSeOia5B7LwD4+h465y73KOdj5QHsbbuoUfPBi+pXgFJIPuG7SsiOdJuijWMyfid49eD+WivpfY7KT8gbAzBA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, - "node_modules/bare-os": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", - "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", - "license": "Apache-2.0", - "optional": true, + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^3.0.1" - } - }, - "node_modules/bare-stream": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", - "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "streamx": "^2.21.0" - }, - "peerDependencies": { - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } + "node": "18 || 20 || >=22" } }, "node_modules/base64-js": { @@ -7026,19 +7624,34 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "peer": true + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "license": "MIT", + "peer": true, "engines": { "node": "*" } @@ -7060,6 +7673,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "license": "MIT", + "peer": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -7067,23 +7681,24 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "license": "MIT", + "peer": true, "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -7095,6 +7710,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -7103,13 +7719,31 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "license": "MIT", + "peer": true + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/bonjour-service": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" @@ -7119,12 +7753,14 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/bootstrap": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", + "deprecated": "This version of Bootstrap is no longer supported. Please upgrade to the latest version.", "funding": [ { "type": "github", @@ -7136,19 +7772,22 @@ } ], "license": "MIT", + "peer": true, "peerDependencies": { "jquery": "1.9.1 - 3", "popper.js": "^1.16.1" } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -7164,9 +7803,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "funding": [ { "type": "opencollective", @@ -7183,10 +7822,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -7195,18 +7835,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -7235,6 +7863,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -7246,26 +7875,55 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/cache-parser": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/cache-parser/-/cache-parser-1.2.5.tgz", - "integrity": "sha512-Md/4VhAHByQ9frQ15WD6LrMNiVw9AEl/J7vWIXw+sxT6fSOpbtt6LHTp76vy8+bOESPBO94117Hm2bIjlI7XjA==", - "license": "MIT" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/cache-parser/-/cache-parser-1.2.6.tgz", + "integrity": "sha512-SjjnKlWgrhDrAWKUxAvmZLRGDa6JExMfjSu59/pvpNoI6mEHYSLcLKUw2RtECEOINvf6dxJo35fY+T/scA0SUA==", + "license": "MIT", + "peer": true }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "license": "MIT", + "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", @@ -7297,6 +7955,7 @@ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", + "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -7322,21 +7981,19 @@ "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", "license": "MIT", + "peer": true, "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/caniuse-api": { @@ -7344,6 +8001,7 @@ "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", "license": "MIT", + "peer": true, "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", @@ -7352,9 +8010,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001715", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz", - "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==", + "version": "1.0.30001776", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001776.tgz", + "integrity": "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==", "funding": [ { "type": "opencollective", @@ -7376,6 +8034,7 @@ "resolved": "https://registry.npmjs.org/cast-array/-/cast-array-1.0.1.tgz", "integrity": "sha512-EiqtV+M9L42wd0IRgYjgVGDq7vdNBUUrdecd03QReJp8pIr59o2A1b0XfP+aCUlzLKx2E7zVetaogeJCtiHa+w==", "license": "MIT", + "peer": true, "dependencies": { "isarray": "0.0.1" } @@ -7384,7 +8043,8 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/chalk": { "version": "4.1.2", @@ -7402,6 +8062,13 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "license": "MIT", + "peer": true + }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -7412,52 +8079,63 @@ } }, "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "license": "MIT" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "license": "MIT", + "peer": true + }, + "node_modules/chevrotain": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.2.0.tgz", + "integrity": "sha512-mHCHTxM51nCklUw9RzRVc0DLjAh/SAUPM4k/zMInlTIo25ldWXOZoPt7XEIk/LwoT4lFVmJcu9g5MHtx371x3A==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@chevrotain/cst-dts-gen": "11.2.0", + "@chevrotain/gast": "11.2.0", + "@chevrotain/regexp-to-ast": "11.2.0", + "@chevrotain/types": "11.2.0", + "@chevrotain/utils": "11.2.0", + "lodash-es": "4.17.23" + } }, "node_modules/child_process": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", "integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==", - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "license": "MIT", + "peer": true, "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" + "node_modules/chroma-js": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.6.0.tgz", + "integrity": "sha512-BLHvCB9s8Z1EV4ethr6xnkl/P2YRFOGqfgvuMG/MyCbZPrTA+NeiByY6XvgF0zP4/2deU2CXnWyMa3zu1LqQ3A==", + "license": "(BSD-3-Clause AND Apache-2.0)", + "peer": true }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.0" } @@ -7494,6 +8172,7 @@ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", "license": "MIT", + "peer": true, "dependencies": { "source-map": "~0.6.0" }, @@ -7506,6 +8185,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -7515,6 +8195,7 @@ "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz", "integrity": "sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w==", "license": "MIT", + "peer": true, "dependencies": { "del": "^4.1.1" }, @@ -7530,6 +8211,7 @@ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "license": "MIT", + "peer": true, "dependencies": { "restore-cursor": "^3.1.0" }, @@ -7537,11 +8219,25 @@ "node": ">=8" } }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "license": "MIT", + "peer": true, + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/cli-spinners": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" }, @@ -7554,22 +8250,21 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "license": "ISC", + "peer": true, "engines": { "node": ">= 10" } }, "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "license": "ISC", + "peer": true, "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", + "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" } }, "node_modules/cliui/node_modules/wrap-ansi": { @@ -7577,6 +8272,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -7594,6 +8290,7 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.8" } @@ -7603,6 +8300,7 @@ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "license": "MIT", + "peer": true, "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", @@ -7623,24 +8321,11 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "license": "MIT" }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -7659,27 +8344,26 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "license": "MIT" + "license": "MIT", + "peer": true + }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "license": "MIT", + "peer": true }, "node_modules/combined-stream": { "version": "1.0.8", @@ -7694,25 +8378,23 @@ } }, "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "license": "MIT", "engines": { - "node": ">= 6" + "node": "^12.20.0 || >=14" } }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "license": "ISC" - }, "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-2.0.0.tgz", + "integrity": "sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw==", "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -7722,6 +8404,7 @@ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "license": "MIT", + "peer": true, "dependencies": { "mime-db": ">= 1.43.0 < 2" }, @@ -7730,16 +8413,17 @@ } }, "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "license": "MIT", + "peer": true, "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" }, @@ -7752,6 +8436,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -7760,16 +8445,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "license": "MIT", - "engines": { - "node": ">= 0.6" - } + "peer": true }, "node_modules/concat-map": { "version": "0.0.1", @@ -7777,17 +8454,12 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "license": "MIT" - }, "node_modules/connect-history-api-fallback": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.8" } @@ -7797,6 +8469,7 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -7807,152 +8480,51 @@ "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "license": "MIT" - }, - "node_modules/copy-webpack-plugin": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", - "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.1", - "globby": "^14.0.0", - "normalize-path": "^3.0.0", - "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", - "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.3", - "ignore": "^7.0.3", - "path-type": "^6.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/ignore": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", - "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", - "dev": true, + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", + "peer": true, "engines": { - "node": ">= 4" + "node": ">= 0.6" } }, - "node_modules/copy-webpack-plugin/node_modules/path-type": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", - "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", - "dev": true, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", + "peer": true, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "peer": true }, - "node_modules/core-js": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz", - "integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==", - "hasInstallScript": true, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } + "peer": true }, "node_modules/core-js-compat": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz", - "integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", "license": "MIT", + "peer": true, "dependencies": { - "browserslist": "^4.24.4" + "browserslist": "^4.28.1" }, "funding": { "type": "opencollective", @@ -7960,11 +8532,12 @@ } }, "node_modules/core-js-pure": { - "version": "3.41.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.41.0.tgz", - "integrity": "sha512-71Gzp96T9YPk63aUvE5Q5qP+DryB4ZloUZPSOebGM88VNw8VNfvdA7z6kGA8iGOTEzAomsRidp4jXSmUIJsL+Q==", + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.48.0.tgz", + "integrity": "sha512-1slJgk89tWC51HQ1AEqG+s2VuwpTRr8ocu4n20QUcH1v9lAN0RXen0Q0AABa/DK1I7RrNWLucplOHMx8hfTGTw==", "hasInstallScript": true, "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" @@ -7974,13 +8547,15 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cosmiconfig": { "version": "8.3.6", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "license": "MIT", + "peer": true, "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", @@ -8006,13 +8581,15 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" + "license": "Python-2.0", + "peer": true }, "node_modules/cosmiconfig/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", + "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -8056,10 +8633,11 @@ } }, "node_modules/css-declaration-sorter": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", - "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.1.tgz", + "integrity": "sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA==", "license": "ISC", + "peer": true, "engines": { "node": "^14 || ^16 || >=18" }, @@ -8068,56 +8646,47 @@ } }, "node_modules/css-loader": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", - "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.4.tgz", + "integrity": "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==", "license": "MIT", + "peer": true, "dependencies": { "icss-utils": "^5.1.0", - "loader-utils": "^2.0.0", - "postcss": "^8.2.15", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", + "postcss": "^8.4.40", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.1.0", - "schema-utils": "^3.0.0", - "semver": "^7.3.5" + "postcss-value-parser": "^4.2.0", + "semver": "^7.6.3" }, "engines": { - "node": ">= 10.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.27.0 || ^5.0.0" - } - }, - "node_modules/css-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "webpack": "^5.27.0" }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/css-loader/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -8129,13 +8698,15 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==", - "license": "BSD" + "license": "BSD", + "peer": true }, "node_modules/css-select": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.0.1", @@ -8152,6 +8723,7 @@ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", "license": "MIT", + "peer": true, "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" @@ -8161,10 +8733,11 @@ } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">= 6" }, @@ -8176,6 +8749,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, "license": "MIT" }, "node_modules/cssesc": { @@ -8183,6 +8757,7 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "license": "MIT", + "peer": true, "bin": { "cssesc": "bin/cssesc" }, @@ -8191,13 +8766,14 @@ } }, "node_modules/cssnano": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.3.tgz", - "integrity": "sha512-MRq4CIj8pnyZpcI2qs6wswoYoDD1t0aL28n+41c1Ukcpm56m1h6mCexIHBGjfZfnTqtGSSCP4/fB1ovxgjBOiw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", "license": "MIT", + "peer": true, "dependencies": { - "cssnano-preset-default": "^6.0.3", - "lilconfig": "^3.0.0" + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" }, "engines": { "node": "^14 || ^16 || >=18.0" @@ -8215,6 +8791,7 @@ "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", "license": "MIT", + "peer": true, "dependencies": { "browserslist": "^4.23.0", "css-declaration-sorter": "^7.2.0", @@ -8259,6 +8836,7 @@ "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -8271,6 +8849,7 @@ "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "license": "MIT", + "peer": true, "dependencies": { "css-tree": "~2.2.0" }, @@ -8284,6 +8863,7 @@ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", "license": "MIT", + "peer": true, "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" @@ -8297,7 +8877,8 @@ "version": "2.0.28", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "license": "CC0-1.0" + "license": "CC0-1.0", + "peer": true }, "node_modules/cssom": { "version": "0.5.0", @@ -8324,16 +8905,18 @@ "license": "MIT" }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT", + "peer": true }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/data-urls": { "version": "3.0.2", @@ -8354,6 +8937,7 @@ "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -8371,6 +8955,7 @@ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -8388,6 +8973,7 @@ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -8404,12 +8990,13 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -8424,9 +9011,9 @@ } }, "node_modules/decimal.js": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", - "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "license": "MIT" }, "node_modules/decode-uri-component": { @@ -8434,29 +9021,15 @@ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dedent": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", - "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -8467,26 +9040,12 @@ } } }, - "node_modules/deep-diff": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz", - "integrity": "sha512-yVn6RZmHiGnxRKR9sJb3iVV2XTF1Ghh2DiWRZ3dMnGc43yUdWWF/kX6lQyk3+P84iprfWKU/8zFTrlkvtFm1ug==", - "license": "MIT" - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/deepmerge": { "version": "4.3.1", @@ -8497,16 +9056,34 @@ "node": ">=0.10.0" } }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "license": "BSD-2-Clause", + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "license": "MIT", + "peer": true, "dependencies": { - "execa": "^5.0.0" + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" }, "engines": { - "node": ">= 10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/defaults": { @@ -8514,6 +9091,7 @@ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "license": "MIT", + "peer": true, "dependencies": { "clone": "^1.0.2" }, @@ -8526,6 +9104,7 @@ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "license": "MIT", + "peer": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -8552,6 +9131,7 @@ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "license": "MIT", + "peer": true, "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -8569,6 +9149,7 @@ "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/glob": "^7.1.1", "globby": "^6.1.0", @@ -8582,43 +9163,6 @@ "node": ">=6" } }, - "node_modules/del/node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", - "license": "MIT", - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", - "license": "MIT", - "dependencies": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del/node_modules/globby/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -8633,6 +9177,7 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -8651,22 +9196,20 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", - "optional": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, + "peer": true, "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/detect-newline": { @@ -8682,13 +9225,15 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/detect-port-alt": { "version": "1.1.6", @@ -8727,17 +9272,12 @@ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "license": "ISC", + "peer": true, "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, - "node_modules/diacritics": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz", - "integrity": "sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==", - "license": "MIT" - }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -8764,6 +9304,7 @@ "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "license": "MIT", + "peer": true, "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" }, @@ -8772,28 +9313,32 @@ } }, "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "esutils": "^2.0.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.0" } }, "node_modules/dom-accessibility-api": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "license": "MIT" + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", "license": "MIT", + "peer": true, "dependencies": { "utila": "~0.4" } @@ -8803,6 +9348,7 @@ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" @@ -8813,6 +9359,7 @@ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", "license": "MIT", + "peer": true, "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", @@ -8827,6 +9374,7 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "license": "BSD-2-Clause", + "peer": true, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -8841,7 +9389,8 @@ "url": "https://github.com/sponsors/fb55" } ], - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/domexception": { "version": "4.0.0", @@ -8861,6 +9410,7 @@ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "domelementtype": "^2.2.0" }, @@ -8876,6 +9426,7 @@ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", @@ -8889,43 +9440,11 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=10" - } - }, - "node_modules/dotenv-defaults": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz", - "integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==", - "license": "MIT", - "dependencies": { - "dotenv": "^8.2.0" - } - }, - "node_modules/dotenv-webpack": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-8.0.1.tgz", - "integrity": "sha512-CdrgfhZOnx4uB18SgaoP9XHRN2v48BbjuXQsZY5ixs5A8579NxQkmMxRtI7aTwSiSQcM2ao12Fdu+L3ZS3bG4w==", - "license": "MIT", - "dependencies": { - "dotenv-defaults": "^2.0.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "webpack": "^4 || ^5" + "license": "MIT", + "peer": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" } }, "node_modules/dunder-proto": { @@ -8948,31 +9467,24 @@ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "license": "MIT" }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT", + "peer": true + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT", + "peer": true }, "node_modules/electron-to-chromium": { - "version": "1.5.141", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.141.tgz", - "integrity": "sha512-qS+qH9oqVYc1ooubTiB9l904WVyM6qNYxtOEEGReoZXw3xlqeYdFr5GclNzbkAufWgwWLEPoDi3d9MoRwwIjGw==", + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", "license": "ISC" }, "node_modules/email-prop-type": { @@ -8980,6 +9492,7 @@ "resolved": "https://registry.npmjs.org/email-prop-type/-/email-prop-type-3.0.1.tgz", "integrity": "sha512-tONZGMEOOkadp5OBftuVXU8DsceWmINxYK+pqPFB4LT5ODjrPX/esel3WGqbV7d6in5/MnZE4n4QcqOr4gh7dg==", "license": "MIT", + "peer": true, "dependencies": { "email-validator": "^2.0.4" } @@ -8988,6 +9501,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/email-validator/-/email-validator-2.0.4.tgz", "integrity": "sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==", + "peer": true, "engines": { "node": ">4.0" } @@ -9005,16 +9519,18 @@ } }, "node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "license": "MIT" + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT", + "peer": true }, "node_modules/emoji-regex-xs": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-2.0.1.tgz", "integrity": "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" } @@ -9024,6 +9540,7 @@ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "license": "MIT", + "peer": true, "engines": { "node": ">= 4" } @@ -9033,27 +9550,20 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -9064,6 +9574,7 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=0.12" }, @@ -9072,10 +9583,11 @@ } }, "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", "license": "MIT", + "peer": true, "bin": { "envinfo": "dist/cli.js" }, @@ -9084,9 +9596,9 @@ } }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -9097,32 +9609,34 @@ "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", "license": "MIT", + "peer": true, "dependencies": { "stackframe": "^1.3.4" } }, "node_modules/es-abstract": { - "version": "1.23.9", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", - "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "license": "MIT", + "peer": true, "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.0", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", @@ -9134,21 +9648,24 @@ "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", + "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.0", + "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.3", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.3", + "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", @@ -9157,7 +9674,7 @@ "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.18" + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -9185,26 +9702,27 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", - "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", + "es-abstract": "^1.24.1", "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", + "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.6", + "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.4", + "iterator.prototype": "^1.1.5", "safe-array-concat": "^1.1.3" }, "engines": { @@ -9212,10 +9730,11 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "license": "MIT" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "license": "MIT", + "peer": true }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -9249,6 +9768,7 @@ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "license": "MIT", + "peer": true, "dependencies": { "hasown": "^2.0.2" }, @@ -9261,6 +9781,7 @@ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "license": "MIT", + "peer": true, "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", @@ -9286,7 +9807,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/escape-string-regexp": { "version": "4.0.0", @@ -9332,685 +9854,338 @@ } }, "node_modules/eslint": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", - "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", + "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "license": "MIT", + "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.1.0", - "@eslint/js": "8.44.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.3", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.6.0", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-airbnb": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", - "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", - "license": "MIT", - "dependencies": { - "eslint-config-airbnb-base": "^15.0.0", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5" - }, - "engines": { - "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-hooks": "^4.3.0" - } - }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", - "license": "MIT", - "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" - } - }, - "node_modules/eslint-config-airbnb-typescript": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", - "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", - "license": "MIT", - "dependencies": { - "eslint-config-airbnb-base": "^15.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0", - "@typescript-eslint/parser": "^5.0.0 || ^6.0.0", - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.3" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-import-resolver-typescript": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.3.4.tgz", - "integrity": "sha512-buzw5z5VtiQMysYLH9iW9BV04YyZebsw+gPi+c4FCjfS9i6COYOrEWw9t3m3wA9PFBfqcBCqWf32qrXLbwafDw==", - "license": "ISC", - "dependencies": { - "debug": "^4.4.0", - "get-tsconfig": "^4.10.0", - "is-bun-module": "^2.0.0", - "stable-hash": "^0.0.5", - "tinyglobby": "^0.2.13", - "unrs-resolver": "^1.6.3" - }, - "engines": { - "node": "^16.17.0 || >=18.6.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint-import-resolver-typescript" + "url": "https://eslint.org/donate" }, "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*", - "eslint-plugin-import-x": "*" - }, - "peerDependenciesMeta": { - "eslint-plugin-import": { - "optional": true - }, - "eslint-plugin-import-x": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" + "jiti": "*" }, "peerDependenciesMeta": { - "eslint": { + "jiti": { "optional": true } } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/eslint-plugin-formatjs": { - "version": "4.13.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-formatjs/-/eslint-plugin-formatjs-4.13.3.tgz", - "integrity": "sha512-4j3IVwaLEXblnvH2/ZIOZwc9zaaZf2+zyn/b8oLJRt6kMCTu2rIs4UsIxy5nBRYZzsBSh7k34JJ5/ngGtJ3kYw==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-formatjs/-/eslint-plugin-formatjs-5.4.2.tgz", + "integrity": "sha512-IdJt/il0FASmk/aJDzl96Zh0tovm+KVhCbA5d+YC14gOpeFe1n6766JMi/RP9YOY9dhe6BbWEJnk9dPJwMMngw==", "license": "MIT", + "peer": true, "dependencies": { - "@formatjs/icu-messageformat-parser": "2.7.8", - "@formatjs/ts-transformer": "3.13.14", - "@types/eslint": "7 || 8", - "@types/picomatch": "^2.3.0", - "@typescript-eslint/utils": "^6.18.1", - "emoji-regex": "^10.2.1", + "@formatjs/icu-messageformat-parser": "2.11.4", + "@formatjs/ts-transformer": "3.14.2", + "@types/eslint": "^9.6.1", + "@types/picomatch": "^3", + "@typescript-eslint/utils": "^8.27.0", "magic-string": "^0.30.0", - "picomatch": "^2.3.1", - "tslib": "2.6.2", - "typescript": "5", + "picomatch": "2 || 3 || 4", + "tslib": "^2.8.0", "unicode-emoji-utils": "^1.2.0" }, - "peerDependencies": { - "eslint": "7 || 8" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/ecma402-abstract": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", - "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", - "license": "MIT", - "dependencies": { - "@formatjs/intl-localematcher": "0.5.4", - "tslib": "^2.4.0" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.7.8", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz", - "integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==", - "license": "MIT", - "dependencies": { - "@formatjs/ecma402-abstract": "2.0.0", - "@formatjs/icu-skeleton-parser": "1.8.2", - "tslib": "^2.4.0" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz", - "integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==", - "license": "MIT", - "dependencies": { - "@formatjs/ecma402-abstract": "2.0.0", - "tslib": "^2.4.0" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/intl-localematcher": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", - "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", - "license": "MIT", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@formatjs/ts-transformer": { - "version": "3.13.14", - "resolved": "https://registry.npmjs.org/@formatjs/ts-transformer/-/ts-transformer-3.13.14.tgz", - "integrity": "sha512-TP/R54lxQ9Drzzimxrrt6yBT/xBofTgYl5wSTpyKe3Aq9vIBVcFmS6EOqycj0X34KGu3EpDPGO0ng8ZQZGLIFg==", - "license": "MIT", - "dependencies": { - "@formatjs/icu-messageformat-parser": "2.7.8", - "@types/json-stable-stringify": "^1.0.32", - "@types/node": "14 || 16 || 17", - "chalk": "^4.0.0", - "json-stable-stringify": "^1.0.1", - "tslib": "^2.4.0", - "typescript": "5" - }, - "peerDependencies": { - "ts-jest": ">=27" - }, - "peerDependenciesMeta": { - "ts-jest": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "license": "MIT" - }, - "node_modules/eslint-plugin-formatjs/node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", - "license": "MIT", - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "peerDependencies": { + "eslint": "^9.23.0" } }, - "node_modules/eslint-plugin-formatjs/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "license": "ISC", + "node_modules/eslint-plugin-jest": { + "version": "28.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.14.0.tgz", + "integrity": "sha512-P9s/qXSMTpRTerE2FQ0qJet2gKbcGyFTPAJipoKxmWqR6uuFqIqk8FuEfg5yBieOezVrEfAMZrEwJ6yEp+1MFQ==", + "license": "MIT", + "peer": true, "dependencies": { - "brace-expansion": "^2.0.1" + "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node": "^16.10.0 || ^18.12.0 || >=20.0.0" }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-formatjs/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "license": "0BSD" - }, - "node_modules/eslint-plugin-formatjs/node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", + "jest": "*" }, - "engines": { - "node": ">=14.17" + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } } }, - "node_modules/eslint-plugin-import": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", "license": "MIT", + "peer": true, "dependencies": { - "@rtsao/scc": "^1.1.0", + "aria-query": "^5.3.2", "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", "hasown": "^2.0.2", - "is-core-module": "^2.15.1", - "is-glob": "^4.0.3", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.0", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", - "tsconfig-paths": "^3.15.0" + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=4.0" }, "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, + "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", - "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", + "node_modules/eslint-plugin-jsx-a11y/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT", + "peer": true + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "peer": true, "dependencies": { - "@babel/runtime": "^7.20.7", - "aria-query": "^5.1.3", - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.6.2", - "axobject-query": "^3.1.1", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.3", - "language-tags": "=1.0.5", - "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "semver": "^6.3.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "node": "*" } }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, "node_modules/eslint-plugin-react": { - "version": "7.33.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", - "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "license": "MIT", + "peer": true, "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "array.prototype.tosorted": "^1.1.1", + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.12", + "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", + "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "object.hasown": "^1.1.2", - "object.values": "^1.1.6", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.4", + "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.8" + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.1.tgz", - "integrity": "sha512-Ck77j8hF7l9N4S/rzSLOWEKpn994YH6iwUK8fr9mXIaQvGpQYmOnQLbiue1u5kI5T1y+gdgqosnEAO9NCz0DBg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "license": "Apache-2.0", + "node_modules/eslint-plugin-react/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "peer": true + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "peer": true, "dependencies": { - "esutils": "^2.0.2" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=0.10.0" + "node": "*" } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", "license": "MIT", + "peer": true, "dependencies": { - "is-core-module": "^2.13.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-scope/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "license": "Apache-2.0", - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "license": "Apache-2.0", + "peer": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "peer": true + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "license": "MIT", + "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -10022,50 +10197,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "license": "MIT", + "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -10076,11 +10213,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint/node_modules/p-locate": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "license": "MIT", + "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -10091,42 +10242,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -10146,10 +10274,11 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -10162,6 +10291,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -10192,6 +10322,7 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -10200,13 +10331,15 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.x" } @@ -10242,15 +10375,6 @@ "node": ">= 0.8.0" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -10267,40 +10391,48 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/expr-eval-fork": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expr-eval-fork/-/expr-eval-fork-2.0.2.tgz", + "integrity": "sha512-NaAnObPVwHEYrODd7Jzp3zzT9pgTAlUUL4MZiZu9XAYPDpx89cPsfyEImFb2XY0vQNbrqg2CG7CLiI+Rs3seaQ==", + "license": "MIT", + "peer": true + }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -10318,6 +10450,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -10326,21 +10459,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } + "peer": true }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -10349,16 +10469,11 @@ "license": "MIT" }, "node_modules/fast-defer": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/fast-defer/-/fast-defer-1.1.8.tgz", - "integrity": "sha512-lEJeOH5VL5R09j6AA0D4Uvq7AgsHw0dAImQQ+F3iSyHZuAxyQfWobsagGpTcOPvJr3urmKRHrs+Gs9hV+/Qm/Q==", - "license": "MIT" - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "license": "MIT" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/fast-defer/-/fast-defer-1.1.9.tgz", + "integrity": "sha512-JP7Xm9HuePSeTT1DI78NeE9eAQvgNb9qNP2jlyQrcx4jiWM189omV6oyd0xaUPWHPlKmvDzz6H1FfPWIDU+xfg==", + "license": "MIT", + "peer": true }, "node_modules/fast-glob": { "version": "3.3.3", @@ -10376,6 +10491,18 @@ "node": ">=8.6.0" } }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -10386,18 +10513,20 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "funding": [ { "type": "github", @@ -10408,21 +10537,23 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 4.9.1" } }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -10433,6 +10564,7 @@ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "websocket-driver": ">=0.5.1" }, @@ -10449,11 +10581,30 @@ "bser": "2.1.1" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "license": "MIT", + "peer": true, "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -10469,20 +10620,22 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.8.0" } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "license": "MIT", + "peer": true, "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/file-loader": { @@ -10490,6 +10643,7 @@ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", "license": "MIT", + "peer": true, "dependencies": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0" @@ -10510,6 +10664,7 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -10524,54 +10679,25 @@ } }, "node_modules/file-selector": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", - "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.10.0.tgz", + "integrity": "sha512-iXLQxZTDe9qtBDkpaU4msOWNbh/4JxYSux7BsVxgt+0HBCpj9qPUFjD3SDBPLCJDoU3MsJh1i+CseQ/9488F/A==", "license": "MIT", + "peer": true, "dependencies": { - "tslib": "^2.4.0" + "tslib": "^2.7.0" }, "engines": { "node": ">= 12" } }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/filesize": { - "version": "10.1.6", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz", - "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", "license": "BSD-3-Clause", "engines": { - "node": ">= 10.4.0" + "node": ">= 0.4.0" } }, "node_modules/fill-range": { @@ -10591,22 +10717,24 @@ "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "license": "MIT", + "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -10618,6 +10746,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -10626,23 +10755,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/find-cache-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", - "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", "license": "MIT", - "dependencies": { - "common-path-prefix": "^3.0.0", - "pkg-dir": "^7.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "peer": true }, "node_modules/find-up": { "version": "4.1.0", @@ -10657,56 +10771,53 @@ "node": ">=8" } }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "license": "BSD-3-Clause", + "peer": true, "bin": { "flat": "cli.js" } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "license": "MIT", + "peer": true, "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "license": "ISC" + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", + "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "license": "ISC", + "peer": true }, "node_modules/focus-lock": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.6.tgz", "integrity": "sha512-Ik/6OCk9RQQ0T5Xw+hKNLWrjSMtv51dD4GRmJjbD5a58TIEpI5a5iXagKVl3Z5UuyslMCA8Xwnu76jQob62Yhg==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.0.3" }, @@ -10715,9 +10826,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -10725,6 +10836,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=4.0" }, @@ -10748,6 +10860,7 @@ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "license": "MIT", + "peer": true, "dependencies": { "is-callable": "^1.2.7" }, @@ -10758,73 +10871,108 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fork-ts-checker-webpack-plugin": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", - "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz", + "integrity": "sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==", "license": "MIT", + "peer": true, "dependencies": { - "@babel/code-frame": "^7.8.3", - "@types/json-schema": "^7.0.5", - "chalk": "^4.1.0", - "chokidar": "^3.4.2", - "cosmiconfig": "^6.0.0", + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^4.0.1", + "cosmiconfig": "^8.2.0", "deepmerge": "^4.2.2", - "fs-extra": "^9.0.0", - "glob": "^7.1.6", - "memfs": "^3.1.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", "minimatch": "^3.0.4", - "schema-utils": "2.7.0", - "semver": "^7.3.2", - "tapable": "^1.0.0" + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" }, "engines": { - "node": ">=10", - "yarn": ">=1.0.0" + "node": ">=14.21.3" }, "peerDependencies": { - "eslint": ">= 6", - "typescript": ">= 2.7", - "vue-template-compiler": "*", - "webpack": ">= 4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - }, - "vue-template-compiler": { - "optional": true - } + "typescript": ">3.6.0", + "webpack": "^5.11.0" } }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "node_modules/fork-ts-checker-webpack-plugin/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "peer": true + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", + "peer": true, "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8" + "node": "*" } }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "license": "MIT", + "peer": true, "dependencies": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">= 8.9.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", @@ -10832,10 +10980,11 @@ } }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -10843,24 +10992,16 @@ "node": ">=10" } }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -10868,16 +11009,18 @@ } }, "node_modules/form-urlencoded": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/form-urlencoded/-/form-urlencoded-4.1.4.tgz", - "integrity": "sha512-R7Vytos0gMYuPQTMwnNzvK9PBItNV+Qkm/pvghEZI3j2kMrzZmJlczAgHFmt12VV+IRYQXgTlSGP1PKAsMCIUA==", - "license": "MIT" + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/form-urlencoded/-/form-urlencoded-6.1.6.tgz", + "integrity": "sha512-0a2GKWoNjT55lPrjZWFUiJ8rK9e3rihCyNqZP2k/GSPfkjemZoDrYrNsvRSQHrKJ4qnD/wXj7KSQomz/lbF2Ew==", + "license": "MIT", + "peer": true }, "node_modules/formidable": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "license": "MIT", + "peer": true, "dependencies": { "@paralleldrive/cuid2": "^2.2.2", "dezalgo": "^1.0.4", @@ -10895,20 +11038,22 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } }, "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "license": "MIT", + "peer": true, "engines": { "node": "*" }, "funding": { - "type": "patreon", + "type": "github", "url": "https://github.com/sponsors/rawify" } }, @@ -10917,42 +11062,31 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" - }, "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "license": "MIT", + "peer": true, "dependencies": { - "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/fs-monkey": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", - "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", - "license": "Unlicense" - }, - "node_modules/fs-readdir-recursive": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "license": "MIT" + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "license": "Unlicense" }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -10988,6 +11122,7 @@ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -11008,10 +11143,21 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -11059,6 +11205,7 @@ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -11102,6 +11249,7 @@ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -11115,9 +11263,10 @@ } }, "node_modules/get-tsconfig": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", - "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -11126,17 +11275,11 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -11154,22 +11297,69 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "license": "ISC", + "peer": true, "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regex.js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", + "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" } }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } }, "node_modules/global-modules": { "version": "2.0.0", @@ -11210,12 +11400,16 @@ } }, "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "license": "MIT", + "peer": true, "engines": { - "node": ">=4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globalthis": { @@ -11223,6 +11417,7 @@ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "license": "MIT", + "peer": true, "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" @@ -11235,32 +11430,30 @@ } }, "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", "license": "MIT", + "peer": true, "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/globby/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/globby/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "license": "MIT", + "peer": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, "node_modules/gopd": { @@ -11281,11 +11474,19 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "license": "MIT" + "node_modules/gradient-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/gradient-string/-/gradient-string-2.0.2.tgz", + "integrity": "sha512-rEDCuqUQ4tbD78TpzsMtt5OIf0cBCSDWSJtUDaF6JsAh+k0v9r++NzxNEG87oDZx9ZwGhD8DaezR2L/yrw0Jdw==", + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.1.2", + "tinygradient": "^1.1.5" + }, + "engines": { + "node": ">=10" + } }, "node_modules/gzip-size": { "version": "6.0.0", @@ -11306,7 +11507,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/harmony-reflect": { "version": "1.6.2", @@ -11314,20 +11516,12 @@ "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==", "license": "(Apache-2.0 OR MPL-1.1)" }, - "node_modules/has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -11349,6 +11543,7 @@ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "license": "MIT", + "peer": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -11361,6 +11556,7 @@ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "license": "MIT", + "peer": true, "dependencies": { "dunder-proto": "^1.0.0" }, @@ -11415,24 +11611,17 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "license": "MIT", + "peer": true, "bin": { "he": "bin/he" } }, - "node_modules/history": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", - "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.7.6" - } - }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "react-is": "^16.7.0" } @@ -11441,13 +11630,15 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.1", "obuf": "^1.0.0", @@ -11459,13 +11650,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/hpack.js/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "license": "MIT", + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -11480,13 +11673,15 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/hpack.js/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -11517,7 +11712,8 @@ "url": "https://patreon.com/mdevils" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/html-escaper": { "version": "2.0.2", @@ -11530,6 +11726,7 @@ "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", "license": "MIT", + "peer": true, "dependencies": { "camel-case": "^4.1.2", "clean-css": "^5.2.2", @@ -11551,15 +11748,17 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "license": "MIT", + "peer": true, "engines": { "node": ">= 12" } }, "node_modules/html-webpack-plugin": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", - "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.7.tgz", + "integrity": "sha512-md+vXtdCAe60s1k6AU3dUyMJnDxUyQAwfwPKoLisvgUF1IXjtlLsk2se54+qfL9Mdm26bbwvjJybpNx48NKRLw==", "license": "MIT", + "peer": true, "dependencies": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", @@ -11599,6 +11798,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.0.0", @@ -11611,6 +11811,7 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "license": "BSD-2-Clause", + "peer": true, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -11619,35 +11820,43 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", + "peer": true, "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-parser-js": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "license": "MIT", + "peer": true, "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -11676,6 +11885,7 @@ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "license": "MIT", + "peer": true, "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -11695,6 +11905,26 @@ } } }, + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/http-vary": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http-vary/-/http-vary-1.0.3.tgz", + "integrity": "sha512-sx7Y8YTqF3o0mFJJvF66n8dbaE8v3liV1RgCz46XP5xK7dnzyZHvwMWRA115q5kjbCPBV65/nOMlgW54WLyiag==", + "license": "MIT", + "peer": true + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -11717,29 +11947,29 @@ "node": ">=10.17.0" } }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.18" + } + }, "node_modules/hyphenate-style-name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", - "license": "BSD-3-Clause" - }, - "node_modules/i18n-iso-countries": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-4.3.1.tgz", - "integrity": "sha512-yxeCvmT8yO1p/epv93c1OHnnYNNMOX6NUNpNfuvzSIcDyripS7OGeKXgzYGd5QI31UK+GBrMG0nPFNv0jrHggw==", - "license": "MIT", - "dependencies": { - "diacritics": "^1.3.0" - }, - "engines": { - "node": ">= 6" - } + "license": "BSD-3-Clause", + "peer": true }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "license": "MIT", + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -11752,6 +11982,7 @@ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "license": "ISC", + "peer": true, "engines": { "node": "^10 || ^12 || >= 14" }, @@ -11789,7 +12020,8 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/ignore": { "version": "5.3.2", @@ -11800,11 +12032,19 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/image-minimizer-webpack-plugin": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/image-minimizer-webpack-plugin/-/image-minimizer-webpack-plugin-3.8.3.tgz", "integrity": "sha512-Ex0cjNJc2FUSuwN7WHNyxkIZINP0M9lrN+uWJznMcsehiM5Z7ELwk+SEkSGEookK1GUd2wf+09jy1PEH5a5XmQ==", "license": "MIT", + "peer": true, "dependencies": { "schema-utils": "^4.2.0", "serialize-javascript": "^6.0.1" @@ -11839,6 +12079,7 @@ "resolved": "https://registry.npmjs.org/imask/-/imask-7.6.1.tgz", "integrity": "sha512-sJlIFM7eathUEMChTh9Mrfw/IgiWgJqBKq2VNbyXvBZ7ev/IlO6/KQTKlV/Fm+viQMLrFLG/zCuudrLIwgK2dg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime-corejs3": "^7.24.4" }, @@ -11850,12 +12091,13 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/immer": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", - "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", "license": "MIT", "funding": { "type": "opencollective", @@ -11863,10 +12105,11 @@ } }, "node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "license": "MIT" + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "license": "MIT", + "peer": true }, "node_modules/import-fresh": { "version": "3.3.1", @@ -11899,29 +12142,17 @@ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "license": "MIT", "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/imurmurhash": { @@ -11937,6 +12168,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11966,16 +12198,17 @@ "license": "ISC" }, "node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "version": "8.2.7", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", + "integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==", "license": "MIT", + "peer": true, "dependencies": { + "@inquirer/external-editor": "^1.0.0", "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", "cli-cursor": "^3.1.0", "cli-width": "^3.0.0", - "external-editor": "^3.0.3", "figures": "^3.0.0", "lodash": "^4.17.21", "mute-stream": "0.0.8", @@ -11996,6 +12229,7 @@ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "license": "MIT", + "peer": true, "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", @@ -12010,6 +12244,7 @@ "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.13.0" } @@ -12019,6 +12254,7 @@ "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.7.tgz", "integrity": "sha512-F134jIoeYMro/3I0h08D0Yt4N9o9pjddU/4IIxMMURqbAtI2wu70X8hvG1V48W49zXHXv3RKSF/po+0fDfsGjA==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@formatjs/ecma402-abstract": "2.2.4", "@formatjs/fast-memoize": "2.2.3", @@ -12031,17 +12267,29 @@ "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.4.tgz", "integrity": "sha512-lFyiQDVvSbQOpU+WFd//ILolGj4UgA/qXrKeZxdV14uKiAUiPAtX6XAn7WBCRi7Mx6I7EybM9E5yYn4BIpZWYg==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/fast-memoize": "2.2.3", "@formatjs/intl-localematcher": "0.5.8", "tslib": "2" } }, + "node_modules/intl-messageformat/node_modules/@formatjs/fast-memoize": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz", + "integrity": "sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "2" + } + }, "node_modules/intl-messageformat/node_modules/@formatjs/icu-messageformat-parser": { "version": "2.9.4", "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.4.tgz", "integrity": "sha512-Tbvp5a9IWuxUcpWNIW6GlMQYEc4rwNHR259uUFoKWNN1jM9obf9Ul0e+7r7MvFOBNcN+13K7NuKCKqQiAn1QEg==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/ecma402-abstract": "2.2.4", "@formatjs/icu-skeleton-parser": "1.8.8", @@ -12053,6 +12301,7 @@ "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.8.tgz", "integrity": "sha512-vHwK3piXwamFcx5YQdCdJxUQ1WdTl6ANclt5xba5zLGDv5Bsur7qz8AD7BevaKxITwpgDeU0u8My3AIibW9ywA==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/ecma402-abstract": "2.2.4", "tslib": "2" @@ -12063,6 +12312,7 @@ "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.8.tgz", "integrity": "sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "2" } @@ -12072,6 +12322,7 @@ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.0.0" } @@ -12081,6 +12332,7 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.10" } @@ -12090,6 +12342,7 @@ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -12106,6 +12359,7 @@ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -12129,6 +12383,7 @@ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "license": "MIT", + "peer": true, "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", @@ -12148,6 +12403,7 @@ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "license": "MIT", + "peer": true, "dependencies": { "has-bigints": "^1.0.2" }, @@ -12175,6 +12431,7 @@ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -12186,55 +12443,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/is-bun-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", - "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", - "license": "MIT", - "dependencies": { - "semver": "^7.7.1" - } - }, - "node_modules/is-bun-module/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -12262,6 +12476,7 @@ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", @@ -12279,6 +12494,7 @@ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -12319,6 +12535,7 @@ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3" }, @@ -12348,13 +12565,15 @@ } }, "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "license": "MIT", + "peer": true, "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" }, @@ -12377,53 +12596,94 @@ "node": ">=0.10.0" } }, - "node_modules/is-interactive": { + "node_modules/is-inside-container": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "license": "MIT", + "peer": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-invalid-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz", - "integrity": "sha512-aZMG0T3F34mTg4eTdszcGXx54oiZ4NtHSft3hWNJMGJXUUqdIj3cOZuHcU0nCWWcY3jd7yRe/3AEm3vSNTpBGQ==", + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "license": "MIT", - "dependencies": { - "is-glob": "^2.0.0" + "peer": true, + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": ">=0.10.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-invalid-path/node_modules/is-extglob": { + "node_modules/is-interactive": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "license": "MIT", + "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/is-invalid-path/node_modules/is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-mergeable-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-mergeable-object/-/is-mergeable-object-1.1.1.tgz", + "integrity": "sha512-CPduJfuGg8h8vW74WOxHtHmtQutyQBzR+3MjQ6iDHIYdbOnm1YC7jv43SqCoU8OPGTJD4nibmiryA4kmogbGrA==", + "license": "MIT", + "peer": true + }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "license": "MIT", + "peer": true, "dependencies": { - "is-extglob": "^1.0.0" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-map": { + "node_modules/is-negative-zero": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -12431,6 +12691,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-network-error": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", + "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -12445,6 +12718,7 @@ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -12461,6 +12735,7 @@ "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -12470,6 +12745,7 @@ "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", "license": "MIT", + "peer": true, "dependencies": { "is-path-inside": "^2.1.0" }, @@ -12477,11 +12753,12 @@ "node": ">=6" } }, - "node_modules/is-path-in-cwd/node_modules/is-path-inside": { + "node_modules/is-path-inside": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", "license": "MIT", + "peer": true, "dependencies": { "path-is-inside": "^1.0.2" }, @@ -12489,22 +12766,14 @@ "node": ">=6" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "license": "MIT", + "peer": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -12515,6 +12784,7 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "license": "MIT", + "peer": true, "dependencies": { "isobject": "^3.0.1" }, @@ -12533,6 +12803,7 @@ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", @@ -12560,6 +12831,7 @@ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -12572,6 +12844,7 @@ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3" }, @@ -12599,6 +12872,7 @@ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -12615,6 +12889,7 @@ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", @@ -12632,6 +12907,7 @@ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "license": "MIT", + "peer": true, "dependencies": { "which-typed-array": "^1.1.16" }, @@ -12647,6 +12923,7 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -12654,23 +12931,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-valid-path": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz", - "integrity": "sha512-+kwPrVDu9Ms03L90Qaml+79+6DZHqHyRoANI6IsZJ/g8frhnfchDOBCa0RbQ6/kdHt5CS5OeIEyrYznNuVN+8A==", - "license": "MIT", - "dependencies": { - "is-invalid-path": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -12683,6 +12949,7 @@ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3" }, @@ -12698,6 +12965,7 @@ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" @@ -12725,7 +12993,8 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/isexe": { "version": "2.0.0", @@ -12738,6 +13007,7 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -12781,33 +13051,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", @@ -12832,9 +13075,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", @@ -12849,6 +13092,7 @@ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "license": "MIT", + "peer": true, "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", @@ -12861,22 +13105,20 @@ "node": ">= 0.4" } }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "license": "Apache-2.0", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "peer": true, "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" + "@isaacs/cliui": "^8.0.2" }, - "bin": { - "jake": "bin/cli.js" + "funding": { + "url": "https://github.com/sponsors/isaacs" }, - "engines": { - "node": ">=10" + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jest": { @@ -12982,15 +13224,6 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, - "node_modules/jest-circus/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-cli": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", @@ -13013,15 +13246,73 @@ "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-cli/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-cli/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" } }, "node_modules/jest-config": { @@ -13101,15 +13392,6 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, - "node_modules/jest-config/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-diff": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", @@ -13347,6 +13629,16 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/jest-localstorage-mock": { + "version": "2.4.26", + "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.26.tgz", + "integrity": "sha512-owAJrYnjulVlMIXOYQIPRCCn3MmqI3GzgfZCXdD3/pmwrIvFMXcKVWZ+aMc44IzaASapg0Z4SEFxR+v5qxDA2w==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=6.16.0" + } + }, "node_modules/jest-matcher-utils": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", @@ -13446,15 +13738,6 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, - "node_modules/jest-message-util/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-mock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", @@ -13528,15 +13811,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-resolve/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-runner": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", @@ -13602,15 +13876,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-snapshot": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", @@ -13675,9 +13940,9 @@ "license": "MIT" }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -13703,6 +13968,18 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-validate": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", @@ -13732,6 +14009,18 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jest-validate/node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -13816,6 +14105,7 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -13833,10 +14123,21 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/js-toml": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/js-toml/-/js-toml-1.0.3.tgz", + "integrity": "sha512-sgyRKshBUSPIlUrbVXYQHReVZUXKHTldaW+Fj7KSan21vgnmMpuAAo00rBvm7W4HQrvZSvv186wNHlIjMPYC/A==", + "license": "MIT", + "peer": true, + "dependencies": { + "chevrotain": "^11.1.1", + "xregexp": "^5.1.2" + } + }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -13907,7 +14208,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -13926,6 +14228,7 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", @@ -13944,7 +14247,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/json5": { "version": "2.2.3", @@ -13959,9 +14263,9 @@ } }, "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -13975,6 +14279,7 @@ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", "license": "Public Domain", + "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -14007,6 +14312,7 @@ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "license": "MIT", + "peer": true, "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -14021,13 +14327,15 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "license": "MIT", + "peer": true, "dependencies": { "json-buffer": "3.0.1" } @@ -14041,6 +14349,16 @@ "node": ">=0.10.0" } }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -14054,25 +14372,31 @@ "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", - "license": "CC0-1.0" + "license": "CC0-1.0", + "peer": true }, "node_modules/language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", "license": "MIT", + "peer": true, "dependencies": { - "language-subtag-registry": "~0.3.2" + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" } }, "node_modules/launch-editor": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", - "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", + "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", "license": "MIT", + "peer": true, "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.8.1" + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" } }, "node_modules/leven": { @@ -14089,6 +14413,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "license": "MIT", + "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -14102,6 +14427,7 @@ "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", "license": "MIT", + "peer": true, "dependencies": { "immediate": "~3.0.5" } @@ -14111,6 +14437,7 @@ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "license": "MIT", + "peer": true, "engines": { "node": ">=14" }, @@ -14125,12 +14452,17 @@ "license": "MIT" }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -14138,6 +14470,7 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "license": "MIT", + "peer": true, "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -14152,6 +14485,7 @@ "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "lie": "3.1.1" } @@ -14160,6 +14494,7 @@ "version": "0.9.2", "resolved": "https://registry.npmjs.org/localforage-memoryStorageDriver/-/localforage-memoryStorageDriver-0.9.2.tgz", "integrity": "sha512-DRB4BkkW9o5HIetbsuvtcg98GP7J1JBRDyDMJK13hfr9QsNpnMW6UUWmU9c6bcRg99akR1mGZ/ubUV1Ek0fbpg==", + "peer": true, "dependencies": { "localforage": ">=1.4.0" } @@ -14177,65 +14512,59 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT", + "peer": true }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "license": "MIT" - }, - "node_modules/lodash.snakecase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.uniqby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "license": "MIT", + "peer": true, "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -14247,6 +14576,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -14264,6 +14612,7 @@ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.0.3" } @@ -14282,17 +14631,19 @@ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } }, "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "license": "MIT", + "peer": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/mailto-link": { @@ -14300,6 +14651,7 @@ "resolved": "https://registry.npmjs.org/mailto-link/-/mailto-link-2.0.0.tgz", "integrity": "sha512-b5FErkZ4t6mpH1IFZSw7Mm2IQHXQ2R0/5Q4xd7Rv8dVkWvE54mFG/UW7HjfFazXFjXTNsM+dSX2tTeIDrV9K9A==", "license": "MIT", + "peer": true, "dependencies": { "assert-ok": "~1.0.0", "cast-array": "~1.0.1", @@ -14310,52 +14662,33 @@ "node": ">= 12" } }, - "node_modules/mailto-link/node_modules/query-string": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz", - "integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==", + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "license": "MIT", "dependencies": { - "decode-uri-component": "^0.2.0", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "license": "MIT", - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/make-dir/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "license": "ISC" - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -14366,10 +14699,11 @@ } }, "node_modules/matchmediaquery": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.3.1.tgz", - "integrity": "sha512-Hlk20WQHRIm9EE9luN1kjRjYXAQToHOIAHPJn9buxBwuhfTHoKUcX+lXBbxc85DVQfXYbEQ4HcwQdd128E3qHQ==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.4.2.tgz", + "integrity": "sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==", "license": "MIT", + "peer": true, "dependencies": { "css-mediaquery": "^0.1.2" } @@ -14387,13 +14721,15 @@ "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "license": "CC0-1.0" + "license": "CC0-1.0", + "peer": true }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -14415,6 +14751,7 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -14439,6 +14776,7 @@ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -14456,11 +14794,24 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "license": "MIT", + "peer": true, "bin": { "mime": "cli.js" }, @@ -14469,10 +14820,11 @@ } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -14489,6 +14841,15 @@ "node": ">= 0.6" } }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -14498,22 +14859,11 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -14524,6 +14874,7 @@ "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz", "integrity": "sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q==", "license": "MIT", + "peer": true, "dependencies": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0", @@ -14545,6 +14896,7 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -14562,18 +14914,22 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -14581,15 +14937,20 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "peer": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } }, "node_modules/moment": { "version": "2.30.1", @@ -14605,6 +14966,7 @@ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -14620,6 +14982,7 @@ "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", "license": "MIT", + "peer": true, "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" @@ -14632,7 +14995,22 @@ "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "license": "ISC" + "license": "ISC", + "peer": true + }, + "node_modules/mylas": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz", + "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } }, "node_modules/nanoid": { "version": "3.3.11", @@ -14645,6 +15023,7 @@ } ], "license": "MIT", + "peer": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -14652,44 +15031,18 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, - "node_modules/napi-postinstall": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.1.6.tgz", - "integrity": "sha512-w1bClprmjwpybo+7M1Rd0N4QK5Ein8kH/1CQ0Wv8Q9vrLbDMakxc4rZpv8zYc8RVErUELJlFhM8UzOF3IqlYKw==", - "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "license": "MIT" }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "license": "MIT" - }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -14698,56 +15051,52 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "license": "MIT", + "peer": true, "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, - "node_modules/node-abi": { - "version": "3.74.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", - "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } + "peer": true }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "license": "(BSD-3-Clause OR GPL-2.0)", + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "license": "MIT", + "peer": true, + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, "engines": { - "node": ">= 6.13.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/node-int64": { @@ -14757,24 +15106,144 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", "license": "MIT" }, - "node_modules/normalize-path": { + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^10.2.1", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/nodemon/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/nodemon/node_modules/has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "node_modules/nodemon/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/nodemon/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14797,6 +15266,7 @@ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "boolbase": "^1.0.0" }, @@ -14805,9 +15275,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", - "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", "license": "MIT" }, "node_modules/object-assign": { @@ -14820,22 +15290,42 @@ } }, "node_modules/object-code": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/object-code/-/object-code-1.3.3.tgz", - "integrity": "sha512-/Ds4Xd5xzrtUOJ+xJQ57iAy0BZsZltOHssnDgcZ8DOhgh41q1YJCnTPnWdWSLkNGNnxYzhYChjc5dgC9mEERCA==", - "license": "MIT" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object-code/-/object-code-2.0.0.tgz", + "integrity": "sha512-qOwMF43O/VAD51nJAB7MKsf1yWksql6O1i0DHRo1yaOQM6xJQH0NAE9UKJzYB7lyKw1jnpeb2BmB8qakjxiYZA==", + "license": "MIT", + "peer": true }, "node_modules/object-filter": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/object-filter/-/object-filter-1.0.2.tgz", "integrity": "sha512-NahvP2vZcy1ZiiYah30CEPw0FpDcSkSePJBMpzl5EQgCmISijiGuJm3SPYp7U+Lf2TljyaIw3E5EgkEx/TNEVA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, "engines": { "node": ">= 0.4" }, @@ -14848,6 +15338,7 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" } @@ -14857,46 +15348,14 @@ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", - "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" + "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -14905,26 +15364,30 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "license": "MIT", + "peer": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" + "es-object-atoms": "^1.1.1" }, "engines": { "node": ">= 0.4" } }, - "node_modules/object.hasown": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", - "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "license": "MIT", + "peer": true, "dependencies": { + "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" @@ -14941,6 +15404,7 @@ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -14958,13 +15422,15 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", + "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -14973,10 +15439,11 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -15027,6 +15494,7 @@ "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", "license": "(WTFPL OR MIT)", + "peer": true, "bin": { "opener": "bin/opener-bin.js" } @@ -15036,6 +15504,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "license": "MIT", + "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -15053,6 +15522,7 @@ "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "license": "MIT", + "peer": true, "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -15071,20 +15541,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", "license": "MIT", + "peer": true, "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", @@ -15144,21 +15606,27 @@ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } }, "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", "license": "MIT", + "peer": true, "dependencies": { - "@types/retry": "0.12.0", + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", "retry": "^0.13.1" }, "engines": { - "node": ">=8" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-try": { @@ -15170,11 +15638,19 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0", + "peer": true + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", "license": "MIT", + "peer": true, "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -15211,22 +15687,35 @@ } }, "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "license": "MIT", "dependencies": { - "entities": "^4.4.0" + "entities": "^6.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -15236,11 +15725,93 @@ "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", "license": "MIT", + "peer": true, "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, + "node_modules/patch-package": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", + "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^10.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.2.4", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-package/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -15263,7 +15834,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "license": "(WTFPL OR MIT)" + "license": "(WTFPL OR MIT)", + "peer": true }, "node_modules/path-key": { "version": "3.1.1", @@ -15280,11 +15852,36 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC", + "peer": true + }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT", + "peer": true }, "node_modules/path-type": { "version": "4.0.0", @@ -15295,6 +15892,30 @@ "node": ">=8" } }, + "node_modules/path-unified": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/path-unified/-/path-unified-0.2.0.tgz", + "integrity": "sha512-MNKqvrKbbbb5p7XHXV6ZAsf/1f/yJQa13S/fcX0uua8ew58Tgc6jXV+16JyAbnR/clgCH+euKDxrF2STxMHdrg==", + "license": "MIT", + "peer": true + }, + "node_modules/path/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC", + "peer": true + }, + "node_modules/path/node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "2.0.3" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -15302,12 +15923,13 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -15318,6 +15940,7 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -15327,6 +15950,7 @@ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -15336,6 +15960,7 @@ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", "license": "MIT", + "peer": true, "dependencies": { "pinkie": "^2.0.0" }, @@ -15353,100 +15978,15 @@ } }, "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "license": "MIT", - "dependencies": { - "find-up": "^6.3.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "license": "MIT", - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "license": "MIT", - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "license": "MIT", "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "find-up": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/pkg-dir/node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/pkg-up": { @@ -15495,31 +16035,75 @@ "p-try": "^2.0.0" }, "engines": { - "node": ">=6" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 16" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/pkg-up/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^2.0.0" + "queue-lit": "^1.5.1" }, "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">=12" } }, "node_modules/popper.js": { @@ -15539,14 +16123,15 @@ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "funding": [ { "type": "opencollective", @@ -15562,8 +16147,9 @@ } ], "license": "MIT", + "peer": true, "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -15576,6 +16162,7 @@ "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", "license": "MIT", + "peer": true, "dependencies": { "postcss-selector-parser": "^6.0.11", "postcss-value-parser": "^4.2.0" @@ -15587,11 +16174,32 @@ "postcss": "^8.2.2" } }, + "node_modules/postcss-calc-ast-parser": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/postcss-calc-ast-parser/-/postcss-calc-ast-parser-0.1.4.tgz", + "integrity": "sha512-CebpbHc96zgFjGgdQ6BqBy6XIUgRx1xXWCAAk6oke02RZ5nxwo9KQejTg8y7uYEeI9kv8jKQPYjoe6REsY23vw==", + "license": "MIT", + "peer": true, + "dependencies": { + "postcss-value-parser": "^3.3.1" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/postcss-calc-ast-parser/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "license": "MIT", + "peer": true + }, "node_modules/postcss-colormin": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", "license": "MIT", + "peer": true, "dependencies": { "browserslist": "^4.23.0", "caniuse-api": "^3.0.0", @@ -15605,11 +16213,28 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-combine-duplicated-selectors": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/postcss-combine-duplicated-selectors/-/postcss-combine-duplicated-selectors-10.0.3.tgz", + "integrity": "sha512-IP0BmwFloCskv7DV7xqvzDXqMHpwdczJa6ZvIW8abgHdcIHs9mCJX2ltFhu3EwA51ozp13DByng30+Ke+eIExA==", + "license": "MIT", + "peer": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/postcss-convert-values": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", "license": "MIT", + "peer": true, "dependencies": { "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" @@ -15636,6 +16261,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@csstools/cascade-layer-name-parser": "^1.0.13", "@csstools/css-parser-algorithms": "^2.7.1", @@ -15654,6 +16280,7 @@ "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -15666,6 +16293,7 @@ "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -15678,6 +16306,7 @@ "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -15690,6 +16319,7 @@ "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -15697,11 +16327,30 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "peer": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, "node_modules/postcss-loader": { "version": "7.3.4", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", "license": "MIT", + "peer": true, "dependencies": { "cosmiconfig": "^8.3.5", "jiti": "^1.20.0", @@ -15720,10 +16369,11 @@ } }, "node_modules/postcss-loader/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -15731,11 +16381,62 @@ "node": ">=10" } }, + "node_modules/postcss-map": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/postcss-map/-/postcss-map-0.11.0.tgz", + "integrity": "sha512-cgHYZrH9aAMds90upYUPhYz8xnAcRD45SwuNns/nQHONIrPQDhpwk3JLsAQGOndQxnRVXfB6nB+3WqSMy8fqlA==", + "license": "Unlicense", + "peer": true, + "dependencies": { + "js-yaml": "^3.12.0", + "postcss": "^7.0.2", + "reduce-function-call": "^1.0.1" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/postcss-map/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "license": "ISC", + "peer": true + }, + "node_modules/postcss-map/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "license": "MIT", + "peer": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-map/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/postcss-merge-longhand": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", "license": "MIT", + "peer": true, "dependencies": { "postcss-value-parser": "^4.2.0", "stylehacks": "^6.1.1" @@ -15752,6 +16453,7 @@ "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", "license": "MIT", + "peer": true, "dependencies": { "browserslist": "^4.23.0", "caniuse-api": "^3.0.0", @@ -15765,11 +16467,26 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-minify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-minify/-/postcss-minify-1.2.0.tgz", + "integrity": "sha512-Cvyz+hW5eBG0okSSOGDeussQy4v9mZHRhMevP2jwADqDS1v2gfoLo94+g4fCeVYtESZKqr+ViMPv3yPkWZFmFQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "postcss-selector-parser": "^6.0 || ^7.0", + "postcss-value-parser": "^4.1" + }, + "peerDependencies": { + "postcss": "^8.0" + } + }, "node_modules/postcss-minify-font-values": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", "license": "MIT", + "peer": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -15785,6 +16502,7 @@ "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", "license": "MIT", + "peer": true, "dependencies": { "colord": "^2.9.3", "cssnano-utils": "^4.0.2", @@ -15802,6 +16520,7 @@ "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", "license": "MIT", + "peer": true, "dependencies": { "browserslist": "^4.23.0", "cssnano-utils": "^4.0.2", @@ -15819,6 +16538,7 @@ "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", "license": "MIT", + "peer": true, "dependencies": { "postcss-selector-parser": "^6.0.16" }, @@ -15834,6 +16554,7 @@ "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "license": "ISC", + "peer": true, "engines": { "node": "^10 || ^12 || >= 14" }, @@ -15846,6 +16567,7 @@ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "license": "MIT", + "peer": true, "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^7.0.0", @@ -15859,10 +16581,11 @@ } }, "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -15876,6 +16599,7 @@ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "license": "ISC", + "peer": true, "dependencies": { "postcss-selector-parser": "^7.0.0" }, @@ -15887,10 +16611,11 @@ } }, "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -15904,6 +16629,7 @@ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "license": "ISC", + "peer": true, "dependencies": { "icss-utils": "^5.0.0" }, @@ -15919,6 +16645,7 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", "license": "MIT", + "peer": true, "engines": { "node": "^14 || ^16 || >=18.0" }, @@ -15931,6 +16658,7 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", "license": "MIT", + "peer": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -15946,6 +16674,7 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", "license": "MIT", + "peer": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -15961,6 +16690,7 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", "license": "MIT", + "peer": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -15976,6 +16706,7 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", "license": "MIT", + "peer": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -15991,6 +16722,7 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", "license": "MIT", + "peer": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -16006,6 +16738,7 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", "license": "MIT", + "peer": true, "dependencies": { "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" @@ -16022,6 +16755,7 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", "license": "MIT", + "peer": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -16037,6 +16771,7 @@ "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", "license": "MIT", + "peer": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -16052,6 +16787,7 @@ "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", "license": "MIT", + "peer": true, "dependencies": { "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" @@ -16068,6 +16804,7 @@ "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", "license": "MIT", + "peer": true, "dependencies": { "browserslist": "^4.23.0", "caniuse-api": "^3.0.0" @@ -16084,6 +16821,7 @@ "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", "license": "MIT", + "peer": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, @@ -16095,12 +16833,13 @@ } }, "node_modules/postcss-rtlcss": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-rtlcss/-/postcss-rtlcss-5.1.2.tgz", - "integrity": "sha512-cmcgRoO1wL7IJyVHw0RneWI/5Oe75NLC2NLlQLsNI7hcui+yRcW4RrILfQa4FqKQRLTU4r5eF0YPi1qZpMzQpA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/postcss-rtlcss/-/postcss-rtlcss-5.7.1.tgz", + "integrity": "sha512-zE68CuARv5StOG/UQLa0W1Y/raUTzgJlfjtas43yh3/G1BFmoPEaHxPRHgeowXRFFhW33FehrNgsljxRLmPVWw==", "license": "Apache-2.0", + "peer": true, "dependencies": { - "rtlcss": "4.1.1" + "rtlcss": "4.3.0" }, "engines": { "node": ">=18.0.0" @@ -16114,6 +16853,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -16127,6 +16867,7 @@ "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", "license": "MIT", + "peer": true, "dependencies": { "postcss-value-parser": "^4.2.0", "svgo": "^3.2.0" @@ -16143,6 +16884,7 @@ "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", "license": "MIT", + "peer": true, "dependencies": { "postcss-selector-parser": "^6.0.16" }, @@ -16157,85 +16899,41 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" - }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prebuild-install/node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/prebuild-install/node_modules/tar-fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", - "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/prebuild-install/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } + "peer": true }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", "license": "MIT", + "peer": true, "dependencies": { "lodash": "^4.17.20", "renderkid": "^3.0.0" @@ -16245,7 +16943,9 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -16259,7 +16959,9 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -16267,11 +16969,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/prompts": { "version": "2.4.2", @@ -16302,6 +17015,7 @@ "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", "license": "MIT", + "peer": true, "dependencies": { "react-is": "^16.3.2", "warning": "^4.0.0" @@ -16314,7 +17028,8 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", @@ -16327,6 +17042,7 @@ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", + "peer": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -16339,7 +17055,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/psl": { "version": "1.15.0", @@ -16353,22 +17070,13 @@ "url": "https://github.com/sponsors/lupomontero" } }, - "node_modules/pubsub-js": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/pubsub-js/-/pubsub-js-1.9.5.tgz", - "integrity": "sha512-5MZ0I9i5JWVO7SizvOviKvZU2qaBbl2KQX150FAA+fJBwYpwOUId7aNygURWSdPzlsA/xZ/InUKXqBbzM0czTA==", + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, "license": "MIT" }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -16394,78 +17102,34 @@ ], "license": "MIT" }, - "node_modules/purgecss": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-5.0.0.tgz", - "integrity": "sha512-RAnuxrGuVyLLTr8uMbKaxDRGWMgK5CCYDfRyUNNcaz5P3kGgD2b7ymQGYEyo2ST7Tl/ScwFgf5l3slKMxHSbrw==", - "license": "MIT", - "dependencies": { - "commander": "^9.0.0", - "glob": "^8.0.3", - "postcss": "^8.4.4", - "postcss-selector-parser": "^6.0.7" - }, - "bin": { - "purgecss": "bin/purgecss.js" - } - }, - "node_modules/purgecss/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", "license": "MIT", + "peer": true, "dependencies": { - "balanced-match": "^1.0.0" + "tslib": "^2.8.1" } }, - "node_modules/purgecss/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", "license": "MIT", + "peer": true, "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/purgecss/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/purgecss/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" + "node": ">=16.0.0" } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -16474,12 +17138,41 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/query-string": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.1.tgz", + "integrity": "sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA==", + "license": "MIT", + "peer": true, + "dependencies": { + "decode-uri-component": "^0.2.0", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "license": "MIT" }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -16505,6 +17198,7 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -16514,54 +17208,33 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", + "peer": true, "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -16574,6 +17247,7 @@ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.6.8.tgz", "integrity": "sha512-yD6uN78XlFOkETQp6GRuVe0s5509x3XYx8PfPbirwFTYCj5/RfmSs9YZGCwkUrhZNFzj7tZPdpb+3k50mK1E4g==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.14.0", "@restart/context": "^2.1.4", @@ -16599,10 +17273,11 @@ } }, "node_modules/react-clientside-effect": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.7.tgz", - "integrity": "sha512-gce9m0Pk/xYYMEojRI9bgvqQAkl6hm7ozQvqWPyQx+kULiatdHgkNM1QG4DQRx5N9BAzWSCJmt9mMV8/KsdgVg==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.8.tgz", + "integrity": "sha512-ma2FePH0z3px2+WOu6h+YycZcEvFmmxIlAb62cF52bG86eMySciO/EQZeQMXd07kPCYB0a1dWDT5J+KE9mCDUw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.12.13" }, @@ -16615,6 +17290,7 @@ "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", "license": "MIT", + "peer": true, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" @@ -16655,13 +17331,69 @@ "node": ">=14" } }, - "node_modules/react-dev-utils/node_modules/filesize": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", - "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", - "license": "BSD-3-Clause", + "node_modules/react-dev-utils/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "license": "MIT", "engines": { - "node": ">= 0.4.0" + "node": ">=8" + } + }, + "node_modules/react-dev-utils/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/react-dev-utils/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/react-dev-utils/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/react-dev-utils/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" } }, "node_modules/react-dev-utils/node_modules/find-up": { @@ -16680,14 +17412,90 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/react-dev-utils/node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "node_modules/react-dev-utils/node_modules/fork-ts-checker-webpack-plugin": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", + "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=10", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "eslint": ">= 6", + "typescript": ">= 2.7", + "vue-template-compiler": "*", + "webpack": ">= 4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/react-dev-utils/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-dev-utils/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/react-dev-utils/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/react-dev-utils/node_modules/loader-utils": { @@ -16714,6 +17522,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/react-dev-utils/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/react-dev-utils/node_modules/p-locate": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", @@ -16723,10 +17543,82 @@ "p-limit": "^3.0.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/react-dev-utils/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/react-dev-utils/node_modules/schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/react-dev-utils/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-dev-utils/node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react-dev-utils/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" } }, "node_modules/react-dom": { @@ -16734,6 +17626,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -16743,10 +17636,11 @@ } }, "node_modules/react-dropzone": { - "version": "14.3.8", - "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz", - "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==", + "version": "14.4.1", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.4.1.tgz", + "integrity": "sha512-QDuV76v3uKbHiH34SpwifZ+gOLi1+RdsCO1kl5vxMT4wW8R82+sthjvBw4th3NHF/XX6FBsqDYZVNN+pnhaw0g==", "license": "MIT", + "peer": true, "dependencies": { "attr-accept": "^2.2.4", "file-selector": "^2.1.0", @@ -16764,6 +17658,7 @@ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.7.0" }, @@ -16771,19 +17666,6 @@ "node": ">= 12" } }, - "node_modules/react-error-boundary": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.1.2.tgz", - "integrity": "sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "peerDependencies": { - "react": ">=16.13.1" - } - }, "node_modules/react-error-overlay": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.1.0.tgz", @@ -16797,10 +17679,11 @@ "license": "MIT" }, "node_modules/react-focus-lock": { - "version": "2.13.6", - "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.13.6.tgz", - "integrity": "sha512-ehylFFWyYtBKXjAO9+3v8d0i+cnc1trGS0vlTGhzFW1vbFXVUTmR8s2tt/ZQG8x5hElg6rhENlLG1H3EZK0Llg==", + "version": "2.13.7", + "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.13.7.tgz", + "integrity": "sha512-20lpZHEQrXPb+pp1tzd4ULL6DyO5D2KnR0G69tTDdydrmNhU7pdFmbQUYVyHUgp+xN29IuFR0PVuhOmvaZL9Og==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.0.0", "focus-lock": "^1.3.6", @@ -16820,24 +17703,25 @@ } }, "node_modules/react-focus-on": { - "version": "3.9.4", - "resolved": "https://registry.npmjs.org/react-focus-on/-/react-focus-on-3.9.4.tgz", - "integrity": "sha512-NFKmeH6++wu8e7LJcbwV8TTd4L5w/U5LMXTMOdUcXhCcZ7F5VOvgeTHd4XN1PD7TNmdvldDu/ENROOykUQ4yQg==", + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/react-focus-on/-/react-focus-on-3.10.2.tgz", + "integrity": "sha512-Ytdx2dh6yoCc2HI4Y7u5bI1xF1oeeRud52v8zQdGsyxyVC5W/dwcgQGp+CCpoLGQegwKHybH8diVj+Qn23y+hA==", "license": "MIT", + "peer": true, "dependencies": { - "aria-hidden": "^1.2.2", - "react-focus-lock": "^2.11.3", - "react-remove-scroll": "^2.6.0", - "react-style-singleton": "^2.2.1", + "aria-hidden": "^1.2.5", + "react-focus-lock": "^2.13.7", + "react-remove-scroll": "^2.6.4", + "react-style-singleton": "^2.2.3", "tslib": "^2.3.1", - "use-sidecar": "^1.1.2" + "use-sidecar": "^1.1.3" }, "engines": { "node": ">=8.5.0" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -16865,6 +17749,7 @@ "resolved": "https://registry.npmjs.org/react-imask/-/react-imask-7.6.1.tgz", "integrity": "sha512-vLNfzcCz62Yzx/GRGh5tiCph9Gbh2cZu+Tz8OiO5it2eNuuhpA0DWhhSlOtVtSJ80+Bx+vFK5De8eQ9AmbkXzA==", "license": "MIT", + "peer": true, "dependencies": { "imask": "^7.6.1", "prop-types": "^15.8.1" @@ -16881,6 +17766,7 @@ "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.8.9.tgz", "integrity": "sha512-TUfj5E7lyUDvz/GtovC9OMh441kBr08rtIbgh3p0R8iF3hVY+V2W9Am7rb8BpJ/29BH1utJOqOOhmvEVh3GfZg==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@formatjs/ecma402-abstract": "2.2.4", "@formatjs/icu-messageformat-parser": "2.9.4", @@ -16908,17 +17794,29 @@ "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.2.4.tgz", "integrity": "sha512-lFyiQDVvSbQOpU+WFd//ILolGj4UgA/qXrKeZxdV14uKiAUiPAtX6XAn7WBCRi7Mx6I7EybM9E5yYn4BIpZWYg==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/fast-memoize": "2.2.3", "@formatjs/intl-localematcher": "0.5.8", "tslib": "2" } }, + "node_modules/react-intl/node_modules/@formatjs/fast-memoize": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.3.tgz", + "integrity": "sha512-3jeJ+HyOfu8osl3GNSL4vVHUuWFXR03Iz9jjgI7RwjG6ysu/Ymdr0JRCPHfF5yGbTE6JCrd63EpvX1/WybYRbA==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "2" + } + }, "node_modules/react-intl/node_modules/@formatjs/icu-messageformat-parser": { "version": "2.9.4", "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.9.4.tgz", "integrity": "sha512-Tbvp5a9IWuxUcpWNIW6GlMQYEc4rwNHR259uUFoKWNN1jM9obf9Ul0e+7r7MvFOBNcN+13K7NuKCKqQiAn1QEg==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/ecma402-abstract": "2.2.4", "@formatjs/icu-skeleton-parser": "1.8.8", @@ -16930,6 +17828,7 @@ "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.8.tgz", "integrity": "sha512-vHwK3piXwamFcx5YQdCdJxUQ1WdTl6ANclt5xba5zLGDv5Bsur7qz8AD7BevaKxITwpgDeU0u8My3AIibW9ywA==", "license": "MIT", + "peer": true, "dependencies": { "@formatjs/ecma402-abstract": "2.2.4", "tslib": "2" @@ -16940,6 +17839,7 @@ "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.8.tgz", "integrity": "sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "2" } @@ -16948,19 +17848,23 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT" + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-loading-skeleton": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.5.0.tgz", "integrity": "sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ==", "license": "MIT", + "peer": true, "peerDependencies": { "react": ">=16.8.0" } @@ -16970,6 +17874,7 @@ "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.2.1.tgz", "integrity": "sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.13.8", "@popperjs/core": "^2.11.6", @@ -16990,6 +17895,7 @@ "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", "license": "MIT", + "peer": true, "dependencies": { "react-fast-compare": "^3.0.1", "warning": "^4.0.2" @@ -17004,47 +17910,36 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/react-proptype-conditional-require/-/react-proptype-conditional-require-1.0.4.tgz", "integrity": "sha512-nopsRn7KnGgazBe2c3H2+Kf+Csp6PGDRLiBkYEDMKY8o/EIgft/WnIm/OnAKTawZiLnJXHAqhpFBddvs6NiXlw==", - "license": "MIT" - }, - "node_modules/react-redux": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", - "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.15.4", - "@types/react-redux": "^7.1.20", - "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^17.0.2" - }, - "peerDependencies": { - "react": "^16.8.3 || ^17 || ^18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } + "peer": true }, "node_modules/react-refresh": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.16.0.tgz", - "integrity": "sha512-FPvF2XxTSikpJxcr+bHut2H4gJ17+18Uy20D5/F+SKzFap62R3cM5wH6b8WN3LyGSYeQilLEcJcR1fjBSI2S1A==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/react-refresh-typescript": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/react-refresh-typescript/-/react-refresh-typescript-2.0.11.tgz", + "integrity": "sha512-u6afET6t6NDNcxD67wSVlgazX/CiRuC7qSqALZueMckjmPQnOBajmwibJr2TTU6MtttDNPjBhD8K3AB8i0opJg==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react-refresh": "0.10.x || 0.11.x || 0.12.x || 0.13.x || 0.14.x || 0.15.x || 0.16.x || 0.17.x || 0.18.x || 0.19.x", + "typescript": "^4.8 || ^5.0" + } + }, "node_modules/react-remove-scroll": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", - "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", "license": "MIT", + "peer": true, "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", @@ -17070,6 +17965,7 @@ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", "license": "MIT", + "peer": true, "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" @@ -17088,30 +17984,32 @@ } }, "node_modules/react-responsive": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-8.2.0.tgz", - "integrity": "sha512-iagCqVrw4QSjhxKp3I/YK6+ODkWY6G+YPElvdYKiUUbywwh9Ds0M7r26Fj2/7dWFFbOpcGnJE6uE7aMck8j5Qg==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.1.tgz", + "integrity": "sha512-OM5/cRvbtUWEX8le8RCT8scA8y2OPtb0Q/IViEyCEM5FBN8lRrkUOZnu87I88A6njxDldvxG+rLBxWiA7/UM9g==", "license": "MIT", + "peer": true, "dependencies": { "hyphenate-style-name": "^1.0.0", - "matchmediaquery": "^0.3.0", + "matchmediaquery": "^0.4.2", "prop-types": "^15.6.1", - "shallow-equal": "^1.1.0" + "shallow-equal": "^3.1.0" }, "engines": { - "node": ">= 0.10" + "node": ">=14" }, "peerDependencies": { "react": ">=16.8.0" } }, "node_modules/react-router": { - "version": "6.29.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.29.0.tgz", - "integrity": "sha512-DXZJoE0q+KyeVw75Ck6GkPxFak63C4fGqZGNijnWgzB/HzSP1ZfTlBj5COaGWwhrMQ/R8bXiq5Ooy4KG+ReyjQ==", + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", "license": "MIT", + "peer": true, "dependencies": { - "@remix-run/router": "1.22.0" + "@remix-run/router": "1.23.2" }, "engines": { "node": ">=14.0.0" @@ -17121,13 +18019,14 @@ } }, "node_modules/react-router-dom": { - "version": "6.29.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.29.0.tgz", - "integrity": "sha512-pkEbJPATRJ2iotK+wUwHfy0xs2T59YPEN8BQxVCPeBZvK7kfPESRc/nyxzdcxR17hXgUPYx2whMwl+eo9cUdnQ==", + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", "license": "MIT", + "peer": true, "dependencies": { - "@remix-run/router": "1.22.0", - "react-router": "6.29.0" + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" }, "engines": { "node": ">=14.0.0" @@ -17137,34 +18036,17 @@ "react-dom": ">=16.8" } }, - "node_modules/react-shallow-renderer": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", - "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "license": "MIT", - "dependencies": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-share": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/react-share/-/react-share-4.4.1.tgz", - "integrity": "sha512-AJ9m9RiJssqvYg7MoJUc9J0D7b/liWrsfQ99ndKc5vJ4oVHHd4Fy87jBlKEQPibT40oYA3AQ/a9/oQY6/yaigw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/react-share/-/react-share-5.2.2.tgz", + "integrity": "sha512-z0nbOX6X6vHHWAvXduNkYeJUKTKNpKM5Xpmc5a2BxjJhUWl+sE7AsSEMmYEUj2DuDjZr5m7KFIGF0sQPKcUN6w==", "license": "MIT", "dependencies": { "classnames": "^2.3.2", "jsonp": "^0.2.1" }, - "engines": { - "node": ">=6.9.0", - "npm": ">=5.0.0" - }, "peerDependencies": { - "react": "^16.3.0 || ^17 || ^18" + "react": "^17 || ^18 || ^19" } }, "node_modules/react-side-effect": { @@ -17181,6 +18063,7 @@ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", "license": "MIT", + "peer": true, "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" @@ -17203,6 +18086,7 @@ "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz", "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -17211,31 +18095,12 @@ "react": "^16.8.3 || ^17.0.0-0 || ^18.0.0" } }, - "node_modules/react-test-renderer": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz", - "integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==", - "license": "MIT", - "dependencies": { - "react-is": "^18.3.1", - "react-shallow-renderer": "^16.15.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-test-renderer/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -17247,11 +18112,32 @@ "react-dom": ">=16.6.0" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -17262,15 +18148,17 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, + "peer": true, "engines": { - "node": ">=8.10.0" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/rechoir": { @@ -17278,6 +18166,7 @@ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "license": "MIT", + "peer": true, "dependencies": { "resolve": "^1.20.0" }, @@ -17297,10 +18186,39 @@ "node": ">=6.0.0" } }, + "node_modules/recursive-readdir/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/recursive-readdir/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/recursive-readdir/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, "license": "MIT", "dependencies": { "indent-string": "^4.0.0", @@ -17310,51 +18228,36 @@ "node": ">=8" } }, - "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/redux-logger": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz", - "integrity": "sha512-JoCIok7bg/XpqA1JqCqXFypuqBbQzGQySrhFzewB7ThcnysTO30l4VCst86AuB9T9tuT03MAA56Jw2PNhRSNCg==", + "node_modules/reduce-function-call": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", + "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==", "license": "MIT", + "peer": true, "dependencies": { - "deep-diff": "^0.3.5" + "balanced-match": "^1.0.0" } }, - "node_modules/redux-mock-store": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.5.tgz", - "integrity": "sha512-YxX+ofKUTQkZE4HbhYG4kKGr7oCTJfB0GLy7bSeqx86GLpGirrbUWstMnqXkqHNaQpcnbMGbof2dYs5KsPE6Zg==", - "dev": true, + "node_modules/reduce-function-call/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT", - "dependencies": { - "lodash.isplainobject": "^4.0.6" - }, - "peerDependencies": { - "redux": "*" - } + "peer": true }, - "node_modules/redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", - "license": "MIT", - "peerDependencies": { - "redux": "^4" - } + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0", + "peer": true }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -17376,13 +18279,15 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "license": "MIT", + "peer": true, "dependencies": { "regenerate": "^1.4.2" }, @@ -17390,32 +18295,19 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, "node_modules/regex-parser": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.1.tgz", "integrity": "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -17432,17 +18324,18 @@ } }, "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "license": "MIT", + "peer": true, "dependencies": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", + "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", + "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "unicode-match-property-value-ecmascript": "^2.2.1" }, "engines": { "node": ">=4" @@ -17452,37 +18345,28 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { - "jsesc": "~3.0.2" + "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.10" } @@ -17492,6 +18376,7 @@ "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", "license": "MIT", + "peer": true, "dependencies": { "css-select": "^4.1.3", "dom-converter": "^0.2.0", @@ -17514,6 +18399,7 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -17524,19 +18410,13 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "license": "MIT" }, - "node_modules/reselect": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", - "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", - "license": "MIT" - }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -17571,16 +18451,11 @@ "node": ">=8" } }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", - "license": "MIT" - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -17591,6 +18466,7 @@ "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", "license": "MIT", + "peer": true, "dependencies": { "adjust-sourcemap-loader": "^4.0.0", "convert-source-map": "^1.7.0", @@ -17606,13 +18482,15 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/resolve-url-loader/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -17631,6 +18509,7 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "license": "MIT", + "peer": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -17644,6 +18523,7 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 4" } @@ -17664,6 +18544,7 @@ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "deprecated": "Rimraf versions prior to v4 are no longer supported", "license": "ISC", + "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -17672,10 +18553,11 @@ } }, "node_modules/rtlcss": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", - "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "license": "MIT", + "peer": true, "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0", @@ -17689,11 +18571,25 @@ "node": ">=12.0.0" } }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.12.0" } @@ -17726,6 +18622,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -17735,6 +18632,7 @@ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", @@ -17767,13 +18665,15 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "license": "MIT", + "peer": true, "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" @@ -17790,6 +18690,7 @@ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -17809,10 +18710,11 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.85.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.1.tgz", - "integrity": "sha512-Uk8WpxM5v+0cMR0XjX9KfRIacmSG86RH4DCCZjLU2rFh5tyutt9siAXJ7G+YfxQ99Q6wrRMbMlVl6KqUms71ag==", + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -17828,30 +18730,395 @@ "@parcel/watcher": "^2.4.1" } }, + "node_modules/sass-embedded": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.97.3.tgz", + "integrity": "sha512-eKzFy13Nk+IRHhlAwP3sfuv+PzOrvzUkwJK2hdoCKYcWGSdmwFpeGpWmyewdw8EgBnsKaSBtgf/0b2K635ecSA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@bufbuild/protobuf": "^2.5.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-all-unknown": "1.97.3", + "sass-embedded-android-arm": "1.97.3", + "sass-embedded-android-arm64": "1.97.3", + "sass-embedded-android-riscv64": "1.97.3", + "sass-embedded-android-x64": "1.97.3", + "sass-embedded-darwin-arm64": "1.97.3", + "sass-embedded-darwin-x64": "1.97.3", + "sass-embedded-linux-arm": "1.97.3", + "sass-embedded-linux-arm64": "1.97.3", + "sass-embedded-linux-musl-arm": "1.97.3", + "sass-embedded-linux-musl-arm64": "1.97.3", + "sass-embedded-linux-musl-riscv64": "1.97.3", + "sass-embedded-linux-musl-x64": "1.97.3", + "sass-embedded-linux-riscv64": "1.97.3", + "sass-embedded-linux-x64": "1.97.3", + "sass-embedded-unknown-all": "1.97.3", + "sass-embedded-win32-arm64": "1.97.3", + "sass-embedded-win32-x64": "1.97.3" + } + }, + "node_modules/sass-embedded-all-unknown": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.97.3.tgz", + "integrity": "sha512-t6N46NlPuXiY3rlmG6/+1nwebOBOaLFOOVqNQOC2cJhghOD4hh2kHNQQTorCsbY9S1Kir2la1/XLBwOJfui0xg==", + "cpu": [ + "!arm", + "!arm64", + "!riscv64", + "!x64" + ], + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "sass": "1.97.3" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.97.3.tgz", + "integrity": "sha512-cRTtf/KV/q0nzGZoUzVkeIVVFv3L/tS1w4WnlHapphsjTXF/duTxI8JOU1c/9GhRPiMdfeXH7vYNcMmtjwX7jg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.97.3.tgz", + "integrity": "sha512-aiZ6iqiHsUsaDx0EFbbmmA0QgxicSxVVN3lnJJ0f1RStY0DthUkquGT5RJ4TPdaZ6ebeJWkboV4bra+CP766eA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.97.3.tgz", + "integrity": "sha512-zVEDgl9JJodofGHobaM/q6pNETG69uuBIGQHRo789jloESxxZe82lI3AWJQuPmYCOG5ElfRthqgv89h3gTeLYA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.97.3.tgz", + "integrity": "sha512-3ke0le7ZKepyXn/dKKspYkpBC0zUk/BMciyP5ajQUDy4qJwobd8zXdAq6kOkdiMB+d9UFJOmEkvgFJHl3lqwcw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.97.3.tgz", + "integrity": "sha512-fuqMTqO4gbOmA/kC5b9y9xxNYw6zDEyfOtMgabS7Mz93wimSk2M1quQaTJnL98Mkcsl2j+7shNHxIS/qpcIDDA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.97.3.tgz", + "integrity": "sha512-b/2RBs/2bZpP8lMkyZ0Px0vkVkT8uBd0YXpOwK7iOwYkAT8SsO4+WdVwErsqC65vI5e1e5p1bb20tuwsoQBMVA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.97.3.tgz", + "integrity": "sha512-2lPQ7HQQg4CKsH18FTsj2hbw5GJa6sBQgDsls+cV7buXlHjqF8iTKhAQViT6nrpLK/e8nFCoaRgSqEC8xMnXuA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.97.3.tgz", + "integrity": "sha512-IP1+2otCT3DuV46ooxPaOKV1oL5rLjteRzf8ldZtfIEcwhSgSsHgA71CbjYgLEwMY9h4jeal8Jfv3QnedPvSjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.97.3.tgz", + "integrity": "sha512-cBTMU68X2opBpoYsSZnI321gnoaiMBEtc+60CKCclN6PCL3W3uXm8g4TLoil1hDD6mqU9YYNlVG6sJ+ZNef6Lg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.97.3.tgz", + "integrity": "sha512-Lij0SdZCsr+mNRSyDZ7XtJpXEITrYsaGbOTz5e6uFLJ9bmzUbV7M8BXz2/cA7bhfpRPT7/lwRKPdV4+aR9Ozcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.97.3.tgz", + "integrity": "sha512-sBeLFIzMGshR4WmHAD4oIM7WJVkSoCIEwutzptFtGlSlwfNiijULp+J5hA2KteGvI6Gji35apR5aWj66wEn/iA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.97.3.tgz", + "integrity": "sha512-/oWJ+OVrDg7ADDQxRLC/4g1+Nsz1g4mkYS2t6XmyMJKFTFK50FVI2t5sOdFH+zmMp+nXHKM036W94y9m4jjEcw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.97.3.tgz", + "integrity": "sha512-l3IfySApLVYdNx0Kjm7Zehte1CDPZVcldma3dZt+TfzvlAEerM6YDgsk5XEj3L8eHBCgHgF4A0MJspHEo2WNfA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.97.3.tgz", + "integrity": "sha512-Kwqwc/jSSlcpRjULAOVbndqEy2GBzo6OBmmuBVINWUaJLJ8Kczz3vIsDUWLfWz/kTEw9FHBSiL0WCtYLVAXSLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-unknown-all": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.97.3.tgz", + "integrity": "sha512-/GHajyYJmvb0IABUQHbVHf1nuHPtIDo/ClMZ81IDr59wT5CNcMe7/dMNujXwWugtQVGI5UGmqXWZQCeoGnct8Q==", + "license": "MIT", + "optional": true, + "os": [ + "!android", + "!darwin", + "!linux", + "!win32" + ], + "peer": true, + "dependencies": { + "sass": "1.97.3" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.97.3.tgz", + "integrity": "sha512-RDGtRS1GVvQfMGAmVXNxYiUOvPzn9oO1zYB/XUM9fudDRnieYTcUytpNTQZLs6Y1KfJxgt5Y+giRceC92fT8Uw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.97.3.tgz", + "integrity": "sha512-SFRa2lED9UEwV6vIGeBXeBOLKF+rowF3WmNfb/BzhxmdAsKofCXrJ8ePW7OcDVrvNEbTOGwhsReIsF5sH8fVaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/sass-loader": { - "version": "13.3.3", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.3.tgz", - "integrity": "sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==", + "version": "16.0.7", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.7.tgz", + "integrity": "sha512-w6q+fRHourZ+e+xA1kcsF27iGM6jdB8teexYCfdUw0sYgcDNeZESnDNT9sUmmPm3ooziwUJXGwZJSTF3kOdBfA==", "license": "MIT", + "peer": true, "dependencies": { "neo-async": "^2.6.2" }, "engines": { - "node": ">= 14.15.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "fibers": ">= 3.1.0", + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "sass": "^1.3.0", "sass-embedded": "*", "webpack": "^5.0.0" }, "peerDependenciesMeta": { - "fibers": { + "@rspack/core": { "optional": true }, "node-sass": { @@ -17862,41 +19129,20 @@ }, "sass-embedded": { "optional": true + }, + "webpack": { + "optional": true } } }, - "node_modules/sass/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/sass/node_modules/immutable": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz", - "integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==", - "license": "MIT" - }, - "node_modules/sass/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", + "node_modules/sax": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", + "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", + "license": "BlueOak-1.0.0", + "peer": true, "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "node": ">=11.0.0" } }, "node_modules/saxes": { @@ -17916,15 +19162,17 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" } }, "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -17940,10 +19188,11 @@ } }, "node_modules/schema-utils/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -17960,6 +19209,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -17971,25 +19221,28 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", + "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", "license": "MIT", + "peer": true, "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" + "@peculiar/x509": "^1.14.2", + "pkijs": "^3.3.3" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/semver": { @@ -18002,24 +19255,25 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", + "peer": true, "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -18030,6 +19284,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -18038,42 +19293,40 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "peer": true }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "randombytes": "^2.1.0" } }, "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", "license": "MIT", + "peer": true, "dependencies": { - "accepts": "~1.3.4", + "accepts": "~1.3.8", "batch": "0.6.1", "debug": "2.6.9", "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" }, "engines": { "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serve-index/node_modules/debug": { @@ -18081,6 +19334,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -18090,62 +19344,56 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } }, "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "license": "MIT", + "peer": true, "dependencies": { "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" }, "engines": { "node": ">= 0.6" } }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "license": "ISC" - }, "node_modules/serve-index/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "license": "ISC" + "license": "MIT", + "peer": true }, "node_modules/serve-index/node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "license": "MIT", + "peer": true, "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -18156,6 +19404,7 @@ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "license": "MIT", + "peer": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -18173,6 +19422,7 @@ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "license": "MIT", + "peer": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -18188,6 +19438,7 @@ "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", "license": "MIT", + "peer": true, "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", @@ -18201,13 +19452,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "license": "MIT", + "peer": true, "dependencies": { "kind-of": "^6.0.2" }, @@ -18216,54 +19469,63 @@ } }, "node_modules/shallow-equal": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", - "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==", - "license": "MIT" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==", + "license": "MIT", + "peer": true }, "node_modules/sharp": { - "version": "0.32.6", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", - "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.2", - "node-addon-api": "^6.1.0", - "prebuild-install": "^7.1.1", - "semver": "^7.5.4", - "simple-get": "^4.0.1", - "tar-fs": "^3.0.4", - "tunnel-agent": "^0.6.0" + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" }, "engines": { - "node": ">=14.15.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" } }, - "node_modules/sharp/node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/sharp/node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "license": "MIT" - }, "node_modules/sharp/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -18293,9 +19555,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", - "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -18309,6 +19571,7 @@ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", + "peer": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -18328,6 +19591,7 @@ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", + "peer": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -18344,6 +19608,7 @@ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -18362,6 +19627,7 @@ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -18382,71 +19648,38 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, "license": "MIT", "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT" - }, "node_modules/sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", "license": "MIT", + "peer": true, "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", @@ -18463,22 +19696,30 @@ "license": "MIT" }, "node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/snake-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "license": "MIT", + "peer": true, "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, "node_modules/sockjs": { @@ -18486,6 +19727,7 @@ "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "license": "MIT", + "peer": true, "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^8.3.2", @@ -18497,6 +19739,7 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "license": "MIT", + "peer": true, "bin": { "uuid": "dist/bin/uuid" } @@ -18505,15 +19748,17 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "license": "BSD-3-Clause", + "peer": true, "engines": { - "node": ">= 8" + "node": ">= 12" } }, "node_modules/source-map-js": { @@ -18521,6 +19766,7 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -18530,6 +19776,7 @@ "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.2.tgz", "integrity": "sha512-oYwAqCuL0OZhBoSgmdrLa7mv9MjommVMiQIWgcztf+eS4+8BfcUee6nenFnDhKOhzAVnk5gpZdfnz1iiBv+5sg==", "license": "MIT", + "peer": true, "dependencies": { "iconv-lite": "^0.6.3", "source-map-js": "^1.0.2" @@ -18550,6 +19797,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -18581,6 +19829,7 @@ "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.1.0", "handle-thing": "^2.0.0", @@ -18597,6 +19846,7 @@ "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.1.0", "detect-node": "^2.0.4", @@ -18611,6 +19861,7 @@ "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -18621,12 +19872,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "license": "BSD-3-Clause" }, - "node_modules/stable-hash": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", - "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", - "license": "MIT" - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -18652,28 +19897,41 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } }, - "node_modules/streamx": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", - "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "license": "MIT", + "peer": true, "dependencies": { - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" }, - "optionalDependencies": { - "bare-events": "^2.2.0" + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/stream": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/stream/-/stream-0.0.3.tgz", + "integrity": "sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A==", + "license": "MIT", + "peer": true, + "dependencies": { + "component-emitter": "^2.0.0" } }, "node_modules/strict-uri-encode": { @@ -18681,6 +19939,7 @@ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=4" } @@ -18690,6 +19949,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -18721,17 +19981,56 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "peer": true + }, "node_modules/string-width/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -18754,11 +20053,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "license": "MIT", + "peer": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", @@ -18780,6 +20091,7 @@ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", @@ -18798,6 +20110,7 @@ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -18822,6 +20135,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -18844,6 +20171,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, "license": "MIT", "dependencies": { "min-indent": "^1.0.0" @@ -18864,20 +20192,73 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-dictionary": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/style-dictionary/-/style-dictionary-4.4.0.tgz", + "integrity": "sha512-+xU0IA1StzqAqFs/QtXkK+XJa7wpS4X5H+JQccRKsRCElgeLGocFU1U/UMvMUylKFw6vwGV+Y/a2wb2pm5rFFQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@bundled-es-modules/deepmerge": "^4.3.1", + "@bundled-es-modules/glob": "^10.4.2", + "@bundled-es-modules/memfs": "^4.9.4", + "@zip.js/zip.js": "^2.7.44", + "chalk": "^5.3.0", + "change-case": "^5.3.0", + "commander": "^12.1.0", + "is-plain-obj": "^4.1.0", + "json5": "^2.2.2", + "patch-package": "^8.0.0", + "path-unified": "^0.2.0", + "prettier": "^3.3.3", + "tinycolor2": "^1.6.0" + }, + "bin": { + "style-dictionary": "bin/style-dictionary.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/style-dictionary/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/style-dictionary/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + } + }, "node_modules/style-loader": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", - "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", "license": "MIT", + "peer": true, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^5.0.0" + "webpack": "^5.27.0" } }, "node_modules/stylehacks": { @@ -18885,6 +20266,7 @@ "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", "license": "MIT", + "peer": true, "dependencies": { "browserslist": "^4.23.0", "postcss-selector-parser": "^6.0.16" @@ -18897,30 +20279,42 @@ } }, "node_modules/superagent": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.0.tgz", - "integrity": "sha512-IKeoGox6oG9zyDeizaezkJ2/aK0wc5la9st7WsAKyrAkfJ56W3whVbVtF68k6wuc87/y9T85NyON5FLz7Mrzzw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", "license": "MIT", + "peer": true, "dependencies": { - "component-emitter": "^1.3.0", + "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", - "debug": "^4.3.4", + "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.0", - "formidable": "^3.5.2", + "form-data": "^4.0.5", + "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.0" + "qs": "^6.14.1" }, "engines": { "node": ">=14.18.0" } }, + "node_modules/superagent/node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/superagent/node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "license": "MIT", + "peer": true, "bin": { "mime": "cli.js" }, @@ -18952,25 +20346,20 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", - "license": "MIT" - }, "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", + "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", "license": "MIT", + "peer": true, "dependencies": { - "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0" + "picocolors": "^1.0.0", + "sax": "^1.5.0" }, "bin": { "svgo": "bin/svgo" @@ -18988,15 +20377,17 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", + "peer": true, "engines": { "node": ">= 10" } }, "node_modules/svgo/node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -19013,6 +20404,7 @@ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "license": "MIT", + "peer": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -19027,6 +20419,7 @@ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "domelementtype": "^2.3.0" }, @@ -19042,6 +20435,7 @@ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -19057,54 +20451,59 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "license": "MIT" }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "license": "MIT", + "peer": true, + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.2.0.tgz", + "integrity": "sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/tabbable": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" - } - }, - "node_modules/tar-fs": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz", - "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, - "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terser": { - "version": "5.39.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", - "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -19116,15 +20515,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.3.17", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.17.tgz", + "integrity": "sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==", "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "engines": { @@ -19154,6 +20553,7 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -19168,6 +20568,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -19182,13 +20583,15 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/terser/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -19198,6 +20601,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "license": "MIT", + "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -19217,13 +20621,32 @@ "node": ">=8" } }, - "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", - "license": "Apache-2.0", + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", "dependencies": { - "b4a": "^1.6.4" + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, "node_modules/text-table": { @@ -19232,38 +20655,53 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "license": "MIT" }, + "node_modules/thingies": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", + "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "license": "MIT" - }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" + "license": "MIT", + "peer": true }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "license": "MIT" + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT", + "peer": true }, "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "license": "MIT", + "peer": true, "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -19272,42 +20710,25 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "node_modules/tinygradient": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/tinygradient/-/tinygradient-1.1.5.tgz", + "integrity": "sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==", "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peer": true, + "dependencies": { + "@types/tinycolor2": "^1.4.0", + "tinycolor2": "^1.0.0" } }, "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, + "peer": true, "engines": { - "node": ">=0.6.0" + "node": ">=14.14" } }, "node_modules/tmpl": { @@ -19333,6 +20754,7 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.6" } @@ -19342,10 +20764,21 @@ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -19376,138 +20809,209 @@ "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", "license": "MIT", "dependencies": { - "punycode": "^2.1.1" + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/try": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/try/-/try-1.0.3.tgz", + "integrity": "sha512-AHA8khVCII6zKyRkyPo6pRwoR9v5jb7QFw6e5avtaVSkxVfaEucYIo06xnwB+pJaEarfYNbs7W3Vq+LZLZiWyA==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/arthurfiorette/try?sponsor=1" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-loader": { + "version": "9.5.7", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.7.tgz", + "integrity": "sha512-/ZNrKgA3K3PtpMYOC71EeMWIloGw3IYEa5/t1cyz2r5/PyUwTXGzYJvcD3kfUvmhlfpz1rhV8B2O6IVTQ0avsg==", + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tsc-alias": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.16.tgz", + "integrity": "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "get-tsconfig": "^4.10.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" }, "engines": { - "node": ">=12" + "node": ">=16.20.2" } }, - "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "node_modules/tsc-alias/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=16" - }, - "peerDependencies": { - "typescript": ">=4.2.0" + "node": ">=8" } }, - "node_modules/ts-jest": { - "version": "29.3.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.2.tgz", - "integrity": "sha512-bJJkrWc6PjFVz5g2DGCNUo8z7oFEYaz1xP1NpeDU7KNLMWPpEyV8Chbpkn8xjzgRDpQhnGMyvyldoL7h8JXyug==", + "node_modules/tsc-alias/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "license": "MIT", "dependencies": { - "bs-logger": "^0.2.6", - "ejs": "^3.1.10", - "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.1", - "type-fest": "^4.39.1", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + "node": ">= 8.10.0" }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" + "funding": { + "url": "https://paulmillr.com/funding/" }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "node_modules/tsc-alias/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "is-glob": "^4.0.1" }, "engines": { - "node": ">=10" + "node": ">= 6" } }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz", - "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==", - "license": "(MIT OR CC0-1.0)", + "node_modules/tsc-alias/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, "engines": { - "node": ">=16" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "node_modules/tsc-alias/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tsconfig-paths-webpack-plugin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", - "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", + "node_modules/tsc-alias/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.7.0", - "tapable": "^2.2.1", - "tsconfig-paths": "^4.1.2" + "picomatch": "^2.2.1" }, "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">=8.10.0" } }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/tsconfig-paths": { + "node_modules/tsconfig-paths": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "license": "MIT", + "peer": true, "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", @@ -19517,16 +21021,20 @@ "node": ">=6" } }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", + "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", "license": "MIT", + "peer": true, "dependencies": { - "minimist": "^1.2.0" + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tapable": "^2.2.1", + "tsconfig-paths": "^4.1.2" }, - "bin": { - "json5": "lib/cli.js" + "engines": { + "node": ">=10.13.0" } }, "node_modules/tsconfig-paths/node_modules/strip-bom": { @@ -19534,6 +21042,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "license": "MIT", + "peer": true, "engines": { "node": ">=4" } @@ -19542,39 +21051,45 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", "license": "MIT", + "peer": true, "dependencies": { - "tslib": "^1.8.1" + "tslib": "^1.9.3" }, "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + "node": ">= 6.0.0" } }, - "node_modules/tsutils/node_modules/tslib": { + "node_modules/tsyringe/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" + "node_modules/turbo": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.9.3.tgz", + "integrity": "sha512-J/VUvsGRykPb9R8Kh8dHVBOqioDexLk9BhLCU/ZybRR+HN9UR3cURdazFvNgMDt9zPP8TF6K73Z+tplfmi0PqQ==", + "dev": true, + "license": "MIT", + "bin": { + "turbo": "bin/turbo" }, - "engines": { - "node": "*" + "optionalDependencies": { + "@turbo/darwin-64": "2.9.3", + "@turbo/darwin-arm64": "2.9.3", + "@turbo/linux-64": "2.9.3", + "@turbo/linux-arm64": "2.9.3", + "@turbo/windows-64": "2.9.3", + "@turbo/windows-arm64": "2.9.3" } }, "node_modules/type-check": { @@ -19582,6 +21097,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "license": "MIT", + "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -19615,6 +21131,7 @@ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "license": "MIT", + "peer": true, "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -19628,6 +21145,7 @@ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -19642,6 +21160,7 @@ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", @@ -19661,6 +21180,7 @@ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "license": "MIT", + "peer": true, "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", @@ -19682,6 +21202,7 @@ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -19698,16 +21219,41 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", + "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.56.1", + "@typescript-eslint/parser": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/unbox-primitive": { @@ -19715,6 +21261,7 @@ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", @@ -19733,6 +21280,7 @@ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.6.3", "@types/react": ">=16.9.11", @@ -19743,6 +21291,13 @@ "react": ">=15.0.0" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -19754,6 +21309,7 @@ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "license": "MIT", + "peer": true, "engines": { "node": ">=4" } @@ -19763,6 +21319,7 @@ "resolved": "https://registry.npmjs.org/unicode-emoji-utils/-/unicode-emoji-utils-1.3.1.tgz", "integrity": "sha512-6PiQxmnlsOsqzZCZz0sykSyMy/r1HiJiOWWXV98+BDva583DU4CtBeyDNsi4wMYUIbjUtMs4RgAuyft0EKLoVw==", "license": "MIT", + "peer": true, "dependencies": { "emoji-regex-xs": "^2.0.0" } @@ -19772,6 +21329,7 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "license": "MIT", + "peer": true, "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -19781,53 +21339,47 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "license": "MIT", + "peer": true, "engines": { "node": ">=4" } }, "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=4" } }, - "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/universal-cookie": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", - "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-8.0.1.tgz", + "integrity": "sha512-B6ks9FLLnP1UbPPcveOidfvB9pHjP+wekP2uRYB9YDfKVpvcjKgy1W5Zj+cEXJ9KTPnqOKGfVDQBmn8/YCQfRg==", "license": "MIT", + "peer": true, "dependencies": { - "@types/cookie": "^0.3.3", - "cookie": "^0.4.0" + "cookie": "^1.0.2" } }, "node_modules/universal-cookie/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "license": "MIT", + "peer": true, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/universalify": { @@ -19844,46 +21396,15 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } }, - "node_modules/unrs-resolver": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.0.tgz", - "integrity": "sha512-b76tVoT9KPniDY1GoYghDUQX20gjzXm/TONfHfgayLaiuo+oGyT9CsQkGCEJs+1/uryVBEOGOt3yYWDXbJhL7g==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "napi-postinstall": "^0.1.6" - }, - "funding": { - "url": "https://github.com/sponsors/JounQin" - }, - "optionalDependencies": { - "@unrs/resolver-binding-darwin-arm64": "1.7.0", - "@unrs/resolver-binding-darwin-x64": "1.7.0", - "@unrs/resolver-binding-freebsd-x64": "1.7.0", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.0", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.0", - "@unrs/resolver-binding-linux-arm64-gnu": "1.7.0", - "@unrs/resolver-binding-linux-arm64-musl": "1.7.0", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.0", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.0", - "@unrs/resolver-binding-linux-riscv64-musl": "1.7.0", - "@unrs/resolver-binding-linux-s390x-gnu": "1.7.0", - "@unrs/resolver-binding-linux-x64-gnu": "1.7.0", - "@unrs/resolver-binding-linux-x64-musl": "1.7.0", - "@unrs/resolver-binding-wasm32-wasi": "1.7.0", - "@unrs/resolver-binding-win32-arm64-msvc": "1.7.0", - "@unrs/resolver-binding-win32-ia32-msvc": "1.7.0", - "@unrs/resolver-binding-win32-x64-msvc": "1.7.0" - } - }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "funding": [ { "type": "opencollective", @@ -19919,11 +21440,26 @@ "punycode": "^2.1.0" } }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "license": "MIT", + "peer": true, + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/url-loader": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", "license": "MIT", + "peer": true, "dependencies": { "loader-utils": "^2.0.0", "mime-types": "^2.1.27", @@ -19951,6 +21487,7 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -19974,11 +21511,19 @@ "requires-port": "^1.0.0" } }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT", + "peer": true + }, "node_modules/use-callback-ref": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.0.0" }, @@ -20000,6 +21545,7 @@ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", "license": "MIT", + "peer": true, "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" @@ -20017,20 +21563,12 @@ } } }, - "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", @@ -20043,34 +21581,38 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/utila": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4.0" } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", + "peer": true, "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8-to-istanbul": { @@ -20087,17 +21629,19 @@ "node": ">=10.12.0" } }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", - "license": "MIT" + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "license": "MIT", + "peer": true }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -20128,15 +21672,17 @@ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.0.0" } }, "node_modules/watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "license": "MIT", + "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -20150,6 +21696,7 @@ "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", "license": "MIT", + "peer": true, "dependencies": { "minimalistic-assert": "^1.0.0" } @@ -20159,6 +21706,7 @@ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "license": "MIT", + "peer": true, "dependencies": { "defaults": "^1.0.3" } @@ -20173,34 +21721,37 @@ } }, "node_modules/webpack": { - "version": "5.99.6", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.6.tgz", - "integrity": "sha512-TJOLrJ6oeccsGWPl7ujCYuc0pIq2cNsuD6GZDma8i5o5Npvcco/z+NKvZSFsP0/x6SShVb0+X2JK/JHUjKY9dQ==", + "version": "5.105.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", + "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" @@ -20223,6 +21774,7 @@ "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", @@ -20249,6 +21801,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", + "peer": true, "engines": { "node": ">= 10" } @@ -20258,6 +21811,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=8.3.0" }, @@ -20279,6 +21833,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -20301,105 +21856,456 @@ "node": ">=14.15.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", + "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", + "license": "MIT", + "peer": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/base64": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", + "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/codegen": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", + "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/fs-core": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.2.tgz", + "integrity": "sha512-SVjwklkpIV5wrynpYtuYnfYH1QF4/nDuLBX7VXdb+3miglcAgBVZb/5y0cOsehRV/9Vb+3UqhkMq3/NR3ztdkQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/fs-fsa": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.2.tgz", + "integrity": "sha512-fhO8+iR2I+OCw668ISDJdn1aArc9zx033sWejIyzQ8RBeXa9bDSaUeA3ix0poYOfrj1KdOzytmYNv2/uLDfV6g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/fs-node": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.2.tgz", + "integrity": "sha512-nX2AdL6cOFwLdju9G4/nbRnYevmCJbh7N7hvR3gGm97Cs60uEjyd0rpR+YBS7cTg175zzl22pGKXR5USaQMvKg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-print": "4.57.2", + "@jsonjoy.com/fs-snapshot": "4.57.2", + "glob-to-regex.js": "^1.0.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/fs-node-builtins": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.2.tgz", + "integrity": "sha512-xhiegylRmhw43Ki2HO1ZBL7DQ5ja/qpRsL29VtQ2xuUHiuDGbgf2uD4p9Qd8hJI5P6RCtGYD50IXHXVq/Ocjcg==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/fs-node-to-fsa": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.2.tgz", + "integrity": "sha512-18LmWTSONhoAPW+IWRuf8w/+zRolPFGPeGwMxlAhhfY11EKzX+5XHDBPAw67dBF5dxDErHJbl40U+3IXSDRXSQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/fs-fsa": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/fs-node-utils": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.2.tgz", + "integrity": "sha512-rsPSJgekz43IlNbLyAM/Ab+ouYLWGp5DDBfYBNNEqDaSpsbXfthBn29Q4muFA9L0F+Z3mKo+CWlgSCXrf+mOyQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/fs-node-builtins": "4.57.2" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/fs-print": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.2.tgz", + "integrity": "sha512-wK9NSow48i4DbDl9F1CQE5TqnyZOJ04elU3WFG5aJ76p+YxO/ulyBBQvKsessPxdo381Bc2pcEoyPujMOhcRqQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/fs-node-utils": "4.57.2", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/fs-snapshot": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.2.tgz", + "integrity": "sha512-GdduDZuoP5V/QCgJkx9+BZ6SC0EZ/smXAdTS7PfMqgMTGXLlt/bH/FqMYaqB9JmLf05sJPtO0XRbAwwkEEPbVw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/buffers": "^17.65.0", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/json-pack": "^17.65.0", + "@jsonjoy.com/util": "^17.65.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", + "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/base64": "17.67.0", + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0", + "@jsonjoy.com/json-pointer": "17.67.0", + "@jsonjoy.com/util": "17.67.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0", + "tree-dump": "^1.1.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/json-pointer": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", + "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/util": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/@jsonjoy.com/json-pointer/node_modules/@jsonjoy.com/util": { + "version": "17.67.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", + "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/buffers": "17.67.0", + "@jsonjoy.com/codegen": "17.67.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/memfs": { + "version": "4.57.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.2.tgz", + "integrity": "sha512-2nWzSsJzrukurSDna4Z0WywuScK4Id3tSKejgu74u8KCdW4uNrseKRSIDg75C6Yw5ZRqBe0F0EtMNlTbUq8bAQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jsonjoy.com/fs-core": "4.57.2", + "@jsonjoy.com/fs-fsa": "4.57.2", + "@jsonjoy.com/fs-node": "4.57.2", + "@jsonjoy.com/fs-node-builtins": "4.57.2", + "@jsonjoy.com/fs-node-to-fsa": "4.57.2", + "@jsonjoy.com/fs-node-utils": "4.57.2", + "@jsonjoy.com/fs-print": "4.57.2", + "@jsonjoy.com/fs-snapshot": "4.57.2", + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "license": "MIT", - "engines": { - "node": ">=14" + "tslib": "2" } }, - "node_modules/webpack-dev-middleware": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", + "peer": true, "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">=18" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" + "url": "https://opencollective.com/express" } }, "node_modules/webpack-dev-server": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", - "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", - "license": "MIT", - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz", + "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.25", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", "colorette": "^2.0.10", - "compression": "^1.7.4", + "compression": "^1.8.1", "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", + "express": "^4.22.1", "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^5.5.0", "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.4", - "ws": "^8.13.0" + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" + "webpack": "^5.0.0" }, "peerDependenciesMeta": { "webpack": { @@ -20410,29 +22316,110 @@ } } }, + "node_modules/webpack-dev-server/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "peer": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/webpack-dev-server/node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 10" } }, - "node_modules/webpack-dev-server/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "peer": true, "dependencies": { - "glob": "^7.1.3" + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/webpack-dev-server/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "peer": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, "node_modules/webpack-merge": { @@ -20440,6 +22427,7 @@ "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "license": "MIT", + "peer": true, "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", @@ -20450,12 +22438,13 @@ } }, "node_modules/webpack-remove-empty-scripts": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/webpack-remove-empty-scripts/-/webpack-remove-empty-scripts-1.0.4.tgz", - "integrity": "sha512-W/Vd94oNXMsQam+W9G+aAzGgFlX1aItcJpkG3byuHGDaxyK3H17oD/b5RcqS/ZHzStIKepksdLDznejDhDUs+Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webpack-remove-empty-scripts/-/webpack-remove-empty-scripts-1.1.1.tgz", + "integrity": "sha512-FqmIy7joxXd0/7jz8UjzMXOKc6B7LR+ynfgaaaH72xUT917h3A94ERxOLvGM8a8XdGIvUsdTLIUt8aBQM2Pdqg==", "license": "ISC", + "peer": true, "dependencies": { - "ansis": "1.5.2" + "ansis": "4.0.0-node10" }, "engines": { "node": ">=12.14" @@ -20473,6 +22462,7 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", "license": "MIT", + "peer": true, "dependencies": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" @@ -20483,15 +22473,41 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/webpack/node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.13.0" } @@ -20501,6 +22517,7 @@ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", @@ -20515,6 +22532,7 @@ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=0.8.0" } @@ -20523,6 +22541,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", "license": "MIT", "dependencies": { "iconv-lite": "0.6.3" @@ -20585,6 +22604,7 @@ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "license": "MIT", + "peer": true, "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", @@ -20604,6 +22624,7 @@ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", @@ -20631,6 +22652,7 @@ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "license": "MIT", + "peer": true, "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", @@ -20645,10 +22667,11 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "license": "MIT", + "peer": true, "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", @@ -20669,13 +22692,15 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -20685,6 +22710,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -20694,6 +22720,25 @@ "node": ">=8" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -20714,9 +22759,9 @@ } }, "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -20734,6 +22779,38 @@ } } }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -20749,6 +22826,16 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "license": "MIT" }, + "node_modules/xregexp": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-5.1.2.tgz", + "integrity": "sha512-6hGgEMCGhqCTFEJbqmWrNIPqfpdirdGWkqshu7fFZddmTSfgv5Sn9D2SaKloR79s5VUiUlpwzg3CM3G6D3VIlw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.9" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -20765,39 +22852,48 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "license": "ISC", + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "license": "MIT", + "peer": true, "dependencies": { - "cliui": "^8.0.1", + "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^4.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^20.2.2" }, "engines": { - "node": ">=12" + "node": ">=10" } }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "license": "ISC", + "peer": true, "engines": { - "node": ">=12" + "node": ">=10" } }, "node_modules/yocto-queue": { diff --git a/package.json b/package.json old mode 100755 new mode 100644 index 1fe16fee1..623eccf9a --- a/package.json +++ b/package.json @@ -1,86 +1,98 @@ { - "name": "@edx/frontend-app-learner-dashboard", - "version": "0.0.1", + "name": "@openedx/frontend-app-learner-dashboard", + "version": "0.0.0-dev", "description": "", "repository": { "type": "git", - "url": "git+https://github.com/edx/frontend-app-learner-dashboard.git" + "url": "git+https://github.com/openedx/frontend-app-learner-dashboard.git" }, + "exports": { + ".": "./dist/index.js" + }, + "files": [ + "/dist" + ], + "workspaces": [ + "packages/*" + ], "browserslist": [ "extends @edx/browserslist-config" ], + "sideEffects": [ + "*.css", + "*.scss" + ], + "atlasTranslations": { + "path": "translations/frontend-app-learner-dashboard/src/i18n/messages", + "dependencies": [ + "@openedx/frontend-base" + ] + }, "scripts": { - "build": "fedx-scripts webpack", - "i18n_extract": "fedx-scripts formatjs extract", - "lint": "fedx-scripts eslint --ext .jsx,.js src/", - "lint-fix": "fedx-scripts eslint --fix --ext .jsx,.js src/", - "semantic-release": "semantic-release", - "start": "fedx-scripts webpack-dev-server --progress", - "dev": "PUBLIC_PATH=/learner-dashboard/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io", - "test": "TZ=GMT fedx-scripts jest --coverage --passWithNoTests", - "quality": "npm run lint-fix && npm run test", - "watch-tests": "jest --watch", - "snapshot": "fedx-scripts jest --updateSnapshot" + "build": "make build", + "build:ci": "make build-ci", + "build:packages": "make build-packages", + "clean": "make clean", + "clean:packages": "make clean-packages", + "dev": "PORT=1996 PUBLIC_PATH=/learner-dashboard openedx dev", + "dev:site": "make dev-site", + "dev:packages": "make dev-packages", + "i18n_extract": "openedx formatjs extract", + "lint": "openedx lint .", + "lint:fix": "openedx lint --fix .", + "prepack": "npm run clean && npm run build", + "test": "openedx test --coverage --passWithNoTests", + "translations:pull": "openedx translations:pull", + "watch:build": "nodemon --exec 'npm run build'" }, - "author": "edX", + "author": "Open edX", "license": "AGPL-3.0", - "homepage": "", + "homepage": "https://github.com/openedx/frontend-app-learner-dashboard#readme", "publishConfig": { "access": "public" }, + "bugs": { + "url": "https://github.com/openedx/frontend-app-learner-dashboard/issues" + }, "dependencies": { - "@edx/brand": "npm:@openedx/brand-openedx@^1.2.2", - "@edx/frontend-component-footer": "^14.6.0", - "@edx/frontend-component-header": "^6.2.0", - "@edx/frontend-enterprise-hotjar": "7.2.0", - "@edx/frontend-platform": "^8.3.1", "@edx/openedx-atlas": "^0.7.0", - "@edx/react-unit-test-utils": "^4.0.0", "@fortawesome/fontawesome-svg-core": "^1.2.36", "@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.2.0", - "@openedx/frontend-plugin-framework": "^1.7.0", - "@openedx/paragon": "^22.16.0", - "@redux-devtools/extension": "3.3.0", - "@reduxjs/toolkit": "^2.0.0", "classnames": "^2.3.1", - "core-js": "3.42.0", - "filesize": "^10.0.0", "font-awesome": "4.7.0", - "history": "5.3.0", "lodash": "^4.17.21", "moment": "^2.29.4", "prop-types": "15.8.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", "react-helmet": "^6.1.0", - "react-intl": "6.8.9", - "react-redux": "^7.2.4", - "react-router-dom": "6.29.0", - "react-share": "^4.4.0", - "redux": "4.2.1", - "redux-logger": "3.0.6", - "redux-thunk": "2.4.2", - "regenerator-runtime": "^0.14.0", - "reselect": "^4.0.0", - "universal-cookie": "^4.0.4", - "util": "^0.12.4" + "react-share": "^5.2.2" }, "devDependencies": { - "@edx/browserslist-config": "^1.3.0", - "@edx/reactifex": "^2.1.1", - "@openedx/frontend-build": "^14.3.3", + "@edx/browserslist-config": "^1.5.0", "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^16.2.0", - "copy-webpack-plugin": "^12.0.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/jest": "^29.5.14", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-expect-message": "^1.1.3", "jest-when": "^3.6.0", + "nodemon": "^3.1.14", "react-dev-utils": "^12.0.0", - "react-test-renderer": "^18.3.1", - "redux-mock-store": "^1.5.4" + "tsc-alias": "^1.8.16", + "turbo": "^2.8.20" + }, + "peerDependencies": { + "@openedx/frontend-base": "^1.0.0-alpha || 0.0.0-dev", + "@openedx/paragon": "^23", + "@tanstack/react-query": "^5", + "@types/react": "^18", + "@types/react-dom": "^18", + "react": "^18", + "react-dom": "^18", + "react-router": "^6", + "react-router-dom": "^6" } } diff --git a/public/index.html b/public/index.html index e1a12ed66..835275552 100755 --- a/public/index.html +++ b/public/index.html @@ -1,6 +1,7 @@ + Learner Dashboard Development Site> diff --git a/public/robots.txt b/public/robots.txt deleted file mode 100644 index 1f53798bb..000000000 --- a/public/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Disallow: / diff --git a/site.config.ci.tsx b/site.config.ci.tsx new file mode 100644 index 000000000..344502a1c --- /dev/null +++ b/site.config.ci.tsx @@ -0,0 +1,24 @@ +import { EnvironmentTypes, SiteConfig, footerApp, headerApp, shellApp } from '@openedx/frontend-base'; + +import { learnerDashboardApp } from './src'; + +import '@openedx/frontend-base/shell/style'; + +const siteConfig: SiteConfig = { + siteId: 'learner-dashboard-ci', + siteName: 'Learner Dashboard CI', + baseUrl: 'http://apps.local.openedx.io', + lmsBaseUrl: 'http://local.openedx.io', + loginUrl: 'http://local.openedx.io/login', + logoutUrl: 'http://local.openedx.io/logout', + + environment: EnvironmentTypes.PRODUCTION, + apps: [ + shellApp, + headerApp, + footerApp, + learnerDashboardApp + ], +}; + +export default siteConfig; diff --git a/site.config.dev.tsx b/site.config.dev.tsx new file mode 100644 index 000000000..76e6f210d --- /dev/null +++ b/site.config.dev.tsx @@ -0,0 +1,40 @@ +import { EnvironmentTypes, SiteConfig, footerApp, headerApp, shellApp } from '@openedx/frontend-base'; + +import { learnerDashboardApp } from './src'; + +import '@openedx/frontend-base/shell/style'; + +const siteConfig: SiteConfig = { + siteId: 'learner-dashboard-dev', + siteName: 'Learner Dashboard Dev', + baseUrl: 'http://apps.local.openedx.io:1996', + lmsBaseUrl: 'http://local.openedx.io:8000', + loginUrl: 'http://local.openedx.io:8000/login', + logoutUrl: 'http://local.openedx.io:8000/logout', + + environment: EnvironmentTypes.DEVELOPMENT, + apps: [ + shellApp, + headerApp, + footerApp, + learnerDashboardApp + ], + externalRoutes: [ + { + role: 'org.openedx.frontend.role.profile', + url: 'http://apps.local.openedx.io:1995/profile/' + }, + { + role: 'org.openedx.frontend.role.account', + url: 'http://apps.local.openedx.io:1997/account/' + }, + { + role: 'org.openedx.frontend.role.logout', + url: 'http://local.openedx.io:8000/logout' + }, + ], + + accessTokenCookieName: 'edx-jwt-cookie-header-payload', +}; + +export default siteConfig; diff --git a/site.config.test.tsx b/site.config.test.tsx new file mode 100644 index 000000000..99991595a --- /dev/null +++ b/site.config.test.tsx @@ -0,0 +1,29 @@ +import type { SiteConfig } from '@openedx/frontend-base'; + +import { appId } from './src/constants'; + +const siteConfig: SiteConfig = { + siteId: 'learner-dashboard-test-site', + siteName: 'Learner Dashboard Test Site', + baseUrl: 'http://localhost:1996', + lmsBaseUrl: 'http://localhost:8000', + loginUrl: 'http://localhost:8000/login', + logoutUrl: 'http://localhost:8000/logout', + + // Use 'test' instead of EnvironmentTypes.TEST to break a circular dependency + // when mocking `@openedx/frontend-base` itself. + environment: 'test' as SiteConfig['environment'], + apps: [{ + appId, + config: { + ECOMMERCE_BASE_URL: 'http://localhost:18130', + FAVICON_URL: 'https://edx-cdn.org/v3/default/favicon.ico', + LEARNING_BASE_URL: 'http://localhost:2000', + }, + }], + + accessTokenCookieName: 'edx-jwt-cookie-header-payload', + segmentKey: '', +}; + +export default siteConfig; diff --git a/src/App.jsx b/src/App.jsx deleted file mode 100755 index e9f3e294a..000000000 --- a/src/App.jsx +++ /dev/null @@ -1,100 +0,0 @@ -import React from 'react'; -import { Helmet } from 'react-helmet'; - -import { useIntl } from '@edx/frontend-platform/i18n'; -import { logError } from '@edx/frontend-platform/logging'; -import { initializeHotjar } from '@edx/frontend-enterprise-hotjar'; - -import { ErrorPage, AppContext } from '@edx/frontend-platform/react'; -import { FooterSlot } from '@edx/frontend-component-footer'; -import { Alert } from '@openedx/paragon'; - -import { RequestKeys } from 'data/constants/requests'; -import store from 'data/store'; -import { - selectors, - actions, -} from 'data/redux'; -import { reduxHooks } from 'hooks'; -import Dashboard from 'containers/Dashboard'; - -import track from 'tracking'; - -import fakeData from 'data/services/lms/fakeData/courses'; - -import AppWrapper from 'containers/WidgetContainers/AppWrapper'; -import LearnerDashboardHeader from 'containers/LearnerDashboardHeader'; - -import { getConfig } from '@edx/frontend-platform'; -import messages from './messages'; -import './App.scss'; - -export const App = () => { - const { authenticatedUser } = React.useContext(AppContext); - const { formatMessage } = useIntl(); - const isFailed = { - initialize: reduxHooks.useRequestIsFailed(RequestKeys.initialize), - refreshList: reduxHooks.useRequestIsFailed(RequestKeys.refreshList), - }; - const hasNetworkFailure = isFailed.initialize || isFailed.refreshList; - const { supportEmail } = reduxHooks.usePlatformSettingsData(); - const loadData = reduxHooks.useLoadData(); - - React.useEffect(() => { - if (authenticatedUser?.administrator || getConfig().NODE_ENV === 'development') { - window.loadEmptyData = () => { - loadData({ ...fakeData.globalData, courses: [] }); - }; - window.loadMockData = () => { - loadData({ - ...fakeData.globalData, - courses: [ - ...fakeData.courseRunData, - ...fakeData.entitlementData, - ], - }); - }; - window.store = store; - window.selectors = selectors; - window.actions = actions; - window.track = track; - } - if (getConfig().HOTJAR_APP_ID) { - try { - initializeHotjar({ - hotjarId: getConfig().HOTJAR_APP_ID, - hotjarVersion: getConfig().HOTJAR_VERSION, - hotjarDebug: !!getConfig().HOTJAR_DEBUG, - }); - } catch (error) { - logError(error); - } - } - }, [authenticatedUser, loadData]); - return ( - <> - - {formatMessage(messages.pageTitle)} - - -
- - -
- {hasNetworkFailure - ? ( - - - - ) : ( - - )} -
-
- -
- - ); -}; - -export default App; diff --git a/src/App.scss b/src/App.scss deleted file mode 100755 index 2292f72e7..000000000 --- a/src/App.scss +++ /dev/null @@ -1,69 +0,0 @@ -// frontend-app-*/src/index.scss -@import "~@edx/brand/paragon/fonts"; -@import "~@edx/brand/paragon/variables"; -@import "~@openedx/paragon/scss/core/core"; -@import "~@edx/brand/paragon/overrides"; - -$fa-font-path: "~font-awesome/fonts"; -@import "~font-awesome/scss/font-awesome"; - -$input-focus-box-shadow: $input-box-shadow; // hack to get upgrade to paragon 4.0.0 to work - -@import "~@edx/frontend-component-header/dist/index"; -@import "~@edx/frontend-component-footer/dist/_footer"; - -.text-ellipsis { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.alert.alert-info .alert-icon { - color: black; -} - -#root { - // Removing a odd 1.5 scaling on checkboxes.: - input[type=checkbox] { - transform: none; - } - display: flex; - flex-direction: column; - min-height: 100vh; - - main { - flex-grow: 1; - } - - header { - flex: 0 0 auto; - - .logo { - display: block; - box-sizing: content-box; - position: relative; - top: 0.1em; - height: 1.75rem; - margin-right: 1rem; - img { - display: block; - height: 100%; - } - } - } - - footer { - flex: 0; - } -} - -#paragon-portal-root { - .pgn__modal-layer { - .pgn__modal-close-container { - right: 1rem !important; - } - } - .confirm-modal .pgn__modal-body { - overflow: hidden; - } -} diff --git a/src/App.test.jsx b/src/App.test.jsx deleted file mode 100644 index 33969e308..000000000 --- a/src/App.test.jsx +++ /dev/null @@ -1,149 +0,0 @@ -import React from 'react'; -import { Helmet } from 'react-helmet'; -import { shallow } from '@edx/react-unit-test-utils'; - -import { useIntl } from '@edx/frontend-platform/i18n'; -import { getConfig } from '@edx/frontend-platform'; - -import { RequestKeys } from 'data/constants/requests'; -import { reduxHooks } from 'hooks'; -import Dashboard from 'containers/Dashboard'; -import LearnerDashboardHeader from 'containers/LearnerDashboardHeader'; -import AppWrapper from 'containers/WidgetContainers/AppWrapper'; -import { App } from './App'; -import messages from './messages'; - -jest.mock('@edx/frontend-component-footer', () => ({ FooterSlot: 'FooterSlot' })); - -jest.mock('containers/Dashboard', () => 'Dashboard'); -jest.mock('containers/LearnerDashboardHeader', () => 'LearnerDashboardHeader'); -jest.mock('containers/WidgetContainers/AppWrapper', () => 'AppWrapper'); -jest.mock('data/redux', () => ({ - selectors: 'redux.selectors', - actions: 'redux.actions', - thunkActions: 'redux.thunkActions', -})); -jest.mock('hooks', () => ({ - reduxHooks: { - useRequestIsFailed: jest.fn(), - usePlatformSettingsData: jest.fn(), - useLoadData: jest.fn(), - }, -})); -jest.mock('data/store', () => 'data/store'); - -jest.mock('@edx/frontend-platform', () => ({ - getConfig: jest.fn(() => ({})), -})); - -const loadData = jest.fn(); -reduxHooks.useLoadData.mockReturnValue(loadData); - -let el; - -const supportEmail = 'test-support-url'; -reduxHooks.usePlatformSettingsData.mockReturnValue({ supportEmail }); - -describe('App router component', () => { - const { formatMessage } = useIntl(); - describe('component', () => { - const runBasicTests = () => { - test('snapshot', () => { expect(el.snapshot).toMatchSnapshot(); }); - it('displays title in helmet component', () => { - const control = el.instance - .findByType(Helmet)[0] - .findByType('title')[0]; - expect(control.children[0].el).toEqual(formatMessage(messages.pageTitle)); - }); - it('displays learner dashboard header', () => { - expect(el.instance.findByType(LearnerDashboardHeader).length).toEqual(1); - }); - it('wraps the header and main components in an AppWrapper widget container', () => { - const container = el.instance.findByType(AppWrapper)[0]; - expect(container.children[0].type).toEqual('LearnerDashboardHeader'); - expect(container.children[1].type).toEqual('main'); - }); - }; - describe('no network failure', () => { - beforeAll(() => { - reduxHooks.useRequestIsFailed.mockReturnValue(false); - getConfig.mockReturnValue({}); - el = shallow(); - }); - runBasicTests(); - it('loads dashboard', () => { - const main = el.instance.findByType('main')[0]; - expect(main.children.length).toEqual(1); - const dashboard = main.children[0].el; - expect(dashboard.type).toEqual('Dashboard'); - expect(dashboard).toEqual(shallow()); - }); - }); - describe('no network failure with optimizely url', () => { - beforeAll(() => { - reduxHooks.useRequestIsFailed.mockReturnValue(false); - getConfig.mockReturnValue({ OPTIMIZELY_URL: 'fake.url' }); - el = shallow(); - }); - runBasicTests(); - it('loads dashboard', () => { - const main = el.instance.findByType('main')[0]; - expect(main.children.length).toEqual(1); - const dashboard = main.children[0].el; - expect(dashboard.type).toEqual('Dashboard'); - expect(dashboard).toEqual(shallow()); - }); - }); - describe('no network failure with optimizely project id', () => { - beforeAll(() => { - reduxHooks.useRequestIsFailed.mockReturnValue(false); - getConfig.mockReturnValue({ OPTIMIZELY_PROJECT_ID: 'fakeId' }); - el = shallow(); - }); - runBasicTests(); - it('loads dashboard', () => { - const main = el.instance.findByType('main')[0]; - expect(main.children.length).toEqual(1); - const dashboard = main.children[0].el; - expect(dashboard.type).toEqual('Dashboard'); - expect(dashboard).toEqual(shallow()); - }); - }); - describe('initialize failure', () => { - beforeAll(() => { - reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.initialize); - getConfig.mockReturnValue({}); - el = shallow(); - }); - runBasicTests(); - it('loads error page', () => { - const main = el.instance.findByType('main')[0]; - expect(main.children.length).toEqual(1); - const alert = main.children[0]; - expect(alert.type).toEqual('Alert'); - expect(alert.children.length).toEqual(1); - const errorPage = alert.children[0]; - expect(errorPage.type).toEqual('ErrorPage'); - expect(errorPage.props.message).toEqual(formatMessage(messages.errorMessage, { supportEmail })); - }); - }); - describe('refresh failure', () => { - beforeAll(() => { - reduxHooks.useRequestIsFailed.mockImplementation((key) => key === RequestKeys.refreshList); - getConfig.mockReturnValue({}); - el = shallow(); - }); - runBasicTests(); - it('loads error page', () => { - const main = el.instance.findByType('main')[0]; - expect(main.children.length).toEqual(1); - const alert = main.children[0]; - expect(alert.type).toEqual('Alert'); - expect(alert.children.length).toEqual(1); - const errorPage = alert.children[0]; - expect(errorPage.type).toEqual('ErrorPage'); - expect(errorPage.props.message).toEqual(formatMessage(messages.errorMessage, { supportEmail })); - }); - }); - }); -}); diff --git a/src/Main.jsx b/src/Main.jsx new file mode 100644 index 000000000..81c73cffb --- /dev/null +++ b/src/Main.jsx @@ -0,0 +1,31 @@ +import { CurrentAppProvider, getSiteConfig, PageWrap, useIntl } from '@openedx/frontend-base'; +import { Helmet } from 'react-helmet'; + +import { appId } from './constants'; +import ContextProviders from './data/context'; +import Dashboard from './containers/Dashboard'; +import messages from './messages'; + +import './style.scss'; + +const Main = () => { + const { formatMessage } = useIntl(); + return ( + + + + {formatMessage(messages['learner-dash.page.title'], { + siteName: getSiteConfig().siteName, + })} + + + + + + + + + ); +}; + +export default Main; diff --git a/src/__mocks__/file.js b/src/__mocks__/file.js new file mode 100644 index 000000000..120a14f13 --- /dev/null +++ b/src/__mocks__/file.js @@ -0,0 +1 @@ +module.exports = 'FileMock'; diff --git a/src/__mocks__/svg.js b/src/__mocks__/svg.js new file mode 100644 index 000000000..0a33a0bb9 --- /dev/null +++ b/src/__mocks__/svg.js @@ -0,0 +1 @@ +module.exports = 'SvgURL'; diff --git a/src/__snapshots__/App.test.jsx.snap b/src/__snapshots__/App.test.jsx.snap deleted file mode 100644 index cf067933e..000000000 --- a/src/__snapshots__/App.test.jsx.snap +++ /dev/null @@ -1,153 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`App router component component initialize failure snapshot 1`] = ` - - - - Learner Home - - - -
- - -
- - - -
-
- -
-
-`; - -exports[`App router component component no network failure snapshot 1`] = ` - - - - Learner Home - - - -
- - -
- -
-
- -
-
-`; - -exports[`App router component component no network failure with optimizely project id snapshot 1`] = ` - - - - Learner Home - - - -
- - -
- -
-
- -
-
-`; - -exports[`App router component component no network failure with optimizely url snapshot 1`] = ` - - - - Learner Home - - - -
- - -
- -
-
- -
-
-`; - -exports[`App router component component refresh failure snapshot 1`] = ` - - - - Learner Home - - - -
- - -
- - - -
-
- -
-
-`; diff --git a/src/__snapshots__/index.test.jsx.snap b/src/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 73e7db1d6..000000000 --- a/src/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,43 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`app registry subscribe: APP_INIT_ERROR. snapshot: displays an ErrorPage to root element 1`] = ` - - - -`; - -exports[`app registry subscribe: APP_READY. links App to root element 1`] = ` - - - - - - - - } - path="/" - /> - - } - path="*" - /> - - - - -`; diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 000000000..eafbadd48 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,21 @@ +import { App } from '@openedx/frontend-base'; +import { appId } from './constants'; +import routes from './routes'; +import providers from './providers'; +import slots from './slots'; + +const app: App = { + appId, + routes, + providers, + slots, + config: { + LEARNING_BASE_URL: 'http://apps.local.openedx.io:2000', + ENABLE_PROGRAMS: false, + ECOMMERCE_BASE_URL: '', + ORDER_HISTORY_URL: '', + SHOW_UNENROLL_SURVEY: false, + } +}; + +export default app; diff --git a/src/assets/top_stripe.svg b/src/assets/top_stripe.svg deleted file mode 100644 index 4c84902f1..000000000 --- a/src/assets/top_stripe.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/components/Banner.test.jsx b/src/components/Banner.test.jsx index 577f4b3df..cf6a5fd20 100644 --- a/src/components/Banner.test.jsx +++ b/src/components/Banner.test.jsx @@ -1,27 +1,27 @@ -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import Banner from './Banner'; -import { Alert } from '@openedx/paragon'; +describe('Banner component', () => { + it('renders children content', () => { + render(Test content); + expect(screen.getByText('Test content')).toBeInTheDocument(); + }); -import Banner from './Banner'; + it('uses default props correctly', () => { + render(Test content); + const banner = screen.getByRole('alert'); + expect(banner).toHaveClass('mb-0'); + }); -describe('Banner', () => { - const props = { - children: 'Hello, world!', - }; - describe('snapshot', () => { - test('renders default banner', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); - }); - test('renders with variants', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); + it('accepts custom variant prop', () => { + render(Test content); + const banner = screen.getByRole('alert'); + expect(banner).toHaveClass('alert-success'); + }); - expect(wrapper.instance.findByType(Alert)[0].props.variant).toEqual('success'); - }); - test('renders with custom class', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); - }); + it('accepts custom className prop', () => { + render(Test content); + const banner = screen.getByRole('alert'); + expect(banner).toHaveClass('custom-class'); }); }); diff --git a/src/components/NoticesWrapper/api.js b/src/components/NoticesWrapper/api.js deleted file mode 100644 index 72284243d..000000000 --- a/src/components/NoticesWrapper/api.js +++ /dev/null @@ -1,25 +0,0 @@ -import { getConfig } from '@edx/frontend-platform'; -import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth'; -import { logError, logInfo } from '@edx/frontend-platform/logging'; - -export const noticesUrl = `${getConfig().LMS_BASE_URL}/notices/api/v1/unacknowledged`; - -export const getNotices = ({ onLoad, notFoundMessage }) => { - const authenticatedUser = getAuthenticatedUser(); - - const handleError = async (e) => { - // Error probably means that notices is not installed, which is fine. - const { customAttributes: { httpErrorStatus } } = e; - if (httpErrorStatus === 404) { - logInfo(`${e}. ${notFoundMessage}`); - } else { - logError(e); - } - }; - if (authenticatedUser) { - return getAuthenticatedHttpClient().get(noticesUrl, {}).then(onLoad).catch(handleError); - } - return null; -}; - -export default { getNotices }; diff --git a/src/components/NoticesWrapper/api.test.js b/src/components/NoticesWrapper/api.test.js deleted file mode 100644 index 4470f6ece..000000000 --- a/src/components/NoticesWrapper/api.test.js +++ /dev/null @@ -1,65 +0,0 @@ -import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth'; -import { logError, logInfo } from '@edx/frontend-platform/logging'; - -import * as api from './api'; - -jest.mock('@edx/frontend-platform', () => ({ - getConfig: jest.fn(() => ({ - LMS_BASE_URL: 'test-lms-url', - })), -})); - -jest.mock('@edx/frontend-platform/auth', () => ({ - getAuthenticatedHttpClient: jest.fn(), - getAuthenticatedUser: jest.fn(), -})); - -jest.mock('@edx/frontend-platform/logging', () => ({ - logError: jest.fn(), - logInfo: jest.fn(), -})); - -const testData = 'test-data'; -const successfulGet = () => Promise.resolve(testData); -const error404 = { customAttributes: { httpErrorStatus: 404 }, test: 'error' }; -const error404Get = () => Promise.reject(error404); -const error500 = { customAttributes: { httpErrorStatus: 500 }, test: 'error' }; -const error500Get = () => Promise.reject(error500); - -const get = jest.fn().mockImplementation(successfulGet); -getAuthenticatedHttpClient.mockReturnValue({ get }); -const authenticatedUser = { fake: 'user' }; -getAuthenticatedUser.mockReturnValue(authenticatedUser); - -const onLoad = jest.fn(); -describe('getNotices api method', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - describe('behavior', () => { - describe('not authenticated', () => { - it('does not fetch anything', () => { - getAuthenticatedUser.mockReturnValueOnce(null); - api.getNotices({ onLoad }); - expect(get).not.toHaveBeenCalled(); - }); - }); - describe('authenticated', () => { - it('fetches noticesUrl with onLoad behavior', async () => { - await api.getNotices({ onLoad }); - expect(get).toHaveBeenCalledWith(api.noticesUrl, {}); - expect(onLoad).toHaveBeenCalledWith(testData); - }); - it('calls logInfo if fetch fails with 404', async () => { - get.mockImplementation(error404Get); - await api.getNotices({ onLoad }); - expect(logInfo).toHaveBeenCalledWith(`${error404}. ${api.error404Message}`); - }); - it('calls logError if fetch fails with non-404 error', async () => { - get.mockImplementation(error500Get); - await api.getNotices({ onLoad }); - expect(logError).toHaveBeenCalledWith(error500); - }); - }); - }); -}); diff --git a/src/components/NoticesWrapper/hooks.js b/src/components/NoticesWrapper/hooks.js deleted file mode 100644 index 3c398aea9..000000000 --- a/src/components/NoticesWrapper/hooks.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import { getConfig } from '@edx/frontend-platform'; -import { useIntl } from 'react-intl'; - -import { StrictDict } from 'utils'; -import { getNotices } from './api'; -import * as module from './hooks'; -import messages from './messages'; - -/** - * This component uses the platform-plugin-notices plugin to function. - * If the user has an unacknowledged notice, they will be rerouted off - * course home and onto a full-screen notice page. If the plugin is not - * installed, or there are no notices, we just passthrough this component. - */ -export const state = StrictDict({ - isRedirected: (val) => React.useState(val), // eslint-disable-line -}); - -export const useNoticesWrapperData = () => { - const [isRedirected, setIsRedirected] = module.state.isRedirected(); - const { formatMessage } = useIntl(); - - React.useEffect(() => { - if (getConfig().ENABLE_NOTICES) { - getNotices({ - onLoad: (data) => { - if (data?.data?.results?.length > 0) { - setIsRedirected(true); - window.location.replace(`${data.data.results[0]}?next=${window.location.href}`); - } - }, - notFoundMessage: formatMessage(messages.error404Message), - }); - } - }, [setIsRedirected, formatMessage]); - return { isRedirected }; -}; - -export default useNoticesWrapperData; diff --git a/src/components/NoticesWrapper/hooks.test.js b/src/components/NoticesWrapper/hooks.test.js deleted file mode 100644 index d08f54f26..000000000 --- a/src/components/NoticesWrapper/hooks.test.js +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react'; - -import { MockUseState } from 'testUtils'; - -import { getConfig } from '@edx/frontend-platform'; -import { getNotices } from './api'; -import * as hooks from './hooks'; - -jest.mock('@edx/frontend-platform', () => ({ getConfig: jest.fn() })); -jest.mock('./api', () => ({ getNotices: jest.fn() })); -const mockFormatMessage = jest.fn(message => message.defaultMessage || 'translated-string'); -jest.mock('react-intl', () => ({ - useIntl: () => ({ - formatMessage: mockFormatMessage, - }), -})); - -getConfig.mockReturnValue({ ENABLE_NOTICES: true }); -const state = new MockUseState(hooks); - -let hook; -describe('NoticesWrapper hooks', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - describe('state hooks', () => { - state.testGetter(state.keys.isRedirected); - }); - describe('useNoticesWrapperData', () => { - beforeEach(() => { - state.mock(); - }); - describe('behavior', () => { - it('initializes state hooks', () => { - hooks.useNoticesWrapperData(); - expect(hooks.state.isRedirected).toHaveBeenCalledWith(); - }); - describe('effects', () => { - it('does not call notices if not enabled', () => { - getConfig.mockReturnValueOnce({ ENABLE_NOTICES: false }); - hooks.useNoticesWrapperData(); - const [cb, prereqs] = React.useEffect.mock.calls[0]; - expect(prereqs).toEqual([state.setState.isRedirected, mockFormatMessage]); - cb(); - expect(getNotices).not.toHaveBeenCalled(); - }); - describe('getNotices call (if enabled) onLoad behavior', () => { - it('does not redirect if there are no results', () => { - hooks.useNoticesWrapperData(); - expect(React.useEffect).toHaveBeenCalled(); - const [cb, prereqs] = React.useEffect.mock.calls[0]; - expect(prereqs).toEqual([state.setState.isRedirected, mockFormatMessage]); - cb(); - expect(getNotices).toHaveBeenCalled(); - const { onLoad } = getNotices.mock.calls[0][0]; - onLoad({}); - expect(state.setState.isRedirected).not.toHaveBeenCalled(); - onLoad({ data: {} }); - expect(state.setState.isRedirected).not.toHaveBeenCalled(); - onLoad({ data: { results: [] } }); - expect(state.setState.isRedirected).not.toHaveBeenCalled(); - }); - it('redirects and set isRedirected if results are returned', () => { - delete window.location; - window.location = { replace: jest.fn(), href: 'test-old-href' }; - hooks.useNoticesWrapperData(); - const [cb, prereqs] = React.useEffect.mock.calls[0]; - expect(prereqs).toEqual([state.setState.isRedirected, mockFormatMessage]); - cb(); - expect(getNotices).toHaveBeenCalled(); - const { onLoad } = getNotices.mock.calls[0][0]; - const target = 'url-target'; - onLoad({ data: { results: [target] } }); - expect(state.setState.isRedirected).toHaveBeenCalledWith(true); - expect(window.location.replace).toHaveBeenCalledWith( - `${target}?next=${window.location.href}`, - ); - }); - }); - }); - }); - describe('output', () => { - it('forwards isRedirected from state call', () => { - hook = hooks.useNoticesWrapperData(); - expect(hook.isRedirected).toEqual(state.stateVals.isRedirected); - }); - }); - }); -}); diff --git a/src/components/NoticesWrapper/index.jsx b/src/components/NoticesWrapper/index.jsx deleted file mode 100644 index faf9b9f2e..000000000 --- a/src/components/NoticesWrapper/index.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import useNoticesWrapperData from './hooks'; - -/** - * This component uses the platform-plugin-notices plugin to function. - * If the user has an unacknowledged notice, they will be rerouted off - * course home and onto a full-screen notice page. If the plugin is not - * installed, or there are no notices, we just passthrough this component. - */ -const NoticesWrapper = ({ children }) => { - const { isRedirected } = useNoticesWrapperData(); - return ( -
- {isRedirected === true ? null : children} -
- ); -}; - -NoticesWrapper.propTypes = { - children: PropTypes.node.isRequired, -}; - -export default NoticesWrapper; diff --git a/src/components/NoticesWrapper/index.test.jsx b/src/components/NoticesWrapper/index.test.jsx deleted file mode 100644 index ea0906edf..000000000 --- a/src/components/NoticesWrapper/index.test.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - -import useNoticesWrapperData from './hooks'; -import NoticesWrapper from '.'; - -jest.mock('./hooks', () => jest.fn()); - -const hookProps = { isRedirected: false }; -useNoticesWrapperData.mockReturnValue(hookProps); - -let el; -const children = [some, children]; -describe('NoticesWrapper component', () => { - describe('behavior', () => { - it('initializes hooks', () => { - el = shallow({children}); - expect(useNoticesWrapperData).toHaveBeenCalledWith(); - }); - }); - describe('output', () => { - it('does not show children if redirected', () => { - useNoticesWrapperData.mockReturnValueOnce({ isRedirected: true }); - el = shallow({children}); - expect(el.instance.children.length).toEqual(0); - }); - it('shows children if not redirected', () => { - el = shallow({children}); - expect(el.instance.children.length).toEqual(2); - expect(el.instance.children[0].type).toEqual(shallow(children[0]).type); - expect(el.instance.props).toEqual(shallow(children[0]).props); - expect(el.instance.children[1].type).toEqual(shallow(children[1]).type); - expect(el.instance.props).toEqual(shallow(children[1]).props); - }); - }); -}); diff --git a/src/components/NoticesWrapper/messages.js b/src/components/NoticesWrapper/messages.js deleted file mode 100644 index 6dbbefaaf..000000000 --- a/src/components/NoticesWrapper/messages.js +++ /dev/null @@ -1,11 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - error404Message: { - id: 'learner-dash.notices.error404Message', - defaultMessage: 'This probably happened because the notices plugin is not installed on platform.', - description: 'Error message when notices API returns 404', - }, -}); - -export default messages; diff --git a/src/components/__snapshots__/Banner.test.jsx.snap b/src/components/__snapshots__/Banner.test.jsx.snap deleted file mode 100644 index 5f70d5b48..000000000 --- a/src/components/__snapshots__/Banner.test.jsx.snap +++ /dev/null @@ -1,31 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Banner snapshot renders default banner 1`] = ` - - Hello, world! - -`; - -exports[`Banner snapshot renders with custom class 1`] = ` - - Hello, world! - -`; - -exports[`Banner snapshot renders with variants 1`] = ` - - Hello, world! - -`; diff --git a/src/config/index.js b/src/config/index.js deleted file mode 100644 index 813332920..000000000 --- a/src/config/index.js +++ /dev/null @@ -1,27 +0,0 @@ -const configuration = { - // BASE_URL: process.env.BASE_URL, - LMS_BASE_URL: process.env.LMS_BASE_URL, - ECOMMERCE_BASE_URL: process.env.ECOMMERCE_BASE_URL, - // LOGIN_URL: process.env.LOGIN_URL, - // LOGOUT_URL: process.env.LOGOUT_URL, - // CSRF_TOKEN_API_PATH: process.env.CSRF_TOKEN_API_PATH, - // REFRESH_ACCESS_TOKEN_ENDPOINT: process.env.REFRESH_ACCESS_TOKEN_ENDPOINT, - // DATA_API_BASE_URL: process.env.DATA_API_BASE_URL, - // SECURE_COOKIES: process.env.NODE_ENV !== 'development', - SEGMENT_KEY: process.env.SEGMENT_KEY, - // ACCESS_TOKEN_COOKIE_NAME: process.env.ACCESS_TOKEN_COOKIE_NAME, - LEARNING_BASE_URL: process.env.LEARNING_BASE_URL, - SESSION_COOKIE_DOMAIN: process.env.SESSION_COOKIE_DOMAIN || '', - SUPPORT_URL: process.env.SUPPORT_URL || null, - ENABLE_NOTICES: process.env.ENABLE_NOTICES || null, - CAREER_LINK_URL: process.env.CAREER_LINK_URL || null, - LOGO_URL: process.env.LOGO_URL, - ENABLE_EDX_PERSONAL_DASHBOARD: process.env.ENABLE_EDX_PERSONAL_DASHBOARD === 'true', - SEARCH_CATALOG_URL: process.env.SEARCH_CATALOG_URL || null, - ENABLE_PROGRAMS: process.env.ENABLE_PROGRAMS === 'true', - NON_BROWSABLE_COURSES: process.env.NON_BROWSABLE_COURSES === 'true', -}; - -const features = {}; - -export { configuration, features }; diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 000000000..4c3170afc --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,2 @@ +export const appId = 'org.openedx.frontend.app.learnerDashboard'; +export const dashboardRole = 'org.openedx.frontend.role.dashboard'; diff --git a/src/containers/CourseCard/CourseCard.scss b/src/containers/CourseCard/CourseCard.scss index a84cafd1c..3a29abb12 100644 --- a/src/containers/CourseCard/CourseCard.scss +++ b/src/containers/CourseCard/CourseCard.scss @@ -1,10 +1,8 @@ -@import "@openedx/paragon/scss/core/core"; - .course-card { .card { .pgn__card-wrapper-image-cap.vertical { display: flex; - min-height: $card-image-vertical-max-height; + min-height: var(--pgn-size-card-image-vertical-max-height); } .pgn__card-image-cap { border-bottom-left-radius: 0 !important; @@ -53,11 +51,11 @@ > .alert { border-radius: 0; box-shadow: none; - padding: map-get($spacers, 3) map-get($spacers, 4); + padding: var(--pgn-spacing-spacer-3) var(--pgn-spacing-spacer-4); &:last-of-type { - border-bottom-left-radius: $alert-border-radius; - border-bottom-right-radius: $alert-border-radius; + border-bottom-left-radius: var(--pgn-size-alert-border-radius); + border-bottom-right-radius: var(--pgn-size-alert-border-radius); } } diff --git a/src/containers/CourseCard/__snapshots__/index.test.jsx.snap b/src/containers/CourseCard/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 08f36c4e7..000000000 --- a/src/containers/CourseCard/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,111 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CourseCard component snapshot: collapsed 1`] = ` -
- -
-
- - - - } - title={ - - } - /> - - - - - - - -
- -
-
-
-`; - -exports[`CourseCard component snapshot: not collapsed 1`] = ` -
- -
-
- - - - } - title={ - - } - /> - - - - - - - -
- -
-
-
-`; diff --git a/src/containers/CourseCard/components/CourseCardActions/ActionButton/__snapshots__/index.test.jsx.snap b/src/containers/CourseCard/components/CourseCardActions/ActionButton/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 13ddef055..000000000 --- a/src/containers/CourseCard/components/CourseCardActions/ActionButton/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ActionButton snapshot is collapsed 1`] = ` - - - - -`; - -exports[`CreditContent component render without action snapshot 1`] = ` - -
- test-message -
- -
-`; diff --git a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/__snapshots__/ProviderLink.test.jsx.snap b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/__snapshots__/ProviderLink.test.jsx.snap deleted file mode 100644 index 8542496f6..000000000 --- a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/components/__snapshots__/ProviderLink.test.jsx.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ProviderLink component render snapshot 1`] = ` - - test-credit-provider-name - -`; diff --git a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.js b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.js index 81cffe90f..90f73656b 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.js +++ b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.js @@ -1,7 +1,8 @@ import React from 'react'; - -import { StrictDict } from 'utils'; -import { apiHooks } from 'hooks'; +import { useAuthenticatedUser } from '@openedx/frontend-base'; +import { StrictDict } from '@src/utils'; +import { useCourseData } from '@src/hooks'; +import { useCreateCreditRequest } from '@src/data/hooks'; import * as module from './hooks'; @@ -11,13 +12,19 @@ export const state = StrictDict({ export const useCreditRequestData = (cardId) => { const [requestData, setRequestData] = module.state.creditRequestData(null); - const createCreditApiRequest = apiHooks.useCreateCreditRequest(cardId); + const courseData = useCourseData(cardId); + const providerId = courseData?.credit?.providerId; + const { username } = useAuthenticatedUser(); + const courseId = courseData?.courseRun?.courseId; + const { mutate: createCreditMutation } = useCreateCreditRequest(); + const createCreditRequest = (e) => { e.preventDefault(); - createCreditApiRequest() - .then((request) => { - setRequestData(request.data); - }); + createCreditMutation({ providerId, courseId, username }, { + onSuccess: (response) => { + setRequestData(response.data); + }, + }); }; return { requestData, createCreditRequest }; }; diff --git a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.test.js b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.test.js deleted file mode 100644 index d3e5c0695..000000000 --- a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.test.js +++ /dev/null @@ -1,56 +0,0 @@ -import { MockUseState } from 'testUtils'; -import { apiHooks } from 'hooks'; -import * as hooks from './hooks'; - -jest.mock('hooks', () => ({ - apiHooks: { - useCreateCreditRequest: jest.fn(), - }, -})); - -const state = new MockUseState(hooks); - -const cardId = 'test-card-id'; -const requestData = { data: 'request data' }; -const creditRequest = jest.fn().mockReturnValue(Promise.resolve(requestData)); -apiHooks.useCreateCreditRequest.mockReturnValue(creditRequest); -const event = { preventDefault: jest.fn() }; - -let out; -describe('Credit Banner view hooks', () => { - describe('state', () => { - state.testGetter(state.keys.creditRequestData); - }); - describe('useCreditRequestData', () => { - beforeEach(() => { - state.mock(); - out = hooks.useCreditRequestData(cardId); - }); - describe('behavior', () => { - it('initializes creditRequestData state field with null value', () => { - state.expectInitializedWith(state.keys.creditRequestData, null); - }); - it('calls useCreateCreditRequest with passed cardID', () => { - expect(apiHooks.useCreateCreditRequest).toHaveBeenCalledWith(cardId); - }); - }); - describe('output', () => { - it('returns requestData state value', () => { - state.mockVal(state.keys.creditRequestData, requestData); - out = hooks.useCreditRequestData(cardId); - expect(out.requestData).toEqual(requestData); - }); - describe('createCreditRequest', () => { - it('returns an event handler that prevents default click behavior', () => { - out.createCreditRequest(event); - expect(event.preventDefault).toHaveBeenCalled(); - }); - it('calls api.createCreditRequest and sets requestData with the response', async () => { - await out.createCreditRequest(event); - expect(creditRequest).toHaveBeenCalledWith(); - expect(state.setState.creditRequestData).toHaveBeenCalledWith(requestData.data); - }); - }); - }); - }); -}); diff --git a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.test.tsx b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.test.tsx new file mode 100644 index 000000000..f721432db --- /dev/null +++ b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/hooks.test.tsx @@ -0,0 +1,189 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import React from 'react'; +import * as api from '@src/data/services/lms/api'; +import { useCourseData } from '@src/hooks'; +import { useAuthenticatedUser } from '@openedx/frontend-base'; +import * as hooks from './hooks'; + +jest.mock('@src/data/services/lms/api', () => ({ + createCreditRequest: jest.fn(), +})); + +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(), +})); + +jest.mock('@openedx/frontend-base', () => ({ + ...jest.requireActual('@openedx/frontend-base'), + logError: jest.fn(), + useAuthenticatedUser: jest.fn(), +})); + +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + mutations: { retry: false }, + }, + }); + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + + ); + return wrapper; +}; + +describe('useCreditRequestData', () => { + let wrapper; + + beforeEach(() => { + wrapper = createWrapper(); + (useAuthenticatedUser as jest.Mock).mockReturnValue({ username: 'test-user' }); + (useCourseData as jest.Mock).mockReturnValue({ + credit: { providerId: 'provider-123' }, + courseRun: { courseId: 'course-456' }, + }); + jest.clearAllMocks(); + }); + + it('initializes requestData as null', () => { + const { result } = renderHook(() => hooks.useCreditRequestData('card-123'), { wrapper }); + + expect(result.current.requestData).toBeNull(); + }); + + it('returns createCreditRequest function', () => { + const { result } = renderHook(() => hooks.useCreditRequestData('card-123'), { wrapper }); + + expect(typeof result.current.createCreditRequest).toBe('function'); + }); + + it('prevents default event behavior', async () => { + const event = { preventDefault: jest.fn() }; + (api.createCreditRequest as jest.Mock).mockResolvedValue({ data: 'success' }); + + const { result } = renderHook(() => hooks.useCreditRequestData('card-123'), { wrapper }); + + await act(async () => { + result.current.createCreditRequest(event); + }); + + expect(event.preventDefault).toHaveBeenCalled(); + }); + + it('calls API with correct parameters', async () => { + const event = { preventDefault: jest.fn() }; + (api.createCreditRequest as jest.Mock).mockResolvedValue({ data: 'success' }); + + const { result } = renderHook(() => hooks.useCreditRequestData('card-123'), { wrapper }); + + await act(async () => { + result.current.createCreditRequest(event); + }); + + expect(api.createCreditRequest).toHaveBeenCalledWith({ + providerId: 'provider-123', + courseId: 'course-456', + username: 'test-user', + }); + }); + + it('sets requestData with response data on success', async () => { + const event = { preventDefault: jest.fn() }; + const responseData = { data: { id: 'credit-123', status: 'pending' } }; + (api.createCreditRequest as jest.Mock).mockResolvedValue(responseData); + + const { result } = renderHook(() => hooks.useCreditRequestData('card-123'), { wrapper }); + + await act(async () => { + result.current.createCreditRequest(event); + }); + + expect(api.createCreditRequest).toHaveBeenCalledWith({ + providerId: 'provider-123', + courseId: 'course-456', + username: 'test-user', + }); + + await waitFor(() => { + expect(result.current.requestData).toEqual(responseData.data); + }); + }); + + it('handles missing providerId gracefully', async () => { + const event = { preventDefault: jest.fn() }; + (useCourseData as jest.Mock).mockReturnValue({ + credit: null, + courseRun: { courseId: 'course-456' }, + }); + + const { result } = renderHook(() => hooks.useCreditRequestData('card-123'), { wrapper }); + + await act(async () => { + result.current.createCreditRequest(event); + }); + + expect(api.createCreditRequest).toHaveBeenCalledWith({ + providerId: undefined, + courseId: 'course-456', + username: 'test-user', + }); + }); + + it('handles missing courseId gracefully', async () => { + const event = { preventDefault: jest.fn() }; + (useCourseData as jest.Mock).mockReturnValue({ + credit: { providerId: 'provider-123' }, + courseRun: null, + }); + + const { result } = renderHook(() => hooks.useCreditRequestData('card-123'), { wrapper }); + + await act(async () => { + result.current.createCreditRequest(event); + }); + + expect(api.createCreditRequest).toHaveBeenCalledWith({ + providerId: 'provider-123', + courseId: undefined, + username: 'test-user', + }); + }); + + it('handles API errors without crashing', async () => { + const event = { preventDefault: jest.fn() }; + (api.createCreditRequest as jest.Mock).mockRejectedValue(new Error('API Error')); + + const { result } = renderHook(() => hooks.useCreditRequestData('card-123'), { wrapper }); + + await act(async () => { + result.current.createCreditRequest(event); + }); + + expect(result.current.requestData).toBeNull(); + }); + + it('uses cardId to fetch course data', () => { + renderHook(() => hooks.useCreditRequestData('different-card'), { wrapper }); + + expect(useCourseData).toHaveBeenCalledWith('different-card'); + }); + + it('handles undefined response data', async () => { + const event = { preventDefault: jest.fn() }; + (api.createCreditRequest as jest.Mock).mockResolvedValue({ status: 200 }); + + const { result } = renderHook(() => hooks.useCreditRequestData('card-123'), { wrapper }); + + await act(async () => { + result.current.createCreditRequest(event); + }); + + await waitFor(() => { + expect(result.current.requestData).toBeUndefined(); + }); + }); +}); diff --git a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/messages.js b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/messages.js index 45e7fd0f3..ddc98347d 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/messages.js +++ b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/messages.js @@ -1,59 +1,59 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ approved: { id: 'learner-dash.courseCard.banners.credit.approved', - description: '', + description: 'Message shown when credit request has been approved', defaultMessage: '{congratulations} {providerName} has approved your request for course credit. To see your course credit, visit the {linkToProviderSite} website.', }, congratulations: { id: 'learner-dash.courseCard.banners.credit.congratulations', - description: '', + description: 'Congratulatory message for credit approval', defaultMessage: 'Congratulations!', }, eligible: { id: 'learner-dash.courseCard.banners.credit.eligible', - description: '', + description: 'Message shown when user is eligible to purchase course credit', defaultMessage: 'You have completed this course and are eligible to purchase course credit. Select {getCredit} to get started.', }, eligibleFromProvider: { id: 'learner-dash.courseCard.banners.credit.eligibleFromProvider', - description: '', + description: 'Message shown when user is eligible for credit from a specific provider', defaultMessage: 'You are now eligible for credit from {providerName}. Congratulations!', }, getCredit: { id: 'learner-dash.courseCard.banners.credit.getCredit', - description: '', + description: 'Button text for initiating the credit process', defaultMessage: 'Get Credit', }, mustRequest: { id: 'learner-dash.courseCard.banners.credit.mustRequest', - description: '', + description: 'Message shown after payment to instruct user to request credit', defaultMessage: 'Thank you for your payment. To receive course credit, you must request credit at the {linkToProviderSite} website. Select {requestCredit} to get started', }, received: { id: 'learner-dash.courseCard.banners.credit.received', - description: '', + description: 'Message shown when credit request has been received', defaultMessage: '{providerName} has received your course credit request. We will update you when credit processing is complete.', }, rejected: { id: 'learner-dash.courseCard.banners.credit.rejected', - description: '', + description: 'Message shown when credit request has been rejected', defaultMessage: '{providerName} did not approve your request for course credit. For more information, contact {linkToProviderSite} directly.', }, requestCredit: { id: 'learner-dash.courseCard.banners.credit.requestCredit', - description: '', + description: 'Button text for requesting credit', defaultMessage: 'Request Credit', }, viewCredit: { id: 'learner-dash.courseCard.banners.credit.viewCredit', - description: '', + description: 'Button text for viewing credit details', defaultMessage: 'View Credit', }, viewDetails: { id: 'learner-dash.courseCard.banners.credit.viewDetails', - description: '', + description: 'Button text for viewing credit request details', defaultMessage: 'View Details', }, }); diff --git a/src/containers/CourseCard/components/CourseCardBanners/EntitlementBanner.jsx b/src/containers/CourseCard/components/CourseCardBanners/EntitlementBanner.jsx index 16751846c..47204692f 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/EntitlementBanner.jsx +++ b/src/containers/CourseCard/components/CourseCardBanners/EntitlementBanner.jsx @@ -1,16 +1,21 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import { Button, MailtoLink } from '@openedx/paragon'; -import { utilHooks, reduxHooks } from 'hooks'; +import { utilHooks, useCourseData, useEntitlementInfo } from '@src/hooks'; +import { useSelectSessionModal } from '@src/data/context'; +import Banner from '@src/components/Banner'; +import { useInitializeLearnerHome } from '@src/data/hooks'; -import Banner from 'components/Banner'; import messages from './messages'; export const EntitlementBanner = ({ cardId }) => { const { formatMessage } = useIntl(); + const { data: learnerHomeData } = useInitializeLearnerHome(); + const courseData = useCourseData(cardId); + const { isEntitlement, hasSessions, @@ -18,9 +23,12 @@ export const EntitlementBanner = ({ cardId }) => { changeDeadline, showExpirationWarning, isExpired, - } = reduxHooks.useCardEntitlementData(cardId); - const { supportEmail } = reduxHooks.usePlatformSettingsData(); - const openSessionModal = reduxHooks.useUpdateSelectSessionModalCallback(cardId); + } = useEntitlementInfo(courseData); + const supportEmail = useMemo( + () => learnerHomeData?.platformSettings?.supportEmail, + [learnerHomeData], + ); + const { updateSelectSessionModal } = useSelectSessionModal(); const formatDate = utilHooks.useFormatDate(); if (!isEntitlement) { @@ -42,7 +50,7 @@ export const EntitlementBanner = ({ cardId }) => { {formatMessage(messages.entitlementExpiringSoon, { changeDeadline: formatDate(changeDeadline), selectSessionButton: ( - ), diff --git a/src/containers/CourseCard/components/CourseCardBanners/EntitlementBanner.test.jsx b/src/containers/CourseCard/components/CourseCardBanners/EntitlementBanner.test.jsx index af213ab10..4f315dc64 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/EntitlementBanner.test.jsx +++ b/src/containers/CourseCard/components/CourseCardBanners/EntitlementBanner.test.jsx @@ -1,26 +1,43 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { IntlProvider } from '@openedx/frontend-base'; +import { formatMessage } from '@src/testUtils'; -import { reduxHooks } from 'hooks'; +import { useCourseData } from '@src/hooks'; import EntitlementBanner from './EntitlementBanner'; +import messages from './messages'; -jest.mock('components/Banner', () => 'Banner'); -jest.mock('hooks', () => ({ +jest.mock('react', () => ({ + ...jest.requireActual('react'), + useMemo: (fn) => fn(), +})); + +jest.mock('@src/data/hooks', () => ({ + useInitializeLearnerHome: jest.fn().mockReturnValue({ + data: { + platformSettings: { + supportEmail: 'test-support-email', + }, + }, + }), +})); +const mockUpdateSelectSessionModal = jest.fn().mockName('updateSelectSessionModal'); +jest.mock('@src/data/context/SelectSessionProvider', () => ({ + useSelectSessionModal: () => ({ + updateSelectSessionModal: mockUpdateSelectSessionModal, + }), +})); + +jest.mock('@src/hooks', () => ({ + ...jest.requireActual('@src/hooks'), + useCourseData: jest.fn(), utilHooks: { - useFormatDate: () => date => date, + useFormatDate: () => date => date?.toDateString(), }, - reduxHooks: { - usePlatformSettingsData: jest.fn(), - useCardEntitlementData: jest.fn(), - useUpdateSelectSessionModalCallback: jest.fn( - (cardId) => jest.fn().mockName(`updateSelectSessionModalCallback(${cardId})`), - ), - }, -})); -const cardId = 'my-test-course-number'; +})); -let el; +const cardId = 'test-card-id'; const entitlementData = { isEntitlement: true, @@ -31,33 +48,78 @@ const entitlementData = { }; const platformData = { supportEmail: 'test-support-email' }; -const render = (overrides = {}) => { +const renderComponent = (overrides = {}) => { const { entitlement = {} } = overrides; - reduxHooks.useCardEntitlementData.mockReturnValueOnce({ ...entitlementData, ...entitlement }); - reduxHooks.usePlatformSettingsData.mockReturnValueOnce(platformData); - el = shallow(); + useCourseData.mockReturnValue({ + entitlement: { ...entitlementData, ...entitlement }, + platformSettings: platformData, + }); + return render(); }; describe('EntitlementBanner', () => { - test('initializes data with course number from entitlement', () => { - render(); - expect(reduxHooks.useCardEntitlementData).toHaveBeenCalledWith(cardId); - expect(reduxHooks.useUpdateSelectSessionModalCallback).toHaveBeenCalledWith(cardId); - }); - test('no display if not an entitlement', () => { - render({ entitlement: { isEntitlement: false } }); - expect(el.isEmptyRender()).toEqual(true); - }); - test('snapshot: no sessions available', () => { - render({ entitlement: { isFulfilled: false, hasSessions: false } }); - expect(el.snapshot).toMatchSnapshot(); - }); - test('snapshot: expiration warning', () => { - render({ entitlement: { showExpirationWarning: true } }); - expect(el.snapshot).toMatchSnapshot(); - }); - test('no display if sessions available and not displaying warning', () => { - render(); - expect(el.isEmptyRender()).toEqual(true); + beforeEach(() => { + jest.clearAllMocks(); + }); + it('initializes data with course number from entitlement', () => { + renderComponent(); + expect(useCourseData).toHaveBeenCalledWith(cardId); + }); + it('no display if not an entitlement', () => { + renderComponent({ entitlement: { isEntitlement: false } }); + const banner = screen.queryByRole('alert'); + expect(banner).toBeNull(); + }); + it('renders when no sessions available', () => { + renderComponent({ entitlement: { isFulfilled: false, hasSessions: false } }); + const banner = screen.getByRole('alert'); + expect(banner).toBeInTheDocument(); + expect(banner).toHaveClass('alert-warning'); + expect(banner.innerHTML).toContain(platformData.supportEmail); + }); + it('renders when expiration warning', () => { + const deadline = new Date(); + deadline.setDate(deadline.getDate() + 4); + const deadlineStr = `${deadline.getMonth() + 1}/${deadline.getDate()}/${deadline.getFullYear()}`; + renderComponent({ entitlement: { changeDeadline: deadlineStr, isFulfilled: false, availableSessions: [1, 2, 3] } }); + const banner = screen.getByRole('alert'); + expect(banner).toBeInTheDocument(); + expect(banner).toHaveClass('alert-info'); + const button = screen.getByRole('button', { name: formatMessage(messages.selectSession) }); + expect(button).toBeInTheDocument(); + }); + it('renders expired banner', () => { + renderComponent({ entitlement: { isExpired: true, availableSessions: [1, 2, 3] } }); + const banner = screen.getByRole('alert'); + expect(banner).toBeInTheDocument(); + expect(banner.innerHTML).toContain(formatMessage(messages.entitlementExpired)); + }); + it('should call updateSelectSessionModal with cardId when select session button is clicked', async () => { + const user = userEvent.setup(); + const deadline = new Date(); + deadline.setDate(deadline.getDate() + 4); + const deadlineStr = `${deadline.getMonth() + 1}/${deadline.getDate()}/${deadline.getFullYear()}`; + renderComponent({ entitlement: { changeDeadline: deadlineStr, isFulfilled: false, availableSessions: [1, 2, 3] } }); + const banner = screen.getByRole('alert'); + expect(banner).toBeInTheDocument(); + expect(banner).toHaveClass('alert-info'); + const button = screen.getByRole('button', { name: formatMessage(messages.selectSession) }); + expect(button).toBeInTheDocument(); + await user.click(button); + + expect(mockUpdateSelectSessionModal).toHaveBeenCalledWith(cardId); + }); + it('should return null when isExpired is false and showExpirationWarning is false', () => { + renderComponent({ + entitlement: { + isEntitlement: true, + hasSessions: true, + isFulfilled: true, + showExpirationWarning: false, + isExpired: false, + }, + }); + const banner = screen.queryByRole('alert'); + expect(banner).toBeNull(); }); }); diff --git a/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/ProgramsList.test.jsx b/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/ProgramsList.test.jsx index cfae9c684..f866868e4 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/ProgramsList.test.jsx +++ b/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/ProgramsList.test.jsx @@ -1,4 +1,4 @@ -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; import { ProgramsList } from './ProgramsList'; @@ -9,15 +9,23 @@ describe('ProgramsList', () => { title: 'Example Program 1', }, { - programUrl: 'http://example.com', + programUrl: 'http://example2.com', title: 'Example Program 2', }, ]; it('renders correctly', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); + render(); + const list = screen.getByRole('list'); + expect(list).toBeInTheDocument(); + expect(list.children.length).toEqual(programs.length); + }); - expect(wrapper.instance.findByType('li').length).toEqual(programs.length); + it('add the links correctly', () => { + render(); + programs.forEach(program => { + const link = screen.getByRole('link', { name: program.title }); + expect(link).toHaveAttribute('href', program.url); + }); }); }); diff --git a/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/__snapshots__/ProgramsList.test.jsx.snap b/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/__snapshots__/ProgramsList.test.jsx.snap deleted file mode 100644 index 6d50e8f9a..000000000 --- a/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/__snapshots__/ProgramsList.test.jsx.snap +++ /dev/null @@ -1,28 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ProgramsList renders correctly 1`] = ` - -`; diff --git a/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/__snapshots__/index.test.jsx.snap b/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 1ee1e9c81..000000000 --- a/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RelatedProgramsBanner render with programs 1`] = ` - - - Related Programs: - - - -`; diff --git a/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/index.jsx b/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/index.jsx index a2400b177..63d683eca 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/index.jsx +++ b/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/index.jsx @@ -2,20 +2,20 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Program } from '@openedx/paragon/icons'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; -import { reduxHooks } from 'hooks'; -import Banner from 'components/Banner'; +import { useCourseData } from '@src/hooks'; +import Banner from '@src/components/Banner'; import ProgramList from './ProgramsList'; import messages from './messages'; export const RelatedProgramsBanner = ({ cardId }) => { const { formatMessage } = useIntl(); + const courseData = useCourseData(cardId); + const programData = courseData?.programs; - const programData = reduxHooks.useCardRelatedProgramsData(cardId); - - if (!programData?.length) { + if (!courseData || !programData?.relatedPrograms.length) { return null; } @@ -27,7 +27,7 @@ export const RelatedProgramsBanner = ({ cardId }) => { {formatMessage(messages.relatedPrograms)} - +
); }; diff --git a/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/index.test.jsx b/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/index.test.jsx index 3ce3d387a..84ac74cf8 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/index.test.jsx +++ b/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/index.test.jsx @@ -1,42 +1,47 @@ -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; -import { reduxHooks } from 'hooks'; +import { useCourseData } from '@src/hooks'; import RelatedProgramsBanner from '.'; -jest.mock('./ProgramsList', () => 'ProgramsList'); - -jest.mock('hooks', () => ({ - reduxHooks: { - useCardRelatedProgramsData: jest.fn(), - }, +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(), })); const cardId = 'test-card-id'; +const programData = { + list: [ + { + title: 'Program 1', + url: 'http://example.com/program1', + }, + { + title: 'Program 2', + url: 'http://example.com/program2', + }, + ], + length: 2, +}; describe('RelatedProgramsBanner', () => { - test('render empty', () => { - reduxHooks.useCardRelatedProgramsData.mockReturnValue({ - length: 0, - }); - const el = shallow(); - expect(el.isEmptyRender()).toEqual(true); + it('render empty', () => { + useCourseData.mockReturnValue(null); + render(); + const banner = screen.queryByRole('alert'); + expect(banner).toBeNull(); + }); + + it('render with programs', () => { + useCourseData.mockReturnValue({ programs: { relatedPrograms: programData.list } }); + render(); + const list = screen.getByRole('list'); + expect(list.childElementCount).toBe(programData.list.length); }); - test('render with programs', () => { - reduxHooks.useCardRelatedProgramsData.mockReturnValue({ - list: [ - { - title: 'Program 1', - url: 'http://example.com/program1', - }, - { - title: 'Program 2', - url: 'http://example.com/program2', - }, - ], - length: 2, - }); - const el = shallow(); - expect(el.snapshot).toMatchSnapshot(); + it('render related programs title', () => { + useCourseData.mockReturnValue({ programs: { relatedPrograms: programData.list } }); + render(); + const title = screen.getByText('Related Programs:'); + expect(title).toBeInTheDocument(); }); }); diff --git a/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/messages.js b/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/messages.js index f624476ea..00699eb07 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/messages.js +++ b/src/containers/CourseCard/components/CourseCardBanners/RelatedProgramsBanner/messages.js @@ -1,4 +1,4 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ relatedPrograms: { diff --git a/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/CertificateBanner.test.jsx.snap b/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/CertificateBanner.test.jsx.snap deleted file mode 100644 index ed9c5e14c..000000000 --- a/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/CertificateBanner.test.jsx.snap +++ /dev/null @@ -1,205 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CertificateBanner snapshot is passing and is downloadable 1`] = ` - - Congratulations. Your certificate is ready. - -`; - -exports[`CertificateBanner snapshot is passing and is earned but unavailable 1`] = ` - - Your grade and certificate will be ready after 10/20/3030. - -`; - -exports[`CertificateBanner snapshot is passing and not downloadable render empty 1`] = `null`; - -exports[`CertificateBanner snapshot is restricted 1`] = ` - - Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know. - -`; - -exports[`CertificateBanner snapshot is restricted and verified 1`] = ` - - Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know. - - If you would like a refund on your Certificate of Achievement, please contact us. - -`; - -exports[`CertificateBanner snapshot is restricted and verified with billing email 1`] = ` - - Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know. - - - billing@email - , - } - } - /> - -`; - -exports[`CertificateBanner snapshot is restricted and verified with support and billing email 1`] = ` - - - suport@email - , - } - } - /> - - - billing@email - , - } - } - /> - -`; - -exports[`CertificateBanner snapshot is restricted and verified with support email 1`] = ` - - - suport@email - , - } - } - /> - - If you would like a refund on your Certificate of Achievement, please contact us. - -`; - -exports[`CertificateBanner snapshot is restricted with billing email 1`] = ` - - Your Certificate of Achievement is being held pending confirmation that the issuance of your Certificate is in compliance with strict U.S. embargoes on Iran, Cuba, Syria, and Sudan. If you think our system has mistakenly identified you as being connected with one of those countries, please let us know. - -`; - -exports[`CertificateBanner snapshot is restricted with support email 1`] = ` - - - suport@email - , - } - } - /> - -`; - -exports[`CertificateBanner snapshot not passing and audit 1`] = ` - - Grade required to pass the course: 0.8‏% - -`; - -exports[`CertificateBanner snapshot not passing and has finished 1`] = ` - - You are not eligible for a certificate. - - - View grades. - - -`; - -exports[`CertificateBanner snapshot not passing and is downloadable 1`] = ` - - Congratulations. Your certificate is ready. - -`; - -exports[`CertificateBanner snapshot not passing and not audit and not finished 1`] = ` - - Grade required for a certificate: 0.8‏% - -`; diff --git a/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/CourseBanner.test.jsx.snap b/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/CourseBanner.test.jsx.snap deleted file mode 100644 index 61c5ad49e..000000000 --- a/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/CourseBanner.test.jsx.snap +++ /dev/null @@ -1,38 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CourseBanner audit access expired snapshot: (auditAccessExpired, findAnotherCourse hyperlink) 1`] = ` - - - Your audit access to this course has expired. - - - Find another course - - - -`; - -exports[`CourseBanner snapshot: stacking banners 1`] = ``; - -exports[`CourseBanner staff snapshot: isStaff 1`] = ``; - -exports[`CourseBanner too early has start date snapshot 1`] = ` - - - You can't access this course just yet because the course hasn't started yet. The course will start on 11/11/3030. - - -`; - -exports[`CourseBanner too early no start date snapshot 1`] = ``; - -exports[`CourseBanner unmet prerequisites snapshot: unmetPrerequisites 1`] = ` - - - You can't access this course just yet because you have not met the pre-requisites. - - -`; diff --git a/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/EntitlementBanner.test.jsx.snap b/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/EntitlementBanner.test.jsx.snap deleted file mode 100644 index 4b3a66831..000000000 --- a/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/EntitlementBanner.test.jsx.snap +++ /dev/null @@ -1,53 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EntitlementBanner snapshot: expiration warning 1`] = ` - - - select a session - , - } - } - /> - -`; - -exports[`EntitlementBanner snapshot: no sessions available 1`] = ` - - - test-support-email - , - } - } - /> - -`; diff --git a/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/index.test.jsx.snap b/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/index.test.jsx.snap deleted file mode 100644 index dd664041f..000000000 --- a/src/containers/CourseCard/components/CourseCardBanners/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CourseCardBanners render with isEnrolled false 1`] = ` -
- - - -
-`; - -exports[`CourseCardBanners renders default CourseCardBanners 1`] = ` -
- - - - - -
-`; diff --git a/src/containers/CourseCard/components/CourseCardBanners/index.jsx b/src/containers/CourseCard/components/CourseCardBanners/index.jsx index ef05f1d4e..0465b464f 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/index.jsx +++ b/src/containers/CourseCard/components/CourseCardBanners/index.jsx @@ -1,16 +1,20 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { reduxHooks } from 'hooks'; +import { useCourseData } from '@src/hooks'; -import CourseBannerSlot from 'plugin-slots/CourseBannerSlot'; +import CourseBannerSlot from '@src/slots/CourseBannerSlot'; import CertificateBanner from './CertificateBanner'; import CreditBanner from './CreditBanner'; import EntitlementBanner from './EntitlementBanner'; import RelatedProgramsBanner from './RelatedProgramsBanner'; export const CourseCardBanners = ({ cardId }) => { - const { isEnrolled } = reduxHooks.useCardEnrollmentData(cardId); + const courseData = useCourseData(cardId); + if (!courseData) { + return null; + } + const { isEnrolled = false } = courseData.enrollment; return (
diff --git a/src/containers/CourseCard/components/CourseCardBanners/index.test.jsx b/src/containers/CourseCard/components/CourseCardBanners/index.test.jsx index 18a6720a7..2c259cb4c 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/index.test.jsx +++ b/src/containers/CourseCard/components/CourseCardBanners/index.test.jsx @@ -1,32 +1,56 @@ -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; +import { MemoryRouter } from 'react-router'; -import { reduxHooks } from 'hooks'; +import { useCourseData } from '@src/hooks'; import CourseCardBanners from '.'; -jest.mock('./CourseBanner', () => 'CourseBanner'); -jest.mock('./CertificateBanner', () => 'CertificateBanner'); -jest.mock('./CreditBanner', () => 'CreditBanner'); -jest.mock('./EntitlementBanner', () => 'EntitlementBanner'); -jest.mock('./RelatedProgramsBanner', () => 'RelatedProgramsBanner'); +jest.mock('./CourseBanner', () => jest.fn(() =>
CourseBanner
)); +jest.mock('./CertificateBanner', () => jest.fn(() =>
CertificateBanner
)); +jest.mock('./CreditBanner', () => jest.fn(() =>
CreditBanner
)); +jest.mock('./EntitlementBanner', () => jest.fn(() =>
EntitlementBanner
)); +jest.mock('./RelatedProgramsBanner', () => jest.fn(() =>
RelatedProgramsBanner
)); -jest.mock('hooks', () => ({ - reduxHooks: { - useCardEnrollmentData: jest.fn(() => ({ isEnrolled: true })), - }, +const mockedComponents = [ + 'CourseBanner', + 'CertificateBanner', + 'CreditBanner', + 'EntitlementBanner', + 'RelatedProgramsBanner', +]; + +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(() => ({ + enrollment: { + isEnrolled: true, + }, + })), })); describe('CourseCardBanners', () => { const props = { cardId: 'test-card-id', }; - test('renders default CourseCardBanners', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); + it('renders default CourseCardBanners', () => { + render(); + mockedComponents.map((componentName) => { + const mockedComponent = screen.getByText(componentName); + return expect(mockedComponent).toBeInTheDocument(); + }); + }); + it('render null with no courseData', () => { + useCourseData.mockReturnValue(null); + const { container } = render(); + expect(container.firstChild).toBeNull(); }); - test('render with isEnrolled false', () => { - reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isEnrolled: false }); - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); + it('render with isEnrolled false', () => { + useCourseData.mockReturnValue({ enrollment: { isEnrolled: false } }); + render(); + const mockedComponentsIfNotEnrolled = mockedComponents.slice(-2); + mockedComponentsIfNotEnrolled.map((componentName) => { + const mockedComponent = screen.getByText(componentName); + return expect(mockedComponent).toBeInTheDocument(); + }); }); }); diff --git a/src/containers/CourseCard/components/CourseCardBanners/messages.js b/src/containers/CourseCard/components/CourseCardBanners/messages.js index a68c48ebb..6baf0e3a5 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/messages.js +++ b/src/containers/CourseCard/components/CourseCardBanners/messages.js @@ -1,4 +1,4 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ auditAccessExpired: { diff --git a/src/containers/CourseCard/components/CourseCardDetails/__snapshots__/index.test.jsx.snap b/src/containers/CourseCard/components/CourseCardDetails/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 61e49aca0..000000000 --- a/src/containers/CourseCard/components/CourseCardDetails/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,56 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CourseCard Details component does not have change session button on regular course 1`] = ` -
- - provider-name - • - test-course-number - • access-message - -
-`; - -exports[`CourseCard Details component has change session button on entitlement course 1`] = ` -
- - provider-name - • - test-course-number - • access-message - • - - -
-`; - -exports[`CourseCard Details component has change session button on entitlement course but no access message 1`] = ` -
- - provider-name - • - test-course-number - • - - -
-`; diff --git a/src/containers/CourseCard/components/CourseCardDetails/hooks.js b/src/containers/CourseCard/components/CourseCardDetails/hooks.js index bcf285acf..35821d404 100644 --- a/src/containers/CourseCard/components/CourseCardDetails/hooks.js +++ b/src/containers/CourseCard/components/CourseCardDetails/hooks.js @@ -1,20 +1,23 @@ -import { useIntl } from '@edx/frontend-platform/i18n'; -import { utilHooks, reduxHooks } from 'hooks'; +import { useIntl } from '@openedx/frontend-base'; +import { utilHooks, useCourseData, useEntitlementInfo } from '@src/hooks'; +import { useSelectSessionModal } from '@src/data/context'; import * as hooks from './hooks'; import messages from './messages'; export const useAccessMessage = ({ cardId }) => { const { formatMessage } = useIntl(); - const enrollment = reduxHooks.useCardEnrollmentData(cardId); - const courseRun = reduxHooks.useCardCourseRunData(cardId); + const courseData = useCourseData(cardId); + const { courseRun, enrollment } = courseData || {}; const formatDate = utilHooks.useFormatDate(); if (!courseRun.isStarted) { - if (!courseRun.startDate && !courseRun.advertisedStart) { return null; } - const startDate = courseRun.advertisedStart ? courseRun.advertisedStart : formatDate(courseRun.startDate); + if (!courseRun.startDate && !courseRun.advertisedStart) { + return null; + } + const startDate = courseRun.advertisedStart || formatDate(courseRun.startDate); return formatMessage(messages.courseStarts, { startDate }); } - if (enrollment.isEnrolled) { + if (enrollment?.isEnrolled) { const { isArchived, endDate } = courseRun; const { accessExpirationDate, @@ -27,7 +30,9 @@ export const useAccessMessage = ({ cardId }) => { { accessExpirationDate: formatDate(accessExpirationDate) }, ); } - if (!endDate) { return null; } + if (!endDate) { + return null; + } return formatMessage( isArchived ? messages.courseEnded : messages.courseEnds, { endDate: formatDate(endDate) }, @@ -38,15 +43,15 @@ export const useAccessMessage = ({ cardId }) => { export const useCardDetailsData = ({ cardId }) => { const { formatMessage } = useIntl(); - const providerName = reduxHooks.useCardProviderData(cardId).name; - const { courseNumber } = reduxHooks.useCardCourseData(cardId); + const courseData = useCourseData(cardId); + const providerName = courseData?.courseProvider?.name; + const courseNumber = courseData?.course?.courseNumber; const { isEntitlement, isFulfilled, canChange, - } = reduxHooks.useCardEntitlementData(cardId); - - const openSessionModal = reduxHooks.useUpdateSelectSessionModalCallback(cardId); + } = useEntitlementInfo(courseData); + const { updateSelectSessionModal } = useSelectSessionModal(); return { providerName: providerName || formatMessage(messages.unknownProviderName), @@ -54,7 +59,7 @@ export const useCardDetailsData = ({ cardId }) => { isEntitlement, isFulfilled, canChange, - openSessionModal, + openSessionModal: () => updateSelectSessionModal(cardId), courseNumber, changeOrLeaveSessionMessage: formatMessage(messages.changeOrLeaveSessionButton), }; diff --git a/src/containers/CourseCard/components/CourseCardDetails/hooks.test.js b/src/containers/CourseCard/components/CourseCardDetails/hooks.test.js index 633696f7e..86c4969ad 100644 --- a/src/containers/CourseCard/components/CourseCardDetails/hooks.test.js +++ b/src/containers/CourseCard/components/CourseCardDetails/hooks.test.js @@ -1,25 +1,38 @@ -import { useIntl } from '@edx/frontend-platform/i18n'; - -import { keyStore } from 'utils'; -import { utilHooks, reduxHooks } from 'hooks'; +import { useIntl } from '@openedx/frontend-base'; +import { keyStore } from '@src/utils'; +import { utilHooks, useCourseData } from '@src/hooks'; +import { useSelectSessionModal } from '@src/data/context'; import * as hooks from './hooks'; import messages from './messages'; -jest.mock('hooks', () => ({ +jest.mock('react', () => ({ + ...jest.requireActual('react'), + useMemo: (fn) => fn(), +})); + +const updateSelectSessionModalMock = jest.fn().mockName('updateSelectSessionModal'); +jest.mock('@src/data/context', () => ({ + useSelectSessionModal: jest.fn(), +})); +jest.mock('@src/hooks', () => ({ + ...jest.requireActual('@src/hooks'), + useCourseData: jest.fn(), utilHooks: { useFormatDate: jest.fn(), }, - reduxHooks: { - useCardCourseData: jest.fn(), - useCardCourseRunData: jest.fn(), - useCardEnrollmentData: jest.fn(), - useCardEntitlementData: jest.fn(), - useCardProviderData: jest.fn(), - useUpdateSelectSessionModalCallback: (...args) => ({ updateSelectSessionModalCallback: args }), - }, })); +jest.mock('@openedx/frontend-base', () => { + const { formatMessage } = jest.requireActual('@src/testUtils'); + return { + ...jest.requireActual('@openedx/frontend-base'), + useIntl: () => ({ + formatMessage, + }), + }; +}); + const cardId = 'my-test-card-id'; const courseNumber = 'test-course-number'; const useAccessMessage = 'test-access-message'; @@ -50,15 +63,13 @@ describe('CourseCardDetails hooks', () => { const runHook = ({ provider = {}, entitlement = {} }) => { jest.spyOn(hooks, hookKeys.useAccessMessage) .mockImplementationOnce(mockAccessMessage); - reduxHooks.useCardProviderData.mockReturnValueOnce({ - ...providerData, - ...provider, - }); - reduxHooks.useCardEntitlementData.mockReturnValueOnce({ - ...entitlementData, - ...entitlement, + useCourseData.mockReturnValue({ + courseProvider: { ...providerData, ...provider }, + course: { courseNumber }, + courseRun: {}, + entitlement: { ...entitlementData, ...entitlement }, }); - reduxHooks.useCardCourseData.mockReturnValueOnce({ courseNumber }); + useSelectSessionModal.mockReturnValue({ updateSelectSessionModal: updateSelectSessionModalMock }); out = hooks.useCardDetailsData({ cardId }); }; beforeEach(() => { @@ -75,6 +86,10 @@ describe('CourseCardDetails hooks', () => { it('forward changeOrLeaveSessionMessage', () => { expect(out.changeOrLeaveSessionMessage).toEqual(formatMessage(messages.changeOrLeaveSessionButton)); }); + it('calls updateSelectSessionModal when openSessionModal is called', () => { + out.openSessionModal(); + expect(updateSelectSessionModalMock).toHaveBeenCalledWith(cardId); + }); }); describe('useAccessMessage', () => { @@ -91,21 +106,16 @@ describe('CourseCardDetails hooks', () => { endDate: '10/20/2000', }; const runHook = ({ enrollment = {}, courseRun = {} }) => { - reduxHooks.useCardCourseRunData.mockReturnValueOnce({ - ...courseRunData, - ...courseRun, - }); - reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ - ...enrollmentData, - ...enrollment, + useCourseData.mockReturnValue({ + courseRun: { ...courseRunData, ...courseRun }, + enrollment: { ...enrollmentData, ...enrollment }, }); out = hooks.useAccessMessage({ cardId }); }; it('loads data from enrollment and course run data based on course number', () => { runHook({}); - expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId); - expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(cardId); + expect(useCourseData).toHaveBeenCalledWith(cardId); }); describe('if not started yet', () => { diff --git a/src/containers/CourseCard/components/CourseCardDetails/index.scss b/src/containers/CourseCard/components/CourseCardDetails/index.scss index fb386e249..131fb85d9 100644 --- a/src/containers/CourseCard/components/CourseCardDetails/index.scss +++ b/src/containers/CourseCard/components/CourseCardDetails/index.scss @@ -1,7 +1,3 @@ -@import "~@edx/brand/paragon/variables"; -@import "~@openedx/paragon/scss/core/core"; -@import "~@edx/brand/paragon/overrides"; - a.course-card-title { - color: $black; + color: var(--pgn-color-black); } diff --git a/src/containers/CourseCard/components/CourseCardDetails/index.test.jsx b/src/containers/CourseCard/components/CourseCardDetails/index.test.jsx index d92e0d26f..8b746ded7 100644 --- a/src/containers/CourseCard/components/CourseCardDetails/index.test.jsx +++ b/src/containers/CourseCard/components/CourseCardDetails/index.test.jsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import CourseCardDetails from '.'; @@ -48,23 +47,36 @@ describe('CourseCard Details component', () => { return separatorsCount; }; - test('has change session button on entitlement course', () => { + it('has change session button on entitlement course', () => { const wrapper = createWrapper(); - expect(wrapper.container).toMatchSnapshot(); + const sessionButton = screen.getByRole('button', { name: defaultHooks.changeOrLeaveSessionMessage }); + expect(sessionButton).toBeInTheDocument(); + + const accessMessage = screen.getByText((text) => text.includes(defaultHooks.accessMessage)); + expect(accessMessage).toBeInTheDocument(); // it has 3 separator, 4 column expect(fetchSeparators(wrapper)).toBe(3); }); - test('has change session button on entitlement course but no access message', () => { + it('has change session button on entitlement course but no access message', () => { const wrapper = createWrapper({ accessMessage: null }); - expect(wrapper.container).toMatchSnapshot(); + const sessionButton = screen.getByRole('button', { name: defaultHooks.changeOrLeaveSessionMessage }); + expect(sessionButton).toBeInTheDocument(); + + const accessMessage = screen.queryByText((text) => text.includes(defaultHooks.accessMessage)); + expect(accessMessage).toBeNull(); + // it has 2 separator, 3 column expect(fetchSeparators(wrapper)).toBe(2); }); - test('does not have change session button on regular course', () => { + it('does not have change session button on regular course', () => { const wrapper = createWrapper({ isEntitlement: false }); - expect(wrapper.container).toMatchSnapshot(); + const sessionButton = screen.queryByRole('button', { name: defaultHooks.changeOrLeaveSessionMessage }); + expect(sessionButton).toBeNull(); + + const accessMessage = screen.getByText((text) => text.includes(defaultHooks.accessMessage)); + expect(accessMessage).toBeInTheDocument(); // it has 2 separator, 3 column expect(fetchSeparators(wrapper)).toBe(2); }); diff --git a/src/containers/CourseCard/components/CourseCardDetails/messages.js b/src/containers/CourseCard/components/CourseCardDetails/messages.js index a096aeb6e..b5f4e300c 100644 --- a/src/containers/CourseCard/components/CourseCardDetails/messages.js +++ b/src/containers/CourseCard/components/CourseCardDetails/messages.js @@ -1,4 +1,4 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ accessExpired: { diff --git a/src/containers/CourseCard/components/CourseCardImage.jsx b/src/containers/CourseCard/components/CourseCardImage.jsx index 97d22a78d..8c2bce6fd 100644 --- a/src/containers/CourseCard/components/CourseCardImage.jsx +++ b/src/containers/CourseCard/components/CourseCardImage.jsx @@ -1,12 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; +import { baseAppUrl } from '@src/data/services/lms/urls'; import { Badge } from '@openedx/paragon'; -import track from 'tracking'; -import { reduxHooks } from 'hooks'; -import verifiedRibbon from 'assets/verified-ribbon.png'; +import track from '@src/tracking'; +import { useCourseData, useCourseTrackingEvent } from '@src/hooks'; +import verifiedRibbon from '@src/assets/verified-ribbon.png'; import useActionDisabledState from './hooks'; import messages from '../messages'; @@ -15,11 +16,10 @@ const { courseImageClicked } = track.course; export const CourseCardImage = ({ cardId, orientation }) => { const { formatMessage } = useIntl(); - const { bannerImgSrc } = reduxHooks.useCardCourseData(cardId); - const { homeUrl } = reduxHooks.useCardCourseRunData(cardId); - const { isVerified } = reduxHooks.useCardEnrollmentData(cardId); + const courseData = useCourseData(cardId); + const { homeUrl } = courseData?.courseRun || {}; const { disableCourseTitle } = useActionDisabledState(cardId); - const handleImageClicked = reduxHooks.useTrackCourseEvent(courseImageClicked, cardId, homeUrl); + const handleImageClicked = useCourseTrackingEvent(courseImageClicked, cardId, homeUrl); const wrapperClassName = `pgn__card-wrapper-image-cap d-inline-block overflow-visible ${orientation}`; const image = ( <> @@ -27,11 +27,11 @@ export const CourseCardImage = ({ cardId, orientation }) => { // w-100 is necessary for images on Safari, otherwise stretches full height of the image // https://stackoverflow.com/a/44250830 className="pgn__card-image-cap w-100 show" - src={bannerImgSrc} + src={courseData?.course?.bannerImgSrc && baseAppUrl(courseData.course.bannerImgSrc)} alt={formatMessage(messages.bannerAlt)} /> { - isVerified && ( + courseData?.enrollment?.isVerified && ( ({ - course: { - courseImageClicked: jest.fn().mockName('segment.courseImageClicked'), - }, +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(() => ({ + course: { bannerImgSrc }, + courseRun: { homeUrl }, + enrollment: {}, + })), + useCourseTrackingEvent: jest.fn((eventName, cardId, url) => ({ + trackCourseEvent: { eventName, cardId, url }, + })), })); -jest.mock('hooks', () => ({ - reduxHooks: { - useCardCourseData: jest.fn(() => ({ bannerImgSrc: 'banner-img-src' })), - useCardCourseRunData: jest.fn(() => ({ homeUrl })), - useCardEnrollmentData: jest.fn(() => ({ isVerified: true })), - useTrackCourseEvent: jest.fn((eventName, cardId, url) => ({ - trackCourseEvent: { eventName, cardId, url }, - })), - }, -})); -jest.mock('./hooks', () => jest.fn(() => ({ disableCourseTitle: false }))); +jest.mock('./hooks', () => jest.fn()); describe('CourseCardImage', () => { const props = { - cardId: 'cardId', - orientation: 'orientation', + cardId: 'test-card-id', + orientation: 'horizontal', }; - beforeEach(() => { - jest.clearAllMocks(); + + it('renders course image with correct attributes', () => { + useActionDisabledState.mockReturnValue({ disableCourseTitle: true }); + useCourseData.mockReturnValue( + { + course: { bannerImgSrc }, + courseRun: { homeUrl }, + enrollment: { isVerified: true }, + }, + ); + render(); + + const image = screen.getByRole('img', { name: formatMessage(messages.bannerAlt) }); + expect(image).toBeInTheDocument(); + expect(image.src).toContain(bannerImgSrc); + expect(image.parentElement).toHaveClass('horizontal'); }); - describe('snapshot', () => { - test('renders clickable link course Image', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); - expect(wrapper.instance.type).toBe('a'); - expect(wrapper.instance.props.onClick).toEqual( - reduxHooks.useTrackCourseEvent( - track.course.courseImageClicked, - props.cardId, - homeUrl, - ), - ); - }); - test('renders disabled link', () => { - useActionDisabledState.mockReturnValueOnce({ disableCourseTitle: true }); - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); - expect(wrapper.instance.type).toBe('div'); - }); + + it('isVerified, should render badge', () => { + useActionDisabledState.mockReturnValue({ disableCourseTitle: false }); + useCourseData.mockReturnValue( + { + course: { bannerImgSrc }, + courseRun: { homeUrl }, + enrollment: { isVerified: true }, + }, + ); + render(); + + const badge = screen.getByText(formatMessage(messages.verifiedBanner)); + expect(badge).toBeInTheDocument(); + const badgeImg = screen.getByRole('img', { name: formatMessage(messages.verifiedBannerRibbonAlt) }); + expect(badgeImg).toBeInTheDocument(); + }); + + it('renders link with correct href if disableCourseTitle is false', () => { + useActionDisabledState.mockReturnValue({ disableCourseTitle: false }); + useCourseData.mockReturnValue( + { + course: { bannerImgSrc }, + courseRun: { homeUrl }, + enrollment: { isVerified: false }, + }, + ); + render(); + + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', homeUrl); }); - describe('behavior', () => { + describe('hooks', () => { it('initializes', () => { - shallow(); - expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(props.cardId); - expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith( - props.cardId, + useActionDisabledState.mockReturnValue({ disableCourseTitle: false }); + useCourseData.mockReturnValue( + { + course: { bannerImgSrc }, + courseRun: { homeUrl }, + enrollment: { isVerified: true }, + }, ); + render(); + expect(useCourseData).toHaveBeenCalledWith(props.cardId); expect(useActionDisabledState).toHaveBeenCalledWith(props.cardId); }); }); diff --git a/src/containers/CourseCard/components/CourseCardMenu/SocialShareMenu.jsx b/src/containers/CourseCard/components/CourseCardMenu/SocialShareMenu.jsx index 90cb8587a..65eb81143 100644 --- a/src/containers/CourseCard/components/CourseCardMenu/SocialShareMenu.jsx +++ b/src/containers/CourseCard/components/CourseCardMenu/SocialShareMenu.jsx @@ -1,30 +1,29 @@ -import React from 'react'; import PropTypes from 'prop-types'; import * as ReactShare from 'react-share'; - -import { StrictDict } from '@edx/react-unit-test-utils'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { EXECUTIVE_EDUCATION_COURSE_MODES } from '@src/data/constants/course'; +import { useIntl } from '@openedx/frontend-base'; import { Dropdown } from '@openedx/paragon'; -import track from 'tracking'; -import { reduxHooks } from 'hooks'; - +import track from '@src/tracking'; +import { useCourseTrackingEvent, useCourseData, useIsMasquerading } from '@src/hooks'; +import { useCardSocialSettingsData } from './hooks'; import messages from './messages'; -export const testIds = StrictDict({ +export const testIds = { emailSettingsModalToggle: 'emailSettingsModalToggle', -}); +}; export const SocialShareMenu = ({ cardId, emailSettings }) => { const { formatMessage } = useIntl(); + const courseData = useCourseData(cardId); + const courseName = courseData?.course?.courseName; + const isExecEd2UCourse = EXECUTIVE_EDUCATION_COURSE_MODES.includes(courseData.enrollment.mode); + const isEmailEnabled = courseData?.enrollment?.isEmailEnabled ?? false; + const { twitter, facebook } = useCardSocialSettingsData(cardId); + const isMasquerading = useIsMasquerading(); - const { courseName } = reduxHooks.useCardCourseData(cardId); - const { isEmailEnabled, isExecEd2UCourse } = reduxHooks.useCardEnrollmentData(cardId); - const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId); - const { isMasquerading } = reduxHooks.useMasqueradeData(); - - const handleTwitterShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'twitter'); - const handleFacebookShare = reduxHooks.useTrackCourseEvent(track.socialShare, cardId, 'facebook'); + const handleTwitterShare = useCourseTrackingEvent(track.socialShare, cardId, 'twitter'); + const handleFacebookShare = useCourseTrackingEvent(track.socialShare, cardId, 'facebook'); if (isExecEd2UCourse) { return null; @@ -51,6 +50,7 @@ export const SocialShareMenu = ({ cardId, emailSettings }) => { })} resetButtonStyle={false} className="pgn__dropdown-item dropdown-item" + aria-label="facebook" > {formatMessage(messages.shareToFacebook)} @@ -65,6 +65,7 @@ export const SocialShareMenu = ({ cardId, emailSettings }) => { })} resetButtonStyle={false} className="pgn__dropdown-item dropdown-item" + aria-label="twitter" > {formatMessage(messages.shareToTwitter)} diff --git a/src/containers/CourseCard/components/CourseCardMenu/SocialShareMenu.test.jsx b/src/containers/CourseCard/components/CourseCardMenu/SocialShareMenu.test.jsx index fbcb8c21e..c1c2b8eb0 100644 --- a/src/containers/CourseCard/components/CourseCardMenu/SocialShareMenu.test.jsx +++ b/src/containers/CourseCard/components/CourseCardMenu/SocialShareMenu.test.jsx @@ -1,43 +1,27 @@ import { when } from 'jest-when'; -import * as ReactShare from 'react-share'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { formatMessage, shallow } from '@edx/react-unit-test-utils'; +import { IntlProvider } from '@openedx/frontend-base'; +import { render, screen } from '@testing-library/react'; -import track from 'tracking'; -import { reduxHooks } from 'hooks'; +import track from '@src/tracking'; +import { useCourseTrackingEvent, useCourseData, useIsMasquerading } from '@src/hooks'; -import { useEmailSettings } from './hooks'; -import SocialShareMenu, { testIds } from './SocialShareMenu'; +import { useEmailSettings, useCardSocialSettingsData } from './hooks'; +import SocialShareMenu from './SocialShareMenu'; import messages from './messages'; -jest.mock('react-share', () => ({ - FacebookShareButton: () => 'FacebookShareButton', - TwitterShareButton: () => 'TwitterShareButton', -})); - -jest.mock('tracking', () => ({ +jest.mock('@src/tracking', () => ({ socialShare: 'test-social-share-key', })); -jest.mock('@edx/frontend-platform/i18n', () => ({ - ...jest.requireActual('@edx/frontend-platform/i18n'), - useIntl: jest.fn().mockReturnValue({ - formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage, - }), -})); - -jest.mock('hooks', () => ({ - reduxHooks: { - useMasqueradeData: jest.fn(), - useCardCourseData: jest.fn(), - useCardEnrollmentData: jest.fn(), - useCardSocialSettingsData: jest.fn(), - useTrackCourseEvent: jest.fn((...args) => ({ trackCourseEvent: args })), - }, +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(), + useCourseTrackingEvent: jest.fn((...args) => ({ trackCourseEvent: args })), + useIsMasquerading: jest.fn(), })); jest.mock('./hooks', () => ({ useEmailSettings: jest.fn(), + useCardSocialSettingsData: jest.fn(), })); const props = { @@ -47,9 +31,9 @@ const props = { const mockHook = (fn, returnValue, options = {}) => { if (options.isCardHook) { - when(fn).calledWith(props.cardId).mockReturnValueOnce(returnValue); + when(fn).calledWith(props.cardId).mockReturnValue(returnValue); } else { - when(fn).calledWith().mockReturnValueOnce(returnValue); + when(fn).calledWith().mockReturnValue(returnValue); } }; @@ -70,101 +54,87 @@ const socialShare = { const mockHooks = (returnVals = {}) => { mockHook( - reduxHooks.useCardEnrollmentData, + useCourseData, { - isEmailEnabled: !!returnVals.isEmailEnabled, - isExecEd2UCourse: !!returnVals.isExecEd2UCourse, + enrollment: { + isEmailEnabled: !!returnVals.isEmailEnabled, + mode: returnVals.isExecEd2UCourse ? 'exec-ed-2u' : 'standard', + }, + course: { courseName }, }, { isCardHook: true }, ); - mockHook(reduxHooks.useCardCourseData, { courseName }, { isCardHook: true }); - mockHook(reduxHooks.useMasqueradeData, { isMasquerading: !!returnVals.isMasquerading }); mockHook( - reduxHooks.useCardSocialSettingsData, + useCardSocialSettingsData, { facebook: { ...socialShare.facebook, isEnabled: !!returnVals.facebook?.isEnabled }, twitter: { ...socialShare.twitter, isEnabled: !!returnVals.twitter?.isEnabled }, }, { isCardHook: true }, ); + mockHook(useIsMasquerading, !!returnVals.isMasquerading); }; -let el; -const render = () => { - el = shallow(); -}; +const renderComponent = () => render(); describe('SocialShareMenu', () => { describe('behavior', () => { beforeEach(() => { mockHooks(); - render(); - }); - it('initializes intl hook', () => { - expect(useIntl).toHaveBeenCalledWith(); + renderComponent(); }); it('initializes local hooks', () => { when(useEmailSettings).expectCalledWith(); }); - it('initializes redux hook data ', () => { - when(reduxHooks.useCardEnrollmentData).expectCalledWith(props.cardId); - when(reduxHooks.useCardCourseData).expectCalledWith(props.cardId); - when(reduxHooks.useCardSocialSettingsData).expectCalledWith(props.cardId); - when(reduxHooks.useMasqueradeData).expectCalledWith(); - when(reduxHooks.useTrackCourseEvent).expectCalledWith(track.socialShare, props.cardId, 'twitter'); - when(reduxHooks.useTrackCourseEvent).expectCalledWith(track.socialShare, props.cardId, 'facebook'); + it('initializes hook data ', () => { + when(useCourseData).expectCalledWith(props.cardId); + when(useCardSocialSettingsData).expectCalledWith(props.cardId); + when(useIsMasquerading).expectCalledWith(); + when(useCourseTrackingEvent).expectCalledWith(track.socialShare, props.cardId, 'twitter'); + when(useCourseTrackingEvent).expectCalledWith(track.socialShare, props.cardId, 'facebook'); }); }); describe('render', () => { it('renders null if exec ed course', () => { mockHooks({ isExecEd2UCourse: true }); - render(); - expect(el.isEmptyRender()).toEqual(true); + renderComponent(); + const emailSettingsButton = screen.queryByRole('button', { name: messages.emailSettings.defaultMessage }); + expect(emailSettingsButton).toBeNull(); + const facebookShareButton = screen.queryByRole('button', { name: 'facebook' }); + expect(facebookShareButton).toBeNull(); + const twitterShareButton = screen.queryByRole('button', { name: 'twitter' }); + expect(twitterShareButton).toBeNull(); }); const testEmailSettingsDropdown = (isMasquerading = false) => { describe('email settings dropdown', () => { - const loadToggle = () => el.instance.findByTestId(testIds.emailSettingsModalToggle)[0]; it('renders', () => { - expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(1); + const emailSettingsButton = screen.getByRole('button', { name: messages.emailSettings.defaultMessage }); + expect(emailSettingsButton).toBeInTheDocument(); }); if (isMasquerading) { it('is disabled', () => { - expect(loadToggle().props.disabled).toEqual(true); + const emailSettingsButton = screen.getByRole('button', { name: messages.emailSettings.defaultMessage }); + expect(emailSettingsButton).toHaveAttribute('aria-disabled', 'true'); + expect(emailSettingsButton).toHaveClass('disabled'); }); } else { it('is enabled', () => { - expect(loadToggle().props.disabled).toEqual(false); + const emailSettingsButton = screen.getByRole('button', { name: messages.emailSettings.defaultMessage }); + expect(emailSettingsButton).toBeEnabled(); }); } - test('show email settings modal on click', () => { - expect(loadToggle().props.onClick).toEqual(props.emailSettings.show); - }); }); }; const testFacebookShareButton = () => { - test('renders facebook share button with courseName and brand', () => { - const button = el.instance.findByType(ReactShare.FacebookShareButton)[0]; - expect(button.props.url).toEqual(socialShare.facebook.shareUrl); - expect(button.props.onClick).toEqual( - reduxHooks.useTrackCourseEvent(track.socialShare, props.cardId, 'facebook'), - ); - expect(button.props.title).toEqual(formatMessage(messages.shareQuote, { - courseName, - socialBrand: socialShare.facebook.socialBrand, - })); + it('renders facebook share button', () => { + const facebookShareButton = screen.getByRole('button', { name: 'facebook' }); + expect(facebookShareButton).toBeInTheDocument(); }); }; const testTwitterShareButton = () => { - test('renders twitter share button with courseName and brand', () => { - const button = el.instance.findByType(ReactShare.TwitterShareButton)[0]; - expect(button.props.url).toEqual(socialShare.twitter.shareUrl); - expect(button.props.onClick).toEqual( - reduxHooks.useTrackCourseEvent(track.socialShare, props.cardId, 'twitter'), - ); - expect(button.props.title).toEqual(formatMessage(messages.shareQuote, { - courseName, - socialBrand: socialShare.twitter.socialBrand, - })); + it('renders twitter share button', () => { + const twitterShareButton = screen.getByRole('button', { name: 'twitter' }); + expect(twitterShareButton).toBeInTheDocument(); }); }; describe('all enabled', () => { @@ -174,19 +144,7 @@ describe('SocialShareMenu', () => { twitter: { isEnabled: true }, isEmailEnabled: true, }); - render(); - }); - describe('email settings dropdown', () => { - const loadToggle = () => el.instance.findByTestId(testIds.emailSettingsModalToggle)[0]; - it('renders', () => { - expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(1); - }); - it('is enabled', () => { - expect(loadToggle().props.disabled).toEqual(false); - }); - test('show email settings modal on click', () => { - expect(loadToggle().props.onClick).toEqual(props.emailSettings.show); - }); + renderComponent(); }); testEmailSettingsDropdown(); testFacebookShareButton(); @@ -194,42 +152,49 @@ describe('SocialShareMenu', () => { }); describe('only email enabled', () => { beforeEach(() => { + jest.clearAllMocks(); mockHooks({ isEmailEnabled: true }); - render(); + renderComponent(); }); testEmailSettingsDropdown(); it('does not render facebook or twitter controls', () => { - expect(el.instance.findByType(ReactShare.FacebookShareButton).length).toEqual(0); - expect(el.instance.findByType(ReactShare.TwitterShareButton).length).toEqual(0); + const facebookShareButton = screen.queryByRole('button', { name: 'facebook' }); + expect(facebookShareButton).toBeNull(); + const twitterShareButton = screen.queryByRole('button', { name: 'twitter' }); + expect(twitterShareButton).toBeNull(); }); - describe('masquerading', () => { - beforeEach(() => { - mockHooks({ isEmailEnabled: true, isMasquerading: true }); - render(); - }); - testEmailSettingsDropdown(true); + }); + describe('masquerading', () => { + beforeEach(() => { + mockHooks({ isEmailEnabled: true, isMasquerading: true }); + renderComponent(); }); + testEmailSettingsDropdown(true); }); describe('only facebook enabled', () => { beforeEach(() => { mockHooks({ facebook: { isEnabled: true } }); - render(); + renderComponent(); }); testFacebookShareButton(); it('does not render email or twitter controls', () => { - expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(0); - expect(el.instance.findByType(ReactShare.TwitterShareButton).length).toEqual(0); + const emailSettingsButton = screen.queryByRole('button', { name: messages.emailSettings.defaultMessage }); + expect(emailSettingsButton).toBeNull(); + const twitterShareButton = screen.queryByRole('button', { name: 'twitter' }); + expect(twitterShareButton).toBeNull(); }); }); describe('only twitter enabled', () => { beforeEach(() => { mockHooks({ twitter: { isEnabled: true } }); - render(); + renderComponent(); }); testTwitterShareButton(); it('does not render email or facebook controls', () => { - expect(el.instance.findByTestId(testIds.emailSettingsModalToggle).length).toEqual(0); - expect(el.instance.findByType(ReactShare.FacebookShareButton).length).toEqual(0); + const emailSettingsButton = screen.queryByRole('button', { name: messages.emailSettings.defaultMessage }); + expect(emailSettingsButton).toBeNull(); + const facebookShareButton = screen.queryByRole('button', { name: 'facebook' }); + expect(facebookShareButton).toBeNull(); }); }); }); diff --git a/src/containers/CourseCard/components/CourseCardMenu/__snapshots__/index.test.jsx.snap b/src/containers/CourseCard/components/CourseCardMenu/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 95d150223..000000000 --- a/src/containers/CourseCard/components/CourseCardMenu/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,81 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CourseCardMenu render show dropdown hide unenroll item and disable email snapshot 1`] = ` - - - - - - - - - -`; - -exports[`CourseCardMenu render show dropdown show unenroll and enable email snapshot 1`] = ` - - - - - - Unenroll - - - - - - - -`; diff --git a/src/containers/CourseCard/components/CourseCardMenu/hooks.js b/src/containers/CourseCard/components/CourseCardMenu/hooks.js index 2fce0363d..6c0f92447 100644 --- a/src/containers/CourseCard/components/CourseCardMenu/hooks.js +++ b/src/containers/CourseCard/components/CourseCardMenu/hooks.js @@ -1,15 +1,16 @@ -import { useKeyedState, StrictDict } from '@edx/react-unit-test-utils'; +import track from '@src/tracking'; +import { useCourseData, useCourseTrackingEvent } from '@src/hooks'; +import { useState } from 'react'; +import { StrictDict } from '@src/utils'; +import { useInitializeLearnerHome } from '@src/data/hooks'; -import track from 'tracking'; -import { reduxHooks } from 'hooks'; - -export const stateKeys = StrictDict({ - isUnenrollConfirmVisible: 'isUnenrollConfirmVisible', - isEmailSettingsVisible: 'isEmailSettingsVisible', +export const state = StrictDict({ + isUnenrollConfirmVisible: (val) => useState(val), // eslint-disable-line + isEmailSettingsVisible: (val) => useState(val), // eslint-disable-line }); export const useUnenrollData = () => { - const [isVisible, setIsVisible] = useKeyedState(stateKeys.isUnenrollConfirmVisible, false); + const [isVisible, setIsVisible] = state.isUnenrollConfirmVisible(false); return { show: () => setIsVisible(true), hide: () => setIsVisible(false), @@ -18,7 +19,7 @@ export const useUnenrollData = () => { }; export const useEmailSettings = () => { - const [isVisible, setIsVisible] = useKeyedState(stateKeys.isEmailSettingsVisible, false); + const [isVisible, setIsVisible] = state.isEmailSettingsVisible(false); return { show: () => setIsVisible(true), hide: () => setIsVisible(false), @@ -27,19 +28,41 @@ export const useEmailSettings = () => { }; export const useHandleToggleDropdown = (cardId) => { - const trackCourseEvent = reduxHooks.useTrackCourseEvent( + const trackCourseEvent = useCourseTrackingEvent( track.course.courseOptionsDropdownClicked, cardId, ); return (isOpen) => { - if (isOpen) { trackCourseEvent(); } + if (isOpen) { + trackCourseEvent(); + } }; }; +export const useCardSocialSettingsData = (cardId) => { + const { data: learnerHomeData } = useInitializeLearnerHome(); + const courseData = useCourseData(cardId); + const socialShareSettings = learnerHomeData?.socialShareSettings; + const { socialShareUrl } = courseData?.course || {}; + const defaultSettings = { isEnabled: false, shareUrl: '' }; + + if (!socialShareSettings) { + return { facebook: defaultSettings, twitter: defaultSettings }; + } + const { facebook, twitter } = socialShareSettings; + const loadSettings = (target) => ({ + isEnabled: target.isEnabled, + shareUrl: `${socialShareUrl}?${target.utmParams}`, + }); + return { facebook: loadSettings(facebook), twitter: loadSettings(twitter) }; +}; + export const useOptionVisibility = (cardId) => { - const { isEnrolled, isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId); - const { twitter, facebook } = reduxHooks.useCardSocialSettingsData(cardId); - const { isEarned } = reduxHooks.useCardCertificateData(cardId); + const courseData = useCourseData(cardId); + const isEmailEnabled = courseData?.enrollment?.isEmailEnabled ?? false; + const isEnrolled = courseData?.enrollment?.isEnrolled ?? false; + const { twitter, facebook } = useCardSocialSettingsData(cardId); + const isEarned = courseData?.certificate?.isEarned ?? false; const shouldShowUnenrollItem = isEnrolled && !isEarned; const shouldShowDropdown = ( diff --git a/src/containers/CourseCard/components/CourseCardMenu/hooks.test.js b/src/containers/CourseCard/components/CourseCardMenu/hooks.test.js index a01a877a6..c8145f4cd 100644 --- a/src/containers/CourseCard/components/CourseCardMenu/hooks.test.js +++ b/src/containers/CourseCard/components/CourseCardMenu/hooks.test.js @@ -1,25 +1,25 @@ -import { mockUseKeyedState } from '@edx/react-unit-test-utils'; - -import { reduxHooks } from 'hooks'; -import track from 'tracking'; +import { useCourseData, useCourseTrackingEvent } from '@src/hooks'; +import { useInitializeLearnerHome } from '@src/data/hooks'; +import track from '@src/tracking'; +import { MockUseState } from '@src/testUtils'; import * as hooks from './hooks'; -jest.mock('hooks', () => ({ - reduxHooks: { - useCardCertificateData: jest.fn(), - useCardEnrollmentData: jest.fn(), - useCardSocialSettingsData: jest.fn(), - useTrackCourseEvent: jest.fn(), - }, +jest.mock('@src/data/hooks', () => ({ + useInitializeLearnerHome: jest.fn(), +})); + +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(), + useCourseTrackingEvent: jest.fn(), })); const trackCourseEvent = jest.fn(); -reduxHooks.useTrackCourseEvent.mockReturnValue(trackCourseEvent); +useCourseTrackingEvent.mockReturnValue(trackCourseEvent); const cardId = 'test-card-id'; let out; -const state = mockUseKeyedState(hooks.stateKeys); +const state = new MockUseState(hooks); describe('CourseCardMenu hooks', () => { beforeEach(() => { @@ -28,7 +28,6 @@ describe('CourseCardMenu hooks', () => { }); describe('useUnenrollData', () => { beforeEach(() => { - state.mockVals({ isUnenrollConfirmVisible: true }); out = hooks.useUnenrollData(); }); describe('behavior', () => { @@ -37,9 +36,6 @@ describe('CourseCardMenu hooks', () => { }); }); describe('output', () => { - test('state is loaded from current state value', () => { - expect(out.isVisible).toEqual(true); - }); test('show sets state value to true', () => { out.show(); expect(state.setState.isUnenrollConfirmVisible).toHaveBeenCalledWith(true); @@ -53,7 +49,6 @@ describe('CourseCardMenu hooks', () => { describe('useEmailSettings', () => { beforeEach(() => { - state.mockVals({ isEmailSettingsVisible: true }); out = hooks.useEmailSettings(); }); describe('behavior', () => { @@ -62,9 +57,6 @@ describe('CourseCardMenu hooks', () => { }); }); describe('output', () => { - test('state is loaded from current state value', () => { - expect(out.isVisible).toEqual(state.values.isEmailSettingsVisible); - }); test('show sets state value to true', () => { out.show(); expect(state.setState.isEmailSettingsVisible).toHaveBeenCalledWith(true); @@ -77,10 +69,12 @@ describe('CourseCardMenu hooks', () => { }); describe('useHandleToggleDropdown', () => { - beforeEach(() => { out = hooks.useHandleToggleDropdown(cardId); }); + beforeEach(() => { + out = hooks.useHandleToggleDropdown(cardId); + }); describe('behavior', () => { it('initializes course event tracker with event name and card ID', () => { - expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith( + expect(useCourseTrackingEvent).toHaveBeenCalledWith( track.course.courseOptionsDropdownClicked, cardId, ); @@ -97,55 +91,61 @@ describe('CourseCardMenu hooks', () => { }); describe('useOptionVisibility', () => { - const mockReduxHooks = (returnVals = {}) => { - reduxHooks.useCardSocialSettingsData.mockReturnValueOnce({ - facebook: { isEnabled: !!returnVals.facebook?.isEnabled }, - twitter: { isEnabled: !!returnVals.twitter?.isEnabled }, - }); - reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ - isEnrolled: !!returnVals.isEnrolled, - isEmailEnabled: !!returnVals.isEmailEnabled, - }); - reduxHooks.useCardCertificateData.mockReturnValueOnce({ - isEarned: !!returnVals.isEarned, + const mockHooks = (returnVals = {}) => { + useInitializeLearnerHome.mockReturnValue({ + data: { + socialShareSettings: { + facebook: { isEnabled: !!returnVals.facebook?.isEnabled }, + twitter: { isEnabled: !!returnVals.twitter?.isEnabled }, + }, + }, + }); + useCourseData.mockReturnValue({ + enrollment: { + isEnrolled: !!returnVals.isEnrolled, + isEmailEnabled: !!returnVals.isEmailEnabled, + }, + certificate: { + isEarned: !!returnVals.isEarned, + }, }); }; describe('shouldShowUnenrollItem', () => { it('returns true if enrolled and not earned', () => { - mockReduxHooks({ isEnrolled: true }); + mockHooks({ isEnrolled: true }); expect(hooks.useOptionVisibility(cardId).shouldShowUnenrollItem).toEqual(true); }); it('returns false if not enrolled', () => { - mockReduxHooks(); + mockHooks(); expect(hooks.useOptionVisibility(cardId).shouldShowUnenrollItem).toEqual(false); }); it('returns false if enrolled but also earned', () => { - mockReduxHooks({ isEarned: true }); + mockHooks({ isEarned: true }); expect(hooks.useOptionVisibility(cardId).shouldShowUnenrollItem).toEqual(false); }); }); describe('shouldShowDropdown', () => { it('returns false if not enrolled and both email and socials are disabled', () => { - mockReduxHooks(); + mockHooks(); expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(false); }); it('returns false if enrolled but already earned, and both email and socials are disabled', () => { - mockReduxHooks({ isEnrolled: true, isEarned: true }); + mockHooks({ isEnrolled: true, isEarned: true }); expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(false); }); it('returns true if either social is enabled', () => { - mockReduxHooks({ facebook: { isEnabled: true } }); + mockHooks({ facebook: { isEnabled: true } }); expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true); - mockReduxHooks({ twitter: { isEnabled: true } }); + mockHooks({ twitter: { isEnabled: true } }); expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true); }); it('returns true if email is enabled', () => { - mockReduxHooks({ isEmailEnabled: true }); + mockHooks({ isEmailEnabled: true }); expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true); }); it('returns true if enrolled and not earned', () => { - mockReduxHooks({ isEnrolled: true }); + mockHooks({ isEnrolled: true }); expect(hooks.useOptionVisibility(cardId).shouldShowDropdown).toEqual(true); }); }); diff --git a/src/containers/CourseCard/components/CourseCardMenu/index.jsx b/src/containers/CourseCard/components/CourseCardMenu/index.jsx index b8687a529..29d43f42d 100644 --- a/src/containers/CourseCard/components/CourseCardMenu/index.jsx +++ b/src/containers/CourseCard/components/CourseCardMenu/index.jsx @@ -1,14 +1,12 @@ -import React from 'react'; import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import { Dropdown, Icon, IconButton } from '@openedx/paragon'; import { MoreVert } from '@openedx/paragon/icons'; -import { StrictDict } from '@edx/react-unit-test-utils'; -import EmailSettingsModal from 'containers/EmailSettingsModal'; -import UnenrollConfirmModal from 'containers/UnenrollConfirmModal'; -import { reduxHooks } from 'hooks'; +import EmailSettingsModal from '@src/containers/EmailSettingsModal'; +import UnenrollConfirmModal from '@src/containers/UnenrollConfirmModal'; +import { useCourseData, useIsMasquerading } from '@src/hooks'; import SocialShareMenu from './SocialShareMenu'; import { useEmailSettings, @@ -19,19 +17,21 @@ import { import messages from './messages'; -export const testIds = StrictDict({ +export const testIds = { unenrollModalToggle: 'unenrollModalToggle', -}); +}; export const CourseCardMenu = ({ cardId }) => { const { formatMessage } = useIntl(); + const courseData = useCourseData(cardId); + + const isEmailEnabled = courseData?.enrollment?.isEmailEnabled ?? false; const emailSettings = useEmailSettings(); const unenrollModal = useUnenrollData(); const handleToggleDropdown = useHandleToggleDropdown(cardId); const { shouldShowUnenrollItem, shouldShowDropdown } = useOptionVisibility(cardId); - const { isMasquerading } = reduxHooks.useMasqueradeData(); - const { isEmailEnabled } = reduxHooks.useCardEnrollmentData(cardId); + const isMasquerading = useIsMasquerading(); if (!shouldShowDropdown) { return null; diff --git a/src/containers/CourseCard/components/CourseCardMenu/index.test.jsx b/src/containers/CourseCard/components/CourseCardMenu/index.test.jsx index 2677eb239..c0405da53 100644 --- a/src/containers/CourseCard/components/CourseCardMenu/index.test.jsx +++ b/src/containers/CourseCard/components/CourseCardMenu/index.test.jsx @@ -1,26 +1,21 @@ import { when } from 'jest-when'; -import { Dropdown } from '@openedx/paragon'; -import { shallow } from '@edx/react-unit-test-utils'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { IntlProvider } from '@openedx/frontend-base'; -import EmailSettingsModal from 'containers/EmailSettingsModal'; -import UnenrollConfirmModal from 'containers/UnenrollConfirmModal'; -import { reduxHooks } from 'hooks'; -import SocialShareMenu from './SocialShareMenu'; +import { useCourseData, useIsMasquerading } from '@src/hooks'; import * as hooks from './hooks'; -import CourseCardMenu, { testIds } from '.'; +import CourseCardMenu from '.'; +import messages from './messages'; -jest.mock('@edx/frontend-platform/i18n', () => ({ - ...jest.requireActual('@edx/frontend-platform/i18n'), - useIntl: jest.fn().mockReturnValue({ - formatMessage: jest.requireActual('@edx/react-unit-test-utils').formatMessage, - }), +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(), + useIsMasquerading: jest.fn(), })); -jest.mock('hooks', () => ({ - reduxHooks: { useMasqueradeData: jest.fn(), useCardEnrollmentData: jest.fn() }, -})); -jest.mock('./SocialShareMenu', () => 'SocialShareMenu'); +jest.mock('./SocialShareMenu', () => jest.fn(() =>
SocialShareMenu
)); +jest.mock('@src/containers/EmailSettingsModal', () => jest.fn(() =>
EmailSettingsModal
)); +jest.mock('@src/containers/UnenrollConfirmModal', () => jest.fn(() =>
UnenrollConfirmModal
)); jest.mock('./hooks', () => ({ useEmailSettings: jest.fn(), useUnenrollData: jest.fn(), @@ -44,13 +39,11 @@ const unenrollData = { hide: jest.fn().mockName('unenrollHide'), }; -let el; - const mockHook = (fn, returnValue, options = {}) => { if (options.isCardHook) { - when(fn).calledWith(props.cardId).mockReturnValueOnce(returnValue); + when(fn).calledWith(props.cardId).mockReturnValue(returnValue); } else { - when(fn).calledWith().mockReturnValueOnce(returnValue); + when(fn).calledWith().mockReturnValue(returnValue); } }; @@ -74,82 +67,61 @@ const mockHooks = (returnVals = {}) => { }, { isCardHook: true }, ); - mockHook(reduxHooks.useMasqueradeData, { isMasquerading: !!returnVals.isMasquerading }); + mockHook(useIsMasquerading, !!returnVals.isMasquerading); mockHook( - reduxHooks.useCardEnrollmentData, - { isEmailEnabled: !!returnVals.isEmailEnabled }, + useCourseData, + { + enrollment: { + isEmailEnabled: !!returnVals.isEmailEnabled, + }, + }, { isCardHook: true }, ); }; -const render = () => { - el = shallow(); -}; +const renderComponent = () => render(); describe('CourseCardMenu', () => { - describe('behavior', () => { + describe('hooks', () => { beforeEach(() => { mockHooks(); - render(); - }); - it('initializes intl hook', () => { - expect(useIntl).toHaveBeenCalledWith(); + renderComponent(); }); it('initializes local hooks', () => { when(hooks.useEmailSettings).expectCalledWith(); - when(hooks.useUnenrollData).expectCalledWith(); - when(hooks.useHandleToggleDropdown).expectCalledWith(props.cardId); - when(hooks.useOptionVisibility).expectCalledWith(props.cardId); }); - it('initializes redux hook data ', () => { - when(reduxHooks.useMasqueradeData).expectCalledWith(); - when(reduxHooks.useCardEnrollmentData).expectCalledWith(props.cardId); + it('initializes hook data ', () => { + when(useIsMasquerading).expectCalledWith(); + when(useCourseData).expectCalledWith(props.cardId); }); }); describe('render', () => { it('renders null if showDropdown is false', () => { mockHooks(); - render(); - expect(el.isEmptyRender()).toEqual(true); + renderComponent(); + const dropdown = screen.queryByRole('button', { name: messages.dropdownAlt.defaultMessage }); + expect(dropdown).toBeNull(); }); - const testHandleToggle = () => { - it('displays Dropdown with onToggle=handleToggleDropdown', () => { - expect(el.instance.findByType(Dropdown)[0].props.onToggle).toEqual(handleToggleDropdown); - }); - }; - const testUnenrollConfirmModal = () => { - it('displays UnenrollConfirmModal with cardId and unenrollModal data', () => { - const modal = el.instance.findByType(UnenrollConfirmModal)[0]; - expect(modal.props.show).toEqual(unenrollData.isVisible); - expect(modal.props.closeModal).toEqual(unenrollData.hide); - expect(modal.props.cardId).toEqual(props.cardId); - }); - }; - const testSocialShareMenu = () => { - it('displays SocialShareMenu with cardID and emailSettings', () => { - const menu = el.instance.findByType(SocialShareMenu)[0]; - expect(menu.props.cardId).toEqual(props.cardId); - expect(menu.props.emailSettings).toEqual(emailSettings); - }); - }; describe('show dropdown', () => { describe('hide unenroll item and disable email', () => { - beforeEach(() => { - mockHooks({ shouldShowDropdown: true }); - render(); - }); - test('snapshot', () => { - expect(el.snapshot).toMatchSnapshot(); - }); - testHandleToggle(); - testSocialShareMenu(); - it('does not render unenroll modal toggle', () => { - expect(el.instance.findByTestId(testIds.unenrollModalToggle).length).toEqual(0); - }); - it('does not render EmailSettingsModal', () => { - expect(el.instance.findByType(EmailSettingsModal).length).toEqual(0); + it('displays Dropdown and renders SocialShareMenu and UnenrollConfirmModal', async () => { + mockHooks({ + shouldShowDropdown: true, + }); + renderComponent(); + const user = userEvent.setup(); + const dropdown = screen.getByRole('button', { name: messages.dropdownAlt.defaultMessage }); + expect(dropdown).toBeInTheDocument(); + await user.click(dropdown); + const unenrollOption = screen.queryByRole('button', { name: messages.unenroll.defaultMessage }); + expect(unenrollOption).toBeNull(); + const socialShareMenu = screen.getByText('SocialShareMenu'); + expect(socialShareMenu).toBeInTheDocument(); + const unenrollConfirmModal = screen.getByText('UnenrollConfirmModal'); + expect(unenrollConfirmModal).toBeInTheDocument(); + const emailSettingsModal = screen.queryByText('EmailSettingsModal'); + expect(emailSettingsModal).toBeNull(); }); - testUnenrollConfirmModal(); }); describe('show unenroll and enable email', () => { const hookProps = { @@ -157,57 +129,49 @@ describe('CourseCardMenu', () => { isEmailEnabled: true, shouldShowUnenrollItem: true, }; - beforeEach(() => { - mockHooks(hookProps); - render(); - }); - test('snapshot', () => { - expect(el.snapshot).toMatchSnapshot(); - }); - testHandleToggle(); - testSocialShareMenu(); describe('unenroll modal toggle', () => { - let toggle; describe('not masquerading', () => { - beforeEach(() => { + it('renders all components', async () => { mockHooks(hookProps); - render(); - [toggle] = el.instance.findByTestId(testIds.unenrollModalToggle); - }); - it('renders unenroll modal toggle', () => { - expect(el.instance.findByTestId(testIds.unenrollModalToggle).length).toEqual(1); - }); - test('onClick from unenroll modal hook', () => { - expect(toggle.props.onClick).toEqual(unenrollData.show); - }); - test('disabled', () => { - expect(toggle.props.disabled).toEqual(false); + renderComponent(); + + const user = userEvent.setup(); + const dropdown = screen.getByRole('button', { name: messages.dropdownAlt.defaultMessage }); + expect(dropdown).toBeInTheDocument(); + await user.click(dropdown); + + const unenrollOption = screen.getByRole('button', { name: messages.unenroll.defaultMessage }); + expect(unenrollOption).toBeInTheDocument(); + const socialShareMenu = screen.getByText('SocialShareMenu'); + expect(socialShareMenu).toBeInTheDocument(); + const unenrollConfirmModal = screen.getByText('UnenrollConfirmModal'); + expect(unenrollConfirmModal).toBeInTheDocument(); + const emailSettingsModal = screen.getByText('EmailSettingsModal'); + expect(emailSettingsModal).toBeInTheDocument(); }); }); describe('masquerading', () => { - beforeEach(() => { + it('renders but unenroll is disabled', async () => { mockHooks({ ...hookProps, isMasquerading: true }); - render(); - [toggle] = el.instance.findByTestId(testIds.unenrollModalToggle); - }); - it('renders', () => { - expect(el.instance.findByTestId(testIds.unenrollModalToggle).length).toEqual(1); - }); - test('onClick from unenroll modal hook', () => { - expect(toggle.props.onClick).toEqual(unenrollData.show); - }); - test('disabled', () => { - expect(toggle.props.disabled).toEqual(true); + renderComponent(); + + const user = userEvent.setup(); + const dropdown = screen.getByRole('button', { name: messages.dropdownAlt.defaultMessage }); + expect(dropdown).toBeInTheDocument(); + await user.click(dropdown); + + const unenrollOption = screen.getByRole('button', { name: messages.unenroll.defaultMessage }); + expect(unenrollOption).toBeInTheDocument(); + expect(unenrollOption).toHaveAttribute('aria-disabled', 'true'); + const socialShareMenu = screen.getByText('SocialShareMenu'); + expect(socialShareMenu).toBeInTheDocument(); + const unenrollConfirmModal = screen.getByText('UnenrollConfirmModal'); + expect(unenrollConfirmModal).toBeInTheDocument(); + const emailSettingsModal = screen.getByText('EmailSettingsModal'); + expect(emailSettingsModal).toBeInTheDocument(); }); }); }); - testUnenrollConfirmModal(); - it('displays EmaiSettingsModal with cardId and emailSettingsModal data', () => { - const modal = el.instance.findByType(EmailSettingsModal)[0]; - expect(modal.props.show).toEqual(emailSettings.isVisible); - expect(modal.props.closeModal).toEqual(emailSettings.hide); - expect(modal.props.cardId).toEqual(props.cardId); - }); }); }); }); diff --git a/src/containers/CourseCard/components/CourseCardMenu/messages.js b/src/containers/CourseCard/components/CourseCardMenu/messages.js index d01a6bcd1..019de033f 100644 --- a/src/containers/CourseCard/components/CourseCardMenu/messages.js +++ b/src/containers/CourseCard/components/CourseCardMenu/messages.js @@ -1,4 +1,4 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ unenroll: { diff --git a/src/containers/CourseCard/components/CourseCardTitle.jsx b/src/containers/CourseCard/components/CourseCardTitle.jsx index 5d7bc7c21..475551062 100644 --- a/src/containers/CourseCard/components/CourseCardTitle.jsx +++ b/src/containers/CourseCard/components/CourseCardTitle.jsx @@ -1,16 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; -import track from 'tracking'; -import { reduxHooks } from 'hooks'; +import track from '@src/tracking'; +import { useCourseData, useCourseTrackingEvent } from '@src/hooks'; import useActionDisabledState from './hooks'; const { courseTitleClicked } = track.course; export const CourseCardTitle = ({ cardId }) => { - const { courseName } = reduxHooks.useCardCourseData(cardId); - const { homeUrl } = reduxHooks.useCardCourseRunData(cardId); - const handleTitleClicked = reduxHooks.useTrackCourseEvent( + const courseData = useCourseData(cardId); + const courseName = courseData?.course?.courseName; + const homeUrl = courseData?.courseRun?.homeUrl; + const handleTitleClicked = useCourseTrackingEvent( courseTitleClicked, cardId, homeUrl, diff --git a/src/containers/CourseCard/components/CourseCardTitle.test.jsx b/src/containers/CourseCard/components/CourseCardTitle.test.jsx index aa3dcdd30..62086f5fa 100644 --- a/src/containers/CourseCard/components/CourseCardTitle.test.jsx +++ b/src/containers/CourseCard/components/CourseCardTitle.test.jsx @@ -1,67 +1,71 @@ -import { shallow } from '@edx/react-unit-test-utils'; - -import { reduxHooks } from 'hooks'; -import track from 'tracking'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { useCourseData, useCourseTrackingEvent } from '@src/hooks'; +import track from '@src/tracking'; import useActionDisabledState from './hooks'; import CourseCardTitle from './CourseCardTitle'; -const homeUrl = 'home-url'; - -jest.mock('tracking', () => ({ +jest.mock('@src/tracking', () => ({ course: { courseTitleClicked: jest.fn().mockName('segment.courseTitleClicked'), }, })); -jest.mock('hooks', () => ({ - reduxHooks: { - useCardCourseData: jest.fn(() => ({ courseName: 'course-name' })), - useCardCourseRunData: jest.fn(() => ({ homeUrl })), - useTrackCourseEvent: jest.fn((eventName, cardId, url) => ({ - trackCourseEvent: { eventName, cardId, url }, - })), - }, +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(), + useCourseTrackingEvent: jest.fn(), })); + jest.mock('./hooks', () => jest.fn(() => ({ disableCourseTitle: false }))); describe('CourseCardTitle', () => { const props = { - cardId: 'cardId', + cardId: 'test-card-id', }; + + const courseName = 'Test Course'; + const homeUrl = 'http://test.com'; + const handleTitleClick = jest.fn(); + beforeEach(() => { jest.clearAllMocks(); - }); - describe('snapshot', () => { - test('renders clickable link course title', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); - const title = wrapper.instance.findByTestId('CourseCardTitle'); - expect(title[0].type).toBe('a'); - expect(title[0].props.onClick).toEqual( - reduxHooks.useTrackCourseEvent( - track.course.courseTitleClicked, - props.cardId, - homeUrl, - ), - ); - }); - test('renders disabled link', () => { - useActionDisabledState.mockReturnValueOnce({ disableCourseTitle: true }); - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); - const title = wrapper.instance.findByTestId('CourseCardTitle'); - expect(title[0].type).toBe('span'); - expect(title[0].props.onClick).toBeUndefined(); + useCourseData.mockReturnValue({ + course: { courseName }, + courseRun: { homeUrl }, }); + useCourseTrackingEvent.mockReturnValue(handleTitleClick); }); - describe('behavior', () => { - it('initializes', () => { - shallow(); - expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(props.cardId); - expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith( - props.cardId, - ); - expect(useActionDisabledState).toHaveBeenCalledWith(props.cardId); - }); + + it('renders course name as link when not disabled', async () => { + useActionDisabledState.mockReturnValue({ disableCourseTitle: false }); + render(); + + const user = userEvent.setup(); + const link = screen.getByRole('link', { name: courseName }); + expect(link).toHaveAttribute('href', homeUrl); + + await user.click(link); + expect(handleTitleClick).toHaveBeenCalled(); + }); + + it('renders course name as span when disabled', () => { + useActionDisabledState.mockReturnValue({ disableCourseTitle: true }); + render(); + + const text = screen.getByText(courseName); + expect(text).toBeInTheDocument(); + expect(text.tagName.toLowerCase()).toBe('span'); + }); + + it('uses correct hooks with cardId', () => { + useActionDisabledState.mockReturnValue({ disableCourseTitle: false }); + render(); + + expect(useCourseData).toHaveBeenCalledWith(props.cardId); + expect(useCourseTrackingEvent).toHaveBeenCalledWith( + track.course.courseTitleClicked, + props.cardId, + homeUrl, + ); }); }); diff --git a/src/containers/CourseCard/components/RelatedProgramsBadge/__snapshots__/index.test.jsx.snap b/src/containers/CourseCard/components/RelatedProgramsBadge/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 3b566299f..000000000 --- a/src/containers/CourseCard/components/RelatedProgramsBadge/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RelatedProgramsBadge component snapshot: 3 programs 1`] = ` - - - - -`; diff --git a/src/containers/CourseCard/components/RelatedProgramsBadge/hooks.jsx b/src/containers/CourseCard/components/RelatedProgramsBadge/hooks.jsx index 2b03dacdb..9b988e359 100644 --- a/src/containers/CourseCard/components/RelatedProgramsBadge/hooks.jsx +++ b/src/containers/CourseCard/components/RelatedProgramsBadge/hooks.jsx @@ -1,8 +1,8 @@ import React from 'react'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; -import { StrictDict } from 'utils'; -import { reduxHooks } from 'hooks'; +import { StrictDict } from '@src/utils'; +import { useCourseData } from '@src/hooks'; import messages from './messages'; import * as module from './hooks'; @@ -14,7 +14,8 @@ export const state = StrictDict({ export const useRelatedProgramsBadgeData = ({ cardId }) => { const [isOpen, setIsOpen] = module.state.isOpen(false); const { formatMessage } = useIntl(); - const numPrograms = reduxHooks.useCardRelatedProgramsData(cardId).length; + const courseData = useCourseData(cardId); + const numPrograms = courseData?.programs?.relatedPrograms?.length || 0; let programsMessage = ''; if (numPrograms) { programsMessage = formatMessage( diff --git a/src/containers/CourseCard/components/RelatedProgramsBadge/hooks.test.js b/src/containers/CourseCard/components/RelatedProgramsBadge/hooks.test.js index 2926da1dc..cd170b27b 100644 --- a/src/containers/CourseCard/components/RelatedProgramsBadge/hooks.test.js +++ b/src/containers/CourseCard/components/RelatedProgramsBadge/hooks.test.js @@ -1,17 +1,25 @@ -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; -import { MockUseState } from 'testUtils'; -import { reduxHooks } from 'hooks'; +import { MockUseState } from '@src/testUtils'; +import { useCourseData } from '@src/hooks'; import * as hooks from './hooks'; import messages from './messages'; -jest.mock('hooks', () => ({ - reduxHooks: { - useCardRelatedProgramsData: jest.fn(), - }, +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(), })); +jest.mock('@openedx/frontend-base', () => { + const { formatMessage } = jest.requireActual('@src/testUtils'); + return { + ...jest.requireActual('@openedx/frontend-base'), + useIntl: () => ({ + formatMessage, + }), + }; +}); + const cardId = 'test-card-id'; const state = new MockUseState(hooks); @@ -29,8 +37,10 @@ describe('RelatedProgramsBadge hooks', () => { describe('useRelatedProgramsBadgeData', () => { beforeEach(() => { state.mock(); - reduxHooks.useCardRelatedProgramsData.mockReturnValueOnce({ - length: numPrograms, + useCourseData.mockReturnValue({ + programs: { + relatedPrograms: new Array(numPrograms).fill({}), + }, }); out = hooks.useRelatedProgramsBadgeData({ cardId }); }); @@ -54,12 +64,12 @@ describe('RelatedProgramsBadge hooks', () => { expect(out.numPrograms).toEqual(numPrograms); }); test('returns empty programsMessage if no programs', () => { - reduxHooks.useCardRelatedProgramsData.mockReturnValueOnce({ length: 0 }); + useCourseData.mockReturnValueOnce({ programs: { relatedPrograms: [] } }); out = hooks.useRelatedProgramsBadgeData({ cardId }); expect(out.programsMessage).toEqual(''); }); test('returns badgeLabelSingular programsMessage if 1 programs', () => { - reduxHooks.useCardRelatedProgramsData.mockReturnValueOnce({ length: 1 }); + useCourseData.mockReturnValueOnce({ programs: { relatedPrograms: [{}] } }); out = hooks.useRelatedProgramsBadgeData({ cardId }); expect(out.programsMessage).toEqual(formatMessage( messages.badgeLabelSingular, diff --git a/src/containers/CourseCard/components/RelatedProgramsBadge/index.jsx b/src/containers/CourseCard/components/RelatedProgramsBadge/index.jsx index ae1aaa7f1..21f02d653 100644 --- a/src/containers/CourseCard/components/RelatedProgramsBadge/index.jsx +++ b/src/containers/CourseCard/components/RelatedProgramsBadge/index.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import { Button, Icon } from '@openedx/paragon'; import { Program } from '@openedx/paragon/icons'; -import RelatedProgramsBadgeModal from 'containers/RelatedProgramsModal'; +import RelatedProgramsBadgeModal from '@src/containers/RelatedProgramsModal'; import useRelatedProgramsBadgeData from './hooks'; export const RelatedProgramsBadge = ({ cardId }) => { diff --git a/src/containers/CourseCard/components/RelatedProgramsBadge/index.test.jsx b/src/containers/CourseCard/components/RelatedProgramsBadge/index.test.jsx index 09e3b9fe8..e15d98bce 100644 --- a/src/containers/CourseCard/components/RelatedProgramsBadge/index.test.jsx +++ b/src/containers/CourseCard/components/RelatedProgramsBadge/index.test.jsx @@ -1,10 +1,9 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; +import RelatedProgramsBadge from '../RelatedProgramsBadge'; import useRelatedProgramsBadge from './hooks'; -import RelatedProgramsBadge from '.'; -jest.mock('containers/RelatedProgramsModal', () => 'RelatedProgramsModal'); +jest.mock('@src/containers/RelatedProgramsModal', () => 'RelatedProgramsModal'); jest.mock('./hooks', () => jest.fn()); const hookProps = { @@ -13,18 +12,23 @@ const hookProps = { closeModal: jest.fn().mockName('useRelatedProgramsBadge.closeModal'), numPrograms: 3, programsMessage: 'useRelatedProgramsBadge.programsMessage', -}; +} -const cardId = 'test-course-number'; +const cardId = 'test-card-id'; describe('RelatedProgramsBadge component', () => { - test('empty render: no programs', () => { + it('should not render if no programs', () => { useRelatedProgramsBadge.mockReturnValueOnce({ ...hookProps, numPrograms: 0 }); - const el = shallow(); - expect(el.isEmptyRender()).toEqual(true); + render(); + const button = screen.queryByRole('button', { name: hookProps.programsMessage }); + expect(button).toBeNull(); + const dialog = screen.queryByRole('dialog'); + expect(dialog).toBeNull(); }); - test('snapshot: 3 programs', () => { - useRelatedProgramsBadge.mockReturnValueOnce(hookProps); - expect(shallow().snapshot).toMatchSnapshot(); + it('3 programs closed', () => { + useRelatedProgramsBadge.mockReturnValue({ ...hookProps, isOpen: false }); + render(); + const button = screen.getByRole('button', { name: hookProps.programsMessage }); + expect(button).toBeInTheDocument(); }); }); diff --git a/src/containers/CourseCard/components/__snapshots__/CourseCardImage.test.jsx.snap b/src/containers/CourseCard/components/__snapshots__/CourseCardImage.test.jsx.snap deleted file mode 100644 index 964a146fc..000000000 --- a/src/containers/CourseCard/components/__snapshots__/CourseCardImage.test.jsx.snap +++ /dev/null @@ -1,72 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CourseCardImage snapshot renders clickable link course Image 1`] = ` - - - Course thumbnail - - - Verified - - ID Verified Ribbon/Badge - - - -`; - -exports[`CourseCardImage snapshot renders disabled link 1`] = ` -
- - Course thumbnail - - - Verified - - ID Verified Ribbon/Badge - - -
-`; diff --git a/src/containers/CourseCard/components/__snapshots__/CourseCardTitle.test.jsx.snap b/src/containers/CourseCard/components/__snapshots__/CourseCardTitle.test.jsx.snap deleted file mode 100644 index 34446ade0..000000000 --- a/src/containers/CourseCard/components/__snapshots__/CourseCardTitle.test.jsx.snap +++ /dev/null @@ -1,33 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CourseCardTitle snapshot renders clickable link course title 1`] = ` -

- - course-name - -

-`; - -exports[`CourseCardTitle snapshot renders disabled link 1`] = ` -

- - course-name - -

-`; diff --git a/src/containers/CourseCard/components/hooks.js b/src/containers/CourseCard/components/hooks.js index 9d80c0ae9..d108b82cf 100644 --- a/src/containers/CourseCard/components/hooks.js +++ b/src/containers/CourseCard/components/hooks.js @@ -1,16 +1,19 @@ -import { reduxHooks } from 'hooks'; +import { useCourseData, useEntitlementInfo, useIsMasquerading } from '@src/hooks'; export const useActionDisabledState = (cardId) => { - const { isMasquerading } = reduxHooks.useMasqueradeData(); + const courseData = useCourseData(cardId); + const isMasquerading = useIsMasquerading(); + const { - hasAccess, isAudit, isAuditAccessExpired, - } = reduxHooks.useCardEnrollmentData(cardId); + isAudit, isAuditAccessExpired, + } = courseData.enrollment || {}; + const { isStaff, hasUnmetPrereqs, isTooEarly } = courseData.enrollment?.coursewareAccess || {}; + const hasAccess = isStaff || !(hasUnmetPrereqs || isTooEarly); const { isEntitlement, isFulfilled, canChange, hasSessions, - } = reduxHooks.useCardEntitlementData(cardId); - - const { resumeUrl, homeUrl } = reduxHooks.useCardCourseRunData(cardId); + } = useEntitlementInfo(courseData); + const { resumeUrl, homeUrl } = courseData.courseRun || {}; const disableBeginCourse = !homeUrl || (isMasquerading || !hasAccess || (isAudit && isAuditAccessExpired)); const disableResumeCourse = !resumeUrl || (isMasquerading || !hasAccess || (isAudit && isAuditAccessExpired)); const disableViewCourse = !hasAccess || (isAudit && isAuditAccessExpired); diff --git a/src/containers/CourseCard/components/hooks.test.js b/src/containers/CourseCard/components/hooks.test.js index 50d2ccc25..7f02f172e 100644 --- a/src/containers/CourseCard/components/hooks.test.js +++ b/src/containers/CourseCard/components/hooks.test.js @@ -1,14 +1,15 @@ -import { reduxHooks } from 'hooks'; - +import { useCourseData, useIsMasquerading } from '@src/hooks'; import * as hooks from './hooks'; -jest.mock('hooks', () => ({ - reduxHooks: { - useMasqueradeData: jest.fn(), - useCardEnrollmentData: jest.fn(), - useCardEntitlementData: jest.fn(), - useCardCourseRunData: jest.fn(), - }, +jest.mock('react', () => ({ + ...jest.requireActual('react'), + useMemo: jest.fn((fn) => fn()), +})); + +jest.mock('@src/hooks', () => ({ + ...jest.requireActual('@src/hooks'), + useCourseData: jest.fn(), + useIsMasquerading: jest.fn(), })); const cardId = 'my-test-course-number'; @@ -38,25 +39,38 @@ describe('useActionDisabledState', () => { isAuditAccessExpired, resumeUrl, homeUrl, + availableSessions, } = { ...defaultData, ...args }; - reduxHooks.useMasqueradeData.mockReturnValueOnce({ isMasquerading }); - reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ - hasAccess, - isAudit, - isAuditAccessExpired, - }); - reduxHooks.useCardEntitlementData.mockReturnValueOnce({ - isEntitlement, - isFulfilled, - canChange, - hasSessions, - }); - reduxHooks.useCardCourseRunData.mockReturnValueOnce({ - resumeUrl, - homeUrl, + useIsMasquerading.mockReturnValue(isMasquerading); + useCourseData.mockReturnValue({ + enrollment: { + hasAccess, + isAudit, + isAuditAccessExpired, + coursewareAccess: { + isStaff: false, + hasUnmetPrereqs: !hasAccess, + isTooEarly: !hasAccess, + }, + }, + entitlement: isEntitlement ? { + isEntitlement: true, + isFulfilled, + canChange, + hasSessions, + availableSessions, + } : {}, + courseRun: { + resumeUrl, + homeUrl, + }, }); }; + beforeEach(() => { + jest.clearAllMocks(); + }); + const runHook = () => hooks.useActionDisabledState(cardId); describe('disableBeginCourse', () => { const testDisabled = (data, expected) => { @@ -142,6 +156,7 @@ describe('useActionDisabledState', () => { hasAccess: true, canChange: true, hasSessions: true, + availableSessions: ['session1'], }, false, ); diff --git a/src/containers/CourseCard/hooks.js b/src/containers/CourseCard/hooks.js index 9a0f5ca77..76973d5bf 100644 --- a/src/containers/CourseCard/hooks.js +++ b/src/containers/CourseCard/hooks.js @@ -1,23 +1,6 @@ -import { useIntl } from '@edx/frontend-platform/i18n'; import { useWindowSize, breakpoints } from '@openedx/paragon'; -import { reduxHooks } from 'hooks'; export const useIsCollapsed = () => { const { width } = useWindowSize(); return width < breakpoints.small.maxWidth; }; - -export const useCardData = ({ cardId }) => { - const { formatMessage } = useIntl(); - const { title, bannerImgSrc } = reduxHooks.useCardCourseData(cardId); - const { isEnrolled } = reduxHooks.useCardEnrollmentData(cardId); - - return { - isEnrolled, - title, - bannerImgSrc, - formatMessage, - }; -}; - -export default useCardData; diff --git a/src/containers/CourseCard/hooks.test.js b/src/containers/CourseCard/hooks.test.js index ece458f19..9010873c2 100644 --- a/src/containers/CourseCard/hooks.test.js +++ b/src/containers/CourseCard/hooks.test.js @@ -1,48 +1,32 @@ -import { useIntl } from '@edx/frontend-platform/i18n'; +import { renderHook } from '@testing-library/react'; +import { useWindowSize } from '@openedx/paragon'; +import { useIsCollapsed } from './hooks'; -import { reduxHooks } from 'hooks'; - -import * as hooks from './hooks'; - -jest.mock('hooks', () => ({ - reduxHooks: { - useCardCourseData: jest.fn(), - useCardEnrollmentData: jest.fn(), +jest.mock('@openedx/paragon', () => ({ + useWindowSize: jest.fn(), + breakpoints: { + small: { + maxWidth: 576, + }, }, })); -const cardId = 'my-test-course-number'; - -describe('CourseCard hooks', () => { - let out; - const { formatMessage } = useIntl(); - beforeEach(() => { +describe('useIsCollapsed', () => { + afterEach(() => { jest.clearAllMocks(); }); - describe('useCardData', () => { - const courseData = { - title: 'fake-title', - bannerImgSrc: 'my-banner-url', - }; - const runHook = ({ course = {} }) => { - reduxHooks.useCardCourseData.mockReturnValueOnce({ - ...courseData, - ...course, - }); - reduxHooks.useCardEnrollmentData.mockReturnValue({ isEnrolled: 'test-is-enrolled' }); - out = hooks.useCardData({ cardId }); - }; - beforeEach(() => { - runHook({}); - }); - it('forwards formatMessage from useIntl', () => { - expect(out.formatMessage).toEqual(formatMessage); - }); - it('passes course title and banner URL form course data', () => { - expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(cardId); - expect(out.title).toEqual(courseData.title); - expect(out.bannerImgSrc).toEqual(courseData.bannerImgSrc); - }); + it('should return true when window width is smaller than small breakpoint', () => { + useWindowSize.mockReturnValue({ width: 500 }); + const { result } = renderHook(() => useIsCollapsed()); + expect(result.current).toBe(true); + expect(useWindowSize).toHaveBeenCalled(); + }); + + it('should return false when window width is larger than small breakpoint', () => { + useWindowSize.mockReturnValue({ width: 800 }); + const { result } = renderHook(() => useIsCollapsed()); + expect(result.current).toBe(false); + expect(useWindowSize).toHaveBeenCalled(); }); }); diff --git a/src/containers/CourseCard/index.test.jsx b/src/containers/CourseCard/index.test.jsx index ffab80dac..a7352cf80 100644 --- a/src/containers/CourseCard/index.test.jsx +++ b/src/containers/CourseCard/index.test.jsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; import CourseCard from '.'; import hooks from './hooks'; @@ -8,22 +8,43 @@ jest.mock('./hooks', () => ({ useIsCollapsed: jest.fn(), })); -jest.mock('./components/CourseCardBanners', () => 'CourseCardBanners'); -jest.mock('./components/CourseCardImage', () => 'CourseCardImage'); -jest.mock('./components/CourseCardMenu', () => 'CourseCardMenu'); -jest.mock('./components/CourseCardActions', () => 'CourseCardActions'); -jest.mock('./components/CourseCardDetails', () => 'CourseCardDetails'); -jest.mock('./components/CourseCardTitle', () => 'CourseCardTitle'); +const namesMockComponents = [ + 'CourseCardBanners', + 'CourseCardImage', + 'CourseCardMenu', + 'CourseCardActions', + 'CourseCardDetails', + 'CourseCardTitle', +]; + +jest.mock('./components/CourseCardBanners', () => jest.fn(() =>
CourseCardBanners
)); +jest.mock('./components/CourseCardImage', () => jest.fn(() =>
CourseCardImage
)); +jest.mock('./components/CourseCardMenu', () => jest.fn(() =>
CourseCardMenu
)); +jest.mock('./components/CourseCardActions', () => jest.fn(() =>
CourseCardActions
)); +jest.mock('./components/CourseCardDetails', () => jest.fn(() =>
CourseCardDetails
)); +jest.mock('./components/CourseCardTitle', () => jest.fn(() =>
CourseCardTitle
)); const cardId = 'test-card-id'; describe('CourseCard component', () => { - test('snapshot: collapsed', () => { + it('collapsed', () => { hooks.useIsCollapsed.mockReturnValueOnce(true); - expect(shallow().snapshot).toMatchSnapshot(); + render(); + const cardImage = screen.getByText('CourseCardImage'); + expect(cardImage.parentElement).not.toHaveClass('d-flex'); + }); + it('not collapsed', () => { + hooks.useIsCollapsed.mockReturnValueOnce(false); + render(); + const cardImage = screen.getByText('CourseCardImage'); + expect(cardImage.parentElement).toHaveClass('d-flex'); }); - test('snapshot: not collapsed', () => { + it('renders courseCard child components', () => { hooks.useIsCollapsed.mockReturnValueOnce(false); - expect(shallow().snapshot).toMatchSnapshot(); + render(); + namesMockComponents.map((courseCardName) => { + const courseCardComponent = screen.getByText(courseCardName); + return expect(courseCardComponent).toBeInTheDocument(); + }); }); }); diff --git a/src/containers/CourseCard/messages.js b/src/containers/CourseCard/messages.js index cc71fb11a..afdfc8a4f 100644 --- a/src/containers/CourseCard/messages.js +++ b/src/containers/CourseCard/messages.js @@ -1,4 +1,4 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ bannerAlt: { diff --git a/src/containers/CourseFilterControls/ActiveCourseFilters.jsx b/src/containers/CourseFilterControls/ActiveCourseFilters.jsx index a121c749c..17f6e6e6a 100644 --- a/src/containers/CourseFilterControls/ActiveCourseFilters.jsx +++ b/src/containers/CourseFilterControls/ActiveCourseFilters.jsx @@ -1,27 +1,24 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import { Button, Chip } from '@openedx/paragon'; import { CloseSmall } from '@openedx/paragon/icons'; -import { reduxHooks } from 'hooks'; +import { useFilters } from '@src/data/context'; import messages from './messages'; import './index.scss'; -export const ActiveCourseFilters = ({ - filters, - handleRemoveFilter, -}) => { +export const ActiveCourseFilters = () => { const { formatMessage } = useIntl(); - const clearFilters = reduxHooks.useClearFilters(); + const { filters, clearFilters, removeFilter } = useFilters(); + return (
{filters.map(filter => ( removeFilter(filter)} > {formatMessage(messages[filter])} @@ -32,9 +29,5 @@ export const ActiveCourseFilters = ({
); }; -ActiveCourseFilters.propTypes = { - filters: PropTypes.arrayOf(PropTypes.string).isRequired, - handleRemoveFilter: PropTypes.func.isRequired, -}; export default ActiveCourseFilters; diff --git a/src/containers/CourseFilterControls/ActiveCourseFilters.test.jsx b/src/containers/CourseFilterControls/ActiveCourseFilters.test.jsx index faf74d449..d54f14498 100644 --- a/src/containers/CourseFilterControls/ActiveCourseFilters.test.jsx +++ b/src/containers/CourseFilterControls/ActiveCourseFilters.test.jsx @@ -1,17 +1,54 @@ -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; +import { formatMessage } from '@src/testUtils'; +import { useFilters } from '@src/data/context'; -import { FilterKeys } from 'data/constants/app'; +import { FilterKeys } from '@src/data/constants/app'; +import userEvent from '@testing-library/user-event'; import ActiveCourseFilters from './ActiveCourseFilters'; +import messages from './messages'; + +const filters = Object.values(FilterKeys); + +jest.mock('@src/data/context', () => ({ + useFilters: jest.fn(), +})); + +const removeFiltersMock = jest.fn().mockName('removeFilter'); +const clearFiltersMock = jest.fn().mockName('clearFilters'); +useFilters.mockReturnValue({ + filters, + removeFilter: removeFiltersMock, + clearFilters: clearFiltersMock, +}); describe('ActiveCourseFilters', () => { - const props = { - filters: Object.values(FilterKeys), - handleRemoveFilter: jest.fn().mockName('handleRemoveFilter'), - }; - describe('snapshot', () => { - test('renders', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); + it('renders chips correctly', () => { + render(); + filters.map((key) => { + const chip = screen.getByText(formatMessage(messages[key])); + return expect(chip).toBeInTheDocument(); }); }); + it('renders button correctly', () => { + render(); + const button = screen.getByRole('button', { name: formatMessage(messages.clearAll) }); + expect(button).toBeInTheDocument(); + }); + it('should call onClick when button is clicked remove filter', async () => { + const user = userEvent.setup(); + render(); + const removeButton = screen.getByRole('button', { name: formatMessage(messages[filters[0]]) }); + await user.click(removeButton); + expect(removeFiltersMock).toHaveBeenCalledTimes(1); + expect(removeFiltersMock).toHaveBeenCalledWith(filters[0]); + }); + it('should call onClick when button is clicked clear all filters', async () => { + const user = userEvent.setup(); + render(); + screen.debug(); + const clearAllButton = screen.getByRole('button', { name: formatMessage(messages.clearAll) }); + await user.click(clearAllButton); + expect(clearFiltersMock).toHaveBeenCalledTimes(1); + }); }); diff --git a/src/containers/CourseFilterControls/CourseFilterControls.jsx b/src/containers/CourseFilterControls/CourseFilterControls.jsx index 4dd981865..07ef06f2d 100644 --- a/src/containers/CourseFilterControls/CourseFilterControls.jsx +++ b/src/containers/CourseFilterControls/CourseFilterControls.jsx @@ -1,7 +1,6 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; - +import { useIntl } from '@openedx/frontend-base'; +import track from '@src/tracking'; import { Button, Form, @@ -14,44 +13,51 @@ import { } from '@openedx/paragon'; import { Close, Tune } from '@openedx/paragon/icons'; -import { reduxHooks } from 'hooks'; - +import { useInitializeLearnerHome } from '@src/data/hooks'; +import { useFilters } from '@src/data/context'; import FilterForm from './components/FilterForm'; import SortForm from './components/SortForm'; -import useCourseFilterControlsData from './hooks'; import messages from './messages'; import './index.scss'; -export const CourseFilterControls = ({ - sortBy, - setSortBy, - filters, -}) => { +export const CourseFilterControls = () => { + const [isOpen, setIsOpen] = React.useState(false); + const [targetRef, setTargetRef] = React.useState(null); const { formatMessage } = useIntl(); - const hasCourses = reduxHooks.useHasCourses(); + const { data } = useInitializeLearnerHome(); + const hasCourses = React.useMemo(() => data?.courses?.length > 0, [data]); const { - isOpen, - open, - close, - target, - setTarget, - handleFilterChange, - handleSortChange, - } = useCourseFilterControlsData({ - filters, - setSortBy, - }); + filters, sortBy, setSortBy, addFilter, removeFilter, + } = useFilters(); + + const openFiltersOptions = () => { + track.filter.filterClicked(); + setIsOpen(true); + }; + const closeFiltersOptions = () => { + track.filter.filterOptionSelected(filters); + setIsOpen(false); + }; + + const handleSortChange = (event) => { + setSortBy(event.target.value); + }; + + const handleFilterChange = ({ target: { checked, value } }) => { + const update = checked ? addFilter : removeFilter; + update(value); + }; const { width } = useWindowSize(); const isMobile = width < breakpoints.small.minWidth; return (
-
-`; diff --git a/src/containers/CourseFilterControls/__snapshots__/CourseFilterControls.test.jsx.snap b/src/containers/CourseFilterControls/__snapshots__/CourseFilterControls.test.jsx.snap deleted file mode 100644 index 834f4e010..000000000 --- a/src/containers/CourseFilterControls/__snapshots__/CourseFilterControls.test.jsx.snap +++ /dev/null @@ -1,169 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CourseFilterControls is not mobile snapshot 1`] = ` -
- -
- -
-
- -
-
-
- -
-
-
-
-
-`; - -exports[`CourseFilterControls mobile snapshot 1`] = ` -
- -
- -
- - Refine - -
-
-
- -
-
- -
-
- - - -
-
-
-
-`; - -exports[`CourseFilterControls no courses snapshot 1`] = ` -
- -
- -
-
- -
-
-
- -
-
-
-
-
-`; diff --git a/src/containers/CourseFilterControls/components/Checkbox.jsx b/src/containers/CourseFilterControls/components/Checkbox.jsx index 78e105cf7..db643609c 100644 --- a/src/containers/CourseFilterControls/components/Checkbox.jsx +++ b/src/containers/CourseFilterControls/components/Checkbox.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import { Form } from '@openedx/paragon'; diff --git a/src/containers/CourseFilterControls/components/Checkbox.test.jsx b/src/containers/CourseFilterControls/components/Checkbox.test.jsx index de5eafa50..e83f94f8c 100644 --- a/src/containers/CourseFilterControls/components/Checkbox.test.jsx +++ b/src/containers/CourseFilterControls/components/Checkbox.test.jsx @@ -1,14 +1,17 @@ -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { formatMessage } from '@src/testUtils'; +import { IntlProvider } from '@openedx/frontend-base'; -import { FilterKeys } from 'data/constants/app'; +import { FilterKeys } from '@src/data/constants/app'; import Checkbox from './Checkbox'; +import messages from '../messages'; describe('Checkbox', () => { - describe('snapshot', () => { + describe('renders correctly', () => { Object.keys(FilterKeys).forEach((filterKey) => { it(`renders ${filterKey}`, () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); + render(); + expect(screen.getByText(formatMessage(messages[filterKey]))).toBeInTheDocument(); }); }); }); diff --git a/src/containers/CourseFilterControls/components/FilterForm.jsx b/src/containers/CourseFilterControls/components/FilterForm.jsx index 0932a1cbb..ea6a31f7a 100644 --- a/src/containers/CourseFilterControls/components/FilterForm.jsx +++ b/src/containers/CourseFilterControls/components/FilterForm.jsx @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; -import { FilterKeys } from 'data/constants/app'; +import { FilterKeys } from '../../../data/constants/app'; import { Form } from '@openedx/paragon'; diff --git a/src/containers/CourseFilterControls/components/FilterForm.test.jsx b/src/containers/CourseFilterControls/components/FilterForm.test.jsx index 2905d3d05..2776eb3d0 100644 --- a/src/containers/CourseFilterControls/components/FilterForm.test.jsx +++ b/src/containers/CourseFilterControls/components/FilterForm.test.jsx @@ -1,29 +1,54 @@ -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; +import { formatMessage } from '@src/testUtils'; +import { FilterKeys } from '@src/data/constants/app'; +import { FilterForm, filterOrder } from './FilterForm'; +import messages from '../messages'; -import { FilterKeys } from 'data/constants/app'; -import FilterForm, { filterOrder } from './FilterForm'; +const mockHandleFilterChange = jest.fn(); -jest.mock('./Checkbox', () => 'Checkbox'); +const defaultProps = { + filters: [FilterKeys.inProgress], + handleFilterChange: mockHandleFilterChange, +}; + +const renderComponent = (props = defaultProps) => render( + + + , +); describe('FilterForm', () => { - const props = { - filters: ['test-filter'], - handleFilterChange: jest.fn().mockName('handleFilterChange'), - }; - describe('snapshot', () => { - test('renders', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders all filter checkboxes in correct order', () => { + renderComponent(); + const checkboxes = screen.getAllByRole('checkbox'); + expect(checkboxes).toHaveLength(filterOrder.length); + checkboxes.forEach((checkbox, index) => { + expect(checkbox).toHaveAttribute('value', filterOrder[index]); + }); + }); + + it('checks boxes based on filters prop', () => { + const filters = [FilterKeys.inProgress, FilterKeys.done]; + renderComponent({ ...defaultProps, filters }); + filters.forEach(filter => { + expect(screen.getByRole('checkbox', { name: formatMessage(messages[filter]) })).toBeChecked(); }); }); - test('filterOrder', () => { - expect(filterOrder).toEqual([ - FilterKeys.inProgress, - FilterKeys.notStarted, - FilterKeys.done, - FilterKeys.notEnrolled, - FilterKeys.upgraded, - ]); + it('calls handleFilterChange when checkbox is clicked', () => { + renderComponent(); + const checkbox = screen.getByRole('checkbox', { name: formatMessage(messages.notStarted) }); + fireEvent.click(checkbox); + expect(mockHandleFilterChange).toHaveBeenCalled(); + }); + + it('displays course status heading', () => { + renderComponent(); + expect(screen.getByText(/course status/i)).toBeInTheDocument(); }); }); diff --git a/src/containers/CourseFilterControls/components/SortForm.jsx b/src/containers/CourseFilterControls/components/SortForm.jsx index 20aba4f56..e6ec32321 100644 --- a/src/containers/CourseFilterControls/components/SortForm.jsx +++ b/src/containers/CourseFilterControls/components/SortForm.jsx @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; -import { SortKeys } from 'data/constants/app'; +import { SortKeys } from '../../../data/constants/app'; import { Form } from '@openedx/paragon'; diff --git a/src/containers/CourseFilterControls/components/SortForm.test.jsx b/src/containers/CourseFilterControls/components/SortForm.test.jsx index 598181f5d..4d70382ed 100644 --- a/src/containers/CourseFilterControls/components/SortForm.test.jsx +++ b/src/containers/CourseFilterControls/components/SortForm.test.jsx @@ -1,19 +1,29 @@ -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; +import { formatMessage } from '@src/testUtils'; -import { SortKeys } from 'data/constants/app'; +import { SortKeys } from '@src/data/constants/app'; import SortForm from './SortForm'; - -jest.mock('./Checkbox', () => 'Checkbox'); +import messages from '../messages'; describe('SortForm', () => { const props = { handleSortChange: jest.fn().mockName('handleSortChange'), sortBy: SortKeys.enrolled, }; - describe('snapshot', () => { - test('renders', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); - }); + it('renders heading', () => { + render(); + const heading = screen.getByText(formatMessage(messages.sort)); + expect(heading).toBeInTheDocument(); + }); + it('renders radio enrolled', () => { + render(); + const enrolled = screen.getByRole('radio', { name: formatMessage(messages.sortLastEnrolled) }); + expect(enrolled).toBeInTheDocument(); + }); + it('renders radio title', () => { + render(); + const title = screen.getByRole('radio', { name: formatMessage(messages.sortTitle) }); + expect(title).toBeInTheDocument(); }); }); diff --git a/src/containers/CourseFilterControls/components/__snapshots__/Checkbox.test.jsx.snap b/src/containers/CourseFilterControls/components/__snapshots__/Checkbox.test.jsx.snap deleted file mode 100644 index 35226ca0f..000000000 --- a/src/containers/CourseFilterControls/components/__snapshots__/Checkbox.test.jsx.snap +++ /dev/null @@ -1,46 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Checkbox snapshot renders done 1`] = ` - - Done - -`; - -exports[`Checkbox snapshot renders inProgress 1`] = ` - - In-Progress - -`; - -exports[`Checkbox snapshot renders notEnrolled 1`] = ` - - Not Enrolled - -`; - -exports[`Checkbox snapshot renders notStarted 1`] = ` - - Not Started - -`; - -exports[`Checkbox snapshot renders upgraded 1`] = ` - - Upgraded - -`; diff --git a/src/containers/CourseFilterControls/components/__snapshots__/FilterForm.test.jsx.snap b/src/containers/CourseFilterControls/components/__snapshots__/FilterForm.test.jsx.snap deleted file mode 100644 index 01f2167d6..000000000 --- a/src/containers/CourseFilterControls/components/__snapshots__/FilterForm.test.jsx.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FilterForm snapshot renders 1`] = ` - -
- Course Status -
- - - - - - - -
-`; diff --git a/src/containers/CourseFilterControls/components/__snapshots__/SortForm.test.jsx.snap b/src/containers/CourseFilterControls/components/__snapshots__/SortForm.test.jsx.snap deleted file mode 100644 index 05c1b195a..000000000 --- a/src/containers/CourseFilterControls/components/__snapshots__/SortForm.test.jsx.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SortForm snapshot renders 1`] = ` - -
- Sort -
- - - Last enrolled - - - Title (A-Z) - - -
-`; diff --git a/src/containers/CourseFilterControls/hooks.js b/src/containers/CourseFilterControls/hooks.js deleted file mode 100644 index b19690193..000000000 --- a/src/containers/CourseFilterControls/hooks.js +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import { useToggle } from '@openedx/paragon'; - -import { StrictDict } from 'utils'; -import track from 'tracking'; -import { reduxHooks } from 'hooks'; - -import * as module from './hooks'; - -export const state = StrictDict({ - target: (val) => React.useState(val), // eslint-disable-line -}); - -/** - * Sets up a toggle for the modal as well as helper functions for handling changes to the form controls. - * - * @param {array} filters Currently active course filters - * @param {function} setSortBy Set function for sorting the course list - * @returns {object} data and functions for managing the CourseFilterControls component - */ -export const useCourseFilterControlsData = ({ - filters, - setSortBy, -}) => { - const [isOpen, toggleOpen, toggleClose] = useToggle(false); - const [target, setTarget] = module.state.target(null); - - const addFilter = reduxHooks.useAddFilter(); - const removeFilter = reduxHooks.useRemoveFilter(); - - const handleFilterChange = ({ target: { checked, value } }) => { - const update = checked ? addFilter : removeFilter; - update(value); - }; - const handleSortChange = ({ target: { value } }) => { - setSortBy(value); - }; - - const open = () => { - track.filter.filterClicked(); - toggleOpen(); - }; - - const close = () => { - track.filter.filterOptionSelected(filters); - toggleClose(); - }; - - return { - isOpen, - open, - close, - target, - setTarget, - handleFilterChange, - handleSortChange, - }; -}; - -export default useCourseFilterControlsData; diff --git a/src/containers/CourseFilterControls/hooks.test.js b/src/containers/CourseFilterControls/hooks.test.js deleted file mode 100644 index eaff38519..000000000 --- a/src/containers/CourseFilterControls/hooks.test.js +++ /dev/null @@ -1,113 +0,0 @@ -import { useToggle } from '@openedx/paragon'; - -import { MockUseState } from 'testUtils'; -import { reduxHooks } from 'hooks'; - -import track from 'tracking'; - -import * as hooks from './hooks'; - -jest.mock('tracking', () => ({ - filter: { - filterClicked: jest.fn(), - filterOptionSelected: jest.fn(), - }, -})); - -jest.mock('hooks', () => ({ - reduxHooks: { - useAddFilter: jest.fn(), - useRemoveFilter: jest.fn(), - }, -})); - -const state = new MockUseState(hooks); - -describe('CourseFilterControls hooks', () => { - let out; - const filters = ['a', 'b', 'c']; - const setSortBy = jest.fn(); - - const removeFilter = jest.fn(); - reduxHooks.useRemoveFilter.mockReturnValue(removeFilter); - const addFilter = jest.fn(); - reduxHooks.useAddFilter.mockReturnValue(addFilter); - - const toggleOpen = jest.fn(); - const toggleClose = jest.fn(); - - describe('state values', () => { - state.testGetter(state.keys.target); - }); - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('useCourseFilterControlsData', () => { - beforeEach(() => { - useToggle.mockReturnValueOnce([false, toggleOpen, toggleClose]); - state.mock(); - out = hooks.useCourseFilterControlsData({ - filters, - setSortBy, - }); - }); - afterEach(state.restore); - - test('default state', () => { - expect(out.isOpen).toEqual(false); - expect(out.target).toEqual(state.stateVals.target); - }); - - test('open calls toggleOpen and track.filter.filterClicked', () => { - out.open(); - expect(toggleOpen).toHaveBeenCalled(); - expect(track.filter.filterClicked).toHaveBeenCalled(); - }); - - test('close calls toggleClose and track.filter.filterOptionSelected', () => { - out.close(); - expect(toggleClose).toHaveBeenCalled(); - expect(track.filter.filterOptionSelected).toHaveBeenCalledWith(filters); - }); - - test('isOpen is true when target is set', () => { - useToggle.mockReturnValueOnce([true, toggleOpen, toggleClose]); - expect(out.target).toEqual(null); - state.mockVal(state.keys.target, 'foo'); - out = hooks.useCourseFilterControlsData({ - filters, - setSortBy, - }); - expect(out.isOpen).toEqual(true); - expect(out.target).toEqual('foo'); - }); - - test('handle filter change', () => { - const value = 'a'; - out.handleFilterChange({ - target: { - checked: true, - value, - }, - }); - expect(addFilter).toHaveBeenCalledWith(value); - out.handleFilterChange({ - target: { - checked: false, - value, - }, - }); - expect(removeFilter).toHaveBeenCalledWith(value); - }); - test('handle sort change', () => { - const value = 'a'; - out.handleSortChange({ - target: { - value, - }, - }); - expect(setSortBy).toHaveBeenCalledWith(value); - }); - }); -}); diff --git a/src/containers/CourseFilterControls/messages.js b/src/containers/CourseFilterControls/messages.js index 39875b7d0..21ff12efd 100644 --- a/src/containers/CourseFilterControls/messages.js +++ b/src/containers/CourseFilterControls/messages.js @@ -1,4 +1,4 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ courseStatus: { diff --git a/src/containers/CoursesPanel/CourseList/__snapshots__/index.test.jsx.snap b/src/containers/CoursesPanel/CourseList/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 89889324b..000000000 --- a/src/containers/CoursesPanel/CourseList/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,70 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CourseList collapsed with multiple courses and pages snapshot 1`] = ` - -
- -
-
- - - - -
-
-`; - -exports[`CourseList no courses or filters snapshot 1`] = ` - -
- -`; - -exports[`CourseList with filters snapshot 1`] = `undefined`; - -exports[`CourseList with multiple courses and pages snapshot 1`] = ` - -
- - - - -
-
-`; diff --git a/src/containers/CoursesPanel/CourseList/index.jsx b/src/containers/CoursesPanel/CourseList/index.jsx index 2b178b23d..f26a2aa11 100644 --- a/src/containers/CoursesPanel/CourseList/index.jsx +++ b/src/containers/CoursesPanel/CourseList/index.jsx @@ -4,21 +4,22 @@ import PropTypes from 'prop-types'; import { Pagination } from '@openedx/paragon'; import { ActiveCourseFilters, -} from 'containers/CourseFilterControls'; -import CourseCard from 'containers/CourseCard'; +} from '../../../containers/CourseFilterControls'; +import CourseCard from '../../../containers/CourseCard'; import { useIsCollapsed } from './hooks'; export const CourseList = ({ courseListData }) => { const { - filterOptions, setPageNumber, numPages, showFilters, visibleList, + setPageNumber, numPages, visibleList, showFilters, } = courseListData; + const isCollapsed = useIsCollapsed(); return ( <> {showFilters && (
- +
)}
@@ -42,7 +43,6 @@ export const CourseList = ({ courseListData }) => { export const courseListDataShape = PropTypes.shape({ showFilters: PropTypes.bool.isRequired, visibleList: PropTypes.arrayOf(PropTypes.shape()).isRequired, - filterOptions: PropTypes.shape().isRequired, numPages: PropTypes.number.isRequired, setPageNumber: PropTypes.func.isRequired, }); diff --git a/src/containers/CoursesPanel/CourseList/index.test.jsx b/src/containers/CoursesPanel/CourseList/index.test.jsx index 0584eda5f..4bcb57613 100644 --- a/src/containers/CoursesPanel/CourseList/index.test.jsx +++ b/src/containers/CoursesPanel/CourseList/index.test.jsx @@ -1,4 +1,4 @@ -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; import { useIsCollapsed } from './hooks'; import CourseList from '.'; @@ -7,9 +7,9 @@ jest.mock('./hooks', () => ({ useIsCollapsed: jest.fn(), })); -jest.mock('containers/CourseCard', () => 'CourseCard'); -jest.mock('containers/CourseFilterControls', () => ({ - ActiveCourseFilters: 'ActiveCourseFilters', +jest.mock('../../CourseCard', () => jest.fn(() =>
CourseCard
)); +jest.mock('../../CourseFilterControls', () => ({ + ActiveCourseFilters: jest.fn(() =>
ActiveCourseFilters
), })); describe('CourseList', () => { @@ -22,43 +22,60 @@ describe('CourseList', () => { }; useIsCollapsed.mockReturnValue(false); - const createWrapper = (courseListData = defaultCourseListData) => ( - shallow() + const renderList = (courseListData = defaultCourseListData) => ( + render() ); describe('no courses or filters', () => { - test('snapshot', () => { - const wrapper = createWrapper(); - expect(wrapper.snapshot).toMatchSnapshot(); + it('should not render related components', () => { + renderList(); + const filterControls = screen.queryByText('ActiveCourseFilters'); + const courseCard = screen.queryByText('CourseCard'); + const prevButton = screen.queryByRole('button', { name: 'Previous' }); + expect(filterControls).toBeNull(); + expect(courseCard).toBeNull(); + expect(prevButton).toBeNull(); }); }); describe('with filters', () => { - test('snapshot', () => { - const wrapper = createWrapper({ - filterOptions: { abitary: 'filter' }, + it('should render filter component', () => { + renderList({ + ...defaultCourseListData, showFilters: true, }); - expect(wrapper.snapshot).toMatchSnapshot(); + const filterControls = screen.getByText('ActiveCourseFilters'); + expect(filterControls).toBeInTheDocument(); }); }); describe('with multiple courses and pages', () => { - test('snapshot', () => { - const wrapper = createWrapper({ - visibleList: [{ cardId: 'foo' }, { cardId: 'bar' }, { cardId: 'baz' }], - numPages: 3, + it('render Course Cards and pagination', () => { + const visibleList = [{ cardId: 'foo' }, { cardId: 'bar' }, { cardId: 'baz' }]; + const numPages = 3; + renderList({ + ...defaultCourseListData, + visibleList, + numPages, }); - expect(wrapper.snapshot).toMatchSnapshot(); + const courseCards = screen.getAllByText('CourseCard'); + expect(courseCards.length).toEqual(visibleList.length); + const pageButtons = screen.getAllByRole('button', { name: /^Page/i }); + expect(pageButtons.length).toBe(numPages); }); }); describe('collapsed with multiple courses and pages', () => { - test('snapshot', () => { + it('should render correct components', () => { + const visibleList = [{ cardId: 'foo' }, { cardId: 'bar' }, { cardId: 'baz' }]; useIsCollapsed.mockReturnValueOnce(true); - const wrapper = createWrapper({ - visibleList: [{ cardId: 'foo' }, { cardId: 'bar' }, { cardId: 'baz' }], + renderList({ + ...defaultCourseListData, + visibleList, numPages: 3, showFilters: true, }); - expect(wrapper.snapshot).toMatchSnapshot(); + const courseCards = screen.getAllByText('CourseCard'); + expect(courseCards.length).toEqual(visibleList.length); + const reducedPagination = screen.getByRole('button', { name: '1 of 3' }); + expect(reducedPagination).toBeInTheDocument(); }); }); }); diff --git a/src/containers/CoursesPanel/NoCoursesView/__snapshots__/index.test.jsx.snap b/src/containers/CoursesPanel/NoCoursesView/__snapshots__/index.test.jsx.snap deleted file mode 100644 index fc408e146..000000000 --- a/src/containers/CoursesPanel/NoCoursesView/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NoCoursesView snapshot 1`] = ` -
- No Courses view banner -

- Looking for a new challenge? -

-

- Explore our courses to add them to your dashboard. -

- -
-`; diff --git a/src/containers/CoursesPanel/NoCoursesView/index.jsx b/src/containers/CoursesPanel/NoCoursesView/index.jsx index f7a38c68e..b35b516c6 100644 --- a/src/containers/CoursesPanel/NoCoursesView/index.jsx +++ b/src/containers/CoursesPanel/NoCoursesView/index.jsx @@ -1,18 +1,19 @@ import React from 'react'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import { Button, Image } from '@openedx/paragon'; import { Search } from '@openedx/paragon/icons'; -import { baseAppUrl } from 'data/services/lms/urls'; +import { baseAppUrl } from '@src/data/services/lms/urls'; -import emptyCourseSVG from 'assets/empty-course.svg'; -import { reduxHooks } from 'hooks'; +import emptyCourseSVG from '@src/assets/empty-course.svg'; +import { useInitializeLearnerHome } from '@src/data/hooks'; import messages from './messages'; import './index.scss'; export const NoCoursesView = () => { const { formatMessage } = useIntl(); - const { courseSearchUrl } = reduxHooks.usePlatformSettingsData(); + const { data: learnerData } = useInitializeLearnerHome(); + const courseSearchUrl = learnerData?.platformSettings?.courseSearchUrl || ''; return (
* { - margin-top: map-get($spacers, 3); - margin-bottom: map-get($spacers, 3); + margin-top: var(--pgn-spacing-spacer-3); + margin-bottom: var(--pgn-spacing-spacer-3); } } diff --git a/src/containers/CoursesPanel/NoCoursesView/index.test.jsx b/src/containers/CoursesPanel/NoCoursesView/index.test.jsx index 923fde28d..331278ad4 100644 --- a/src/containers/CoursesPanel/NoCoursesView/index.test.jsx +++ b/src/containers/CoursesPanel/NoCoursesView/index.test.jsx @@ -1,18 +1,40 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; +import { formatMessage } from '@src/testUtils'; +import { baseAppUrl } from '@src/data/services/lms/urls'; import EmptyCourse from '.'; +import messages from './messages'; -jest.mock('hooks', () => ({ - reduxHooks: { - usePlatformSettingsData: jest.fn(() => ({ - courseSearchUrl: '/course-search-url', - })), - }, +const courseSearchUrl = '/course-search-url'; + +jest.mock('@src/data/hooks', () => ({ + useInitializeLearnerHome: jest.fn(() => ({ + data: { + platformSettings: { + courseSearchUrl, + }, + }, + })), })); describe('NoCoursesView', () => { - test('snapshot', () => { - expect(shallow().snapshot).toMatchSnapshot(); + it('should display image, heading and button', () => { + render(); + const image = screen.getByRole('img', { alt: formatMessage(messages.bannerAlt) }); + expect(image).toBeInTheDocument(); + }); + it('should display heading and prompt', () => { + render(); + const heading = screen.getByText(formatMessage(messages.lookingForChallengePrompt)); + const prompt = screen.getByText(formatMessage(messages.exploreCoursesPrompt)); + expect(heading).toBeInTheDocument(); + expect(prompt).toBeInTheDocument(); + }); + it('should display button', () => { + render(); + const button = screen.getByRole('link', { name: formatMessage(messages.exploreCoursesButton) }); + expect(button).toBeInTheDocument(); + expect(button.href).toBe(baseAppUrl(courseSearchUrl)); }); }); diff --git a/src/containers/CoursesPanel/NoCoursesView/messages.js b/src/containers/CoursesPanel/NoCoursesView/messages.js index ebe25fb99..596339901 100644 --- a/src/containers/CoursesPanel/NoCoursesView/messages.js +++ b/src/containers/CoursesPanel/NoCoursesView/messages.js @@ -1,4 +1,4 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ lookingForChallengePrompt: { diff --git a/src/containers/CoursesPanel/__snapshots__/index.test.jsx.snap b/src/containers/CoursesPanel/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 168743287..000000000 --- a/src/containers/CoursesPanel/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,55 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CoursesPanel no courses snapshot 1`] = ` -
-
-

- My Courses -

-
- -
-
- -
-`; - -exports[`CoursesPanel with courses snapshot 1`] = ` -
-
-

- My Courses -

-
- -
-
- -
-`; diff --git a/src/containers/CoursesPanel/hooks.js b/src/containers/CoursesPanel/hooks.js deleted file mode 100644 index 2cdeeb3ed..000000000 --- a/src/containers/CoursesPanel/hooks.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; - -import { ListPageSize, SortKeys } from 'data/constants/app'; -import { reduxHooks } from 'hooks'; -import { StrictDict } from 'utils'; - -import * as module from './hooks'; - -export const state = StrictDict({ - sortBy: (val) => React.useState(val), // eslint-disable-line -}); - -/** - * Filters are fetched from the store and used to generate a list of "visible" courses. - * Other values returned and used for the layout of the CoursesPanel component are: - * the current page number, the sorting method, and whether or not to enable filters and pagination. - * - * @returns data for the CoursesPanel component - */ -export const useCourseListData = () => { - const filters = reduxHooks.useFilters(); - const removeFilter = reduxHooks.useRemoveFilter(); - const pageNumber = reduxHooks.usePageNumber(); - const setPageNumber = reduxHooks.useSetPageNumber(); - - const [sortBy, setSortBy] = module.state.sortBy(SortKeys.enrolled); - - const querySearch = new URLSearchParams(window.location.search); - const disablePagination = querySearch.get('disable_pagination'); - - const { numPages, visibleList } = reduxHooks.useCurrentCourseList({ - sortBy, - filters, - pageSize: Number(disablePagination) === 1 ? 0 : ListPageSize, - }); - - const handleRemoveFilter = (filter) => () => removeFilter(filter); - - return { - pageNumber, - numPages, - setPageNumber, - visibleList, - filterOptions: { - sortBy, - setSortBy, - filters, - handleRemoveFilter, - }, - showFilters: filters.length > 0, - }; -}; - -export default useCourseListData; diff --git a/src/containers/CoursesPanel/hooks.test.js b/src/containers/CoursesPanel/hooks.test.js deleted file mode 100644 index 64f608ca8..000000000 --- a/src/containers/CoursesPanel/hooks.test.js +++ /dev/null @@ -1,115 +0,0 @@ -import { MockUseState } from 'testUtils'; -import { reduxHooks } from 'hooks'; -import { ListPageSize, SortKeys } from 'data/constants/app'; -import * as hooks from './hooks'; - -jest.mock('hooks', () => ({ - reduxHooks: { - useCurrentCourseList: jest.fn(), - usePageNumber: jest.fn(() => 23), - useSetPageNumber: jest.fn(), - useFilters: jest.fn(), - useRemoveFilter: jest.fn(), - }, -})); - -const mockGet = jest.fn(() => ({})); - -global.URLSearchParams = jest.fn().mockImplementation(() => ({ - get: mockGet, -})); - -const state = new MockUseState(hooks); - -const testList = ['a', 'b']; -const testListData = { - numPages: 52, - visibleList: testList, -}; -const testSortBy = 'fake sort option'; -const testFilters = ['some', 'fake', 'filters']; - -const setPageNumber = jest.fn(val => ({ setPageNumber: val })); -reduxHooks.useSetPageNumber.mockReturnValue(setPageNumber); - -const removeFilter = jest.fn(); -reduxHooks.useRemoveFilter.mockReturnValue(removeFilter); -reduxHooks.useFilters.mockReturnValue(['some', 'fake', 'filters']); - -describe('CourseList hooks', () => { - let out; - - reduxHooks.useCurrentCourseList.mockReturnValue(testListData); - - describe('state values', () => { - state.testGetter(state.keys.sortBy); - jest.clearAllMocks(); - }); - - describe('useCourseListData', () => { - afterEach(state.restore); - beforeEach(() => { - state.mock(); - state.mockVal(state.keys.sortBy, testSortBy); - out = hooks.useCourseListData(); - }); - describe('behavior', () => { - it('initializes sort with enrollment date', () => { - state.expectInitializedWith(state.keys.sortBy, SortKeys.enrolled); - }); - it('loads current course list with sortBy, filters, and page size', () => { - expect(reduxHooks.useCurrentCourseList).toHaveBeenCalledWith({ - sortBy: testSortBy, - filters: testFilters, - pageSize: ListPageSize, - }); - }); - it('loads current course list with page size 0 if/when there is query param disable_pagination=1', () => { - state.mock(); - state.mockVal(state.keys.sortBy, testSortBy); - mockGet.mockReturnValueOnce('1'); - out = hooks.useCourseListData(); - expect(reduxHooks.useCurrentCourseList).toHaveBeenCalledWith({ - sortBy: testSortBy, - filters: testFilters, - pageSize: 0, - }); - }); - }); - describe('output', () => { - test('pageNumber loads from usePageNumber hook', () => { - expect(out.pageNumber).toEqual(reduxHooks.usePageNumber()); - }); - test('numPages and visible list load from useCurrentCourseList hook', () => { - expect(out.numPages).toEqual(testListData.numPages); - expect(out.visibleList).toEqual(testListData.visibleList); - }); - test('showFilters is true iff filters is not empty', () => { - expect(out.showFilters).toEqual(true); - state.mockVal(state.keys.sortBy, testSortBy); - reduxHooks.useFilters.mockReturnValueOnce([]); - out = hooks.useCourseListData(); - // don't show filter when list is empty. - expect(out.showFilters).toEqual(false); - }); - describe('filterOptions', () => { - test('sortBy and setSortBy are connected to the state value', () => { - expect(out.filterOptions.sortBy).toEqual(testSortBy); - expect(out.filterOptions.setSortBy).toEqual(state.setState.sortBy); - }); - test('filters passed by useFilters hook', () => { - expect(out.filterOptions.filters).toEqual(testFilters); - }); - test('handleRemoveFilter creates callback to call setFilter.remove', () => { - const cb = out.filterOptions.handleRemoveFilter(testFilters[0]); - expect(removeFilter).not.toHaveBeenCalled(); - cb(); - expect(removeFilter).toHaveBeenCalledWith(testFilters[0]); - }); - test('setPageNumber dispatches setPageNumber action with passed value', () => { - expect(out.setPageNumber(2)).toEqual(setPageNumber(2)); - }); - }); - }); - }); -}); diff --git a/src/containers/CoursesPanel/index.jsx b/src/containers/CoursesPanel/index.jsx index 6bf525ab4..412c4893c 100644 --- a/src/containers/CoursesPanel/index.jsx +++ b/src/containers/CoursesPanel/index.jsx @@ -1,15 +1,15 @@ -import React from 'react'; +import React, { useMemo } from 'react'; -import { useIntl } from '@edx/frontend-platform/i18n'; - -import { reduxHooks } from 'hooks'; +import { useIntl } from '@openedx/frontend-base'; +import { useInitializeLearnerHome } from '@src/data/hooks'; import { CourseFilterControls, -} from 'containers/CourseFilterControls'; -import CourseListSlot from 'plugin-slots/CourseListSlot'; -import NoCoursesViewSlot from 'plugin-slots/NoCoursesViewSlot'; +} from '../../containers/CourseFilterControls'; +import CourseListSlot from '../../slots/CourseListSlot'; +import NoCoursesViewSlot from '../../slots/NoCoursesViewSlot'; +import { useFilters } from '@src/data/context'; -import { useCourseListData } from './hooks'; +import { getVisibleList } from '@src/utils/dataTransformers'; import messages from './messages'; @@ -22,14 +22,45 @@ import './index.scss'; */ export const CoursesPanel = () => { const { formatMessage } = useIntl(); - const hasCourses = reduxHooks.useHasCourses(); - const courseListData = useCourseListData(); + const { data } = useInitializeLearnerHome(); + const hasCourses = useMemo(() => data?.courses?.length > 0, [data]); + + const { + filters, sortBy, pageNumber, setPageNumber, + } = useFilters(); + const { visibleList, numPages } = useMemo(() => { + const transformedCourses = data?.coursesByCardId + ? Object.values(data.coursesByCardId) + : []; + return getVisibleList( + transformedCourses, + filters, + sortBy, + pageNumber, + ); + }, [data, filters, sortBy, pageNumber]); + + // Clamp page number when filtered/mutated list shrinks + React.useEffect(() => { + if (numPages > 0 && pageNumber > numPages) { + setPageNumber(1); + } + }, [numPages, pageNumber, setPageNumber]); + + const courseListData = { + filterOptions: filters, + setPageNumber, + numPages, + visibleList, + showFilters: filters.length > 0, + }; + return (

{formatMessage(messages.myCourses)}

- +
{hasCourses ? : } diff --git a/src/containers/CoursesPanel/index.scss b/src/containers/CoursesPanel/index.scss index 43f2d974d..c8d21343a 100644 --- a/src/containers/CoursesPanel/index.scss +++ b/src/containers/CoursesPanel/index.scss @@ -1,4 +1,4 @@ -@import "@openedx/paragon/scss/core/core"; +@use "@openedx/paragon/styles/css/core/custom-media-breakpoints.css"; .course-list-heading-container { display: flex; @@ -8,8 +8,8 @@ } .course-list-title { - font-size: $h2-font-size; - margin: map-get($spacers, 3) 0; + font-size: var(--pgn-typography-font-size-h2-base); + margin: var(--pgn-spacing-spacer-3) 0; } .course-list-loading { @@ -24,7 +24,7 @@ align-self: center; } -@include media-breakpoint-down(md) { +@media (--pgn-size-breakpoint-max-width-md) { .course-list-heading-container { flex-direction: column-reverse; align-items: flex-start; @@ -36,7 +36,7 @@ } .course-list-title { - font-size: $h3-font-size; - margin: map-get($spacers, 2) 0; + font-size: var(--pgn-typography-font-size-h3-base); + margin: var(--pgn-spacing-spacer-2) 0; } } diff --git a/src/containers/CoursesPanel/index.test.jsx b/src/containers/CoursesPanel/index.test.jsx index 5917f14a1..f16057780 100644 --- a/src/containers/CoursesPanel/index.test.jsx +++ b/src/containers/CoursesPanel/index.test.jsx @@ -1,58 +1,153 @@ -import { shallow } from '@edx/react-unit-test-utils'; - -import { reduxHooks } from 'hooks'; -import { useCourseListData } from './hooks'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; +import { MemoryRouter } from 'react-router'; +import { useInitializeLearnerHome } from '@src/data/hooks'; +import { useFilters } from '@src/data/context'; +import * as dataTransformers from '@src/utils/dataTransformers'; +import messagesNoCourses from '@src/containers/CoursesPanel/NoCoursesView/messages'; import CoursesPanel from '.'; +import messages from './messages'; -jest.mock('hooks', () => ({ - reduxHooks: { useHasCourses: jest.fn() }, +jest.mock('@src/data/hooks', () => ({ + useInitializeLearnerHome: jest.fn(() => ({ + data: { + courses: [{ id: 1 }, { id: 2 }], + coursesByCardId: { 'card-0': { id: 1, cardId: 'card-0' }, 'card-1': { id: 2, cardId: 'card-1' } }, + }, + })), })); -jest.mock('./hooks', () => ({ - useCourseListData: jest.fn(), +jest.mock('@src/data/context', () => ({ + useFilters: jest.fn(() => ({ + filters: [], + sortBy: 'enrolled', + pageNumber: 1, + setPageNumber: jest.fn(), + })), })); -jest.mock('containers/CourseCard', () => 'CourseCard'); -jest.mock('containers/CourseFilterControls', () => ({ - ActiveCourseFilters: 'ActiveCourseFilters', - CourseFilterControls: 'CourseFilterControls', -})); -jest.mock('@openedx/frontend-plugin-framework', () => ({ - PluginSlot: 'PluginSlot', +jest.mock('@src/containers/CourseCard', () => jest.fn(() =>
CourseCard
)); +jest.mock('@src/containers/CourseFilterControls', () => ({ + ActiveCourseFilters: jest.fn(() =>
ActiveCourseFilters
), + CourseFilterControls: jest.fn(() =>
CourseFilterControls
), })); -jest.mock('./CourseList', () => 'CourseList'); -reduxHooks.useHasCourses.mockReturnValue(true); describe('CoursesPanel', () => { - const defaultCourseListData = { - filterOptions: {}, - numPages: 1, - setPageNumber: jest.fn().mockName('setPageNumber'), - showFilters: false, - visibleList: [], - }; - const createWrapper = (courseListData) => { - useCourseListData.mockReturnValueOnce({ - ...defaultCourseListData, - ...courseListData, - }); - return shallow(); + const visibleList = courseListData?.visibleList || []; + const coursesByCardId = Object.fromEntries(visibleList.map((c, i) => [`card-${i}`, { ...c, cardId: c.cardId || `card-${i}` }])); + useInitializeLearnerHome.mockReturnValue({ data: { courses: visibleList, coursesByCardId } }); + return render(); }; describe('no courses', () => { - test('snapshot', () => { - reduxHooks.useHasCourses.mockReturnValue(false); - const wrapper = createWrapper(); - expect(wrapper.snapshot).toMatchSnapshot(); + it('should render no courses view slot', () => { + createWrapper(); + const imgNoCourses = screen.getByRole('img', { name: messagesNoCourses.bannerAlt.defaultMessage }); + expect(imgNoCourses).toBeInTheDocument(); + const courseCard = screen.queryByText('CourseCard'); + expect(courseCard).toBeNull(); }); }); describe('with courses', () => { - test('snapshot', () => { - reduxHooks.useHasCourses.mockReturnValue(true); - const wrapper = createWrapper(); - expect(wrapper.snapshot).toMatchSnapshot(); + it('should render courselist', () => { + const visibleList = [{ cardId: 'foo' }, { cardId: 'bar' }, { cardId: 'baz' }]; + createWrapper({ visibleList }); + const courseCards = screen.getAllByText('CourseCard'); + expect(courseCards.length).toEqual(visibleList.length); + }); + it('displays course filter controls', () => { + createWrapper(); + expect(screen.getByText('CourseFilterControls')).toBeInTheDocument(); + }); + + it('displays course list slot when courses exist', () => { + const visibleList = [{ cardId: 'foo' }, { cardId: 'bar' }, { cardId: 'baz' }]; + createWrapper({ visibleList }); + const heading = screen.getByText(messages.myCourses.defaultMessage); + expect(heading).toBeInTheDocument(); + }); + }); + + describe('page number clamping', () => { + const mockSetPageNumber = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + jest.spyOn(dataTransformers, 'getVisibleList'); + }); + + it('clamps page number to 1 when current page exceeds total pages', () => { + useFilters.mockReturnValue({ + filters: [], + sortBy: 'enrolled', + pageNumber: 5, // User is on page 5 + setPageNumber: mockSetPageNumber, + }); + + dataTransformers.getVisibleList.mockReturnValue({ + visibleList: [{ id: 1 }], + numPages: 2, + }); + + createWrapper({ visibleList: [{ id: 1 }] }); + + expect(mockSetPageNumber).toHaveBeenCalledWith(1); + }); + + it('does not clamp page number when current page is valid', () => { + useFilters.mockReturnValue({ + filters: [], + sortBy: 'enrolled', + pageNumber: 2, + setPageNumber: mockSetPageNumber, + }); + + dataTransformers.getVisibleList.mockReturnValue({ + visibleList: [{ id: 1 }], + numPages: 3, + }); + + createWrapper({ visibleList: [{ id: 1 }] }); + + expect(mockSetPageNumber).not.toHaveBeenCalled(); + }); + + it('does not clamp when numPages is 0', () => { + useFilters.mockReturnValue({ + filters: [], + sortBy: 'enrolled', + pageNumber: 2, + setPageNumber: mockSetPageNumber, + }); + + dataTransformers.getVisibleList.mockReturnValue({ + visibleList: [], + numPages: 0, + }); + + createWrapper({ visibleList: [] }); + + expect(mockSetPageNumber).not.toHaveBeenCalled(); + }); + + it('handles edge case when pageNumber equals numPages', () => { + useFilters.mockReturnValue({ + filters: [], + sortBy: 'enrolled', + pageNumber: 2, + setPageNumber: mockSetPageNumber, + }); + + dataTransformers.getVisibleList.mockReturnValue({ + visibleList: [{ id: 1 }], + numPages: 2, + }); + + createWrapper({ visibleList: [{ id: 1 }] }); + + expect(mockSetPageNumber).not.toHaveBeenCalled(); }); }); }); diff --git a/src/containers/CoursesPanel/messages.js b/src/containers/CoursesPanel/messages.js index ec73e1ce4..0d55a0e1f 100644 --- a/src/containers/CoursesPanel/messages.js +++ b/src/containers/CoursesPanel/messages.js @@ -1,4 +1,4 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ myCourses: { diff --git a/src/containers/Dashboard/DashboardLayout.jsx b/src/containers/Dashboard/DashboardLayout.jsx index 4c6823685..bb19e6325 100644 --- a/src/containers/Dashboard/DashboardLayout.jsx +++ b/src/containers/Dashboard/DashboardLayout.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { Container, Col, Row } from '@openedx/paragon'; -import WidgetSidebarSlot from 'plugin-slots/WidgetSidebarSlot'; +import WidgetSidebarSlot from '../../slots/WidgetSidebarSlot'; import hooks from './hooks'; diff --git a/src/containers/Dashboard/DashboardLayout.test.jsx b/src/containers/Dashboard/DashboardLayout.test.jsx index fabb98467..fb1a93cd1 100644 --- a/src/containers/Dashboard/DashboardLayout.test.jsx +++ b/src/containers/Dashboard/DashboardLayout.test.jsx @@ -1,14 +1,25 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; -import { Col, Row } from '@openedx/paragon'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; +import { MemoryRouter } from 'react-router'; import hooks from './hooks'; -import DashboardLayout, { columnConfig } from './DashboardLayout'; +import DashboardLayout from './DashboardLayout'; + +jest.mock('@src/data/hooks', () => ({ + useInitializeLearnerHome: jest.fn().mockReturnValue({ + data: { + platformSettings: { + courseSearchUrl: '/courses', + }, + }, + }), +})); jest.mock('./hooks', () => ({ useDashboardLayoutData: jest.fn(), })); + const hookProps = { isCollapsed: true, sidebarShowing: false, @@ -16,50 +27,50 @@ const hookProps = { }; hooks.useDashboardLayoutData.mockReturnValue(hookProps); -const children = 'test-children'; +const children =
test children
; -let el; describe('DashboardLayout', () => { beforeEach(() => { jest.clearAllMocks(); - el = shallow({children}); + render({children}); }); const testColumns = () => { - it('loads courseList and sidebar column layout', () => { - const columns = el.instance.findByType(Row)[0].findByType(Col); - Object.keys(columnConfig.sidebar).forEach(size => { - expect(columns[1].props[size]).toEqual(columnConfig.sidebar[size]); - }); + it('loads courseList and sidebar column layout with corresponding children', () => { + const courseChildren = screen.getByText('test children'); + const courseListCol = courseChildren.parentElement; + const sidebarCol = courseListCol.nextSibling; + expect(courseListCol).toHaveClass('course-list-column'); + expect(sidebarCol).toHaveClass('sidebar-column'); }); it('displays children in first column', () => { - const columns = el.instance.findByType(Row)[0].findByType(Col); - expect(columns[0].children).not.toHaveLength(0); + const courseChildren = screen.getByText('test children'); + const courseListCol = courseChildren.parentElement; + expect(courseChildren).toBeInTheDocument(); + expect(courseListCol).toHaveClass('course-list-column'); }); it('displays WidgetSidebarSlot in second column', () => { - const columns = el.instance.findByType(Row)[0].findByType(Col); - expect(columns[1].findByType('WidgetSidebarSlot')).toHaveLength(1); + const courseListCol = screen.getByText('test children').parentElement; + const sidebarCol = courseListCol.nextSibling; + expect(sidebarCol).toHaveClass('sidebar-column'); + // Slot renders its default children directly (no wrapper with slot id) + expect(sidebarCol.children.length).toBeGreaterThan(0); }); }; - const testSidebarLayout = () => { + const testSidebarLayout = ({ isCollapsed }) => { it('displays withSidebar width for course list column', () => { - const columns = el.instance.findByType(Row)[0].findByType(Col); - Object.keys(columnConfig.courseList.withSidebar).forEach(size => { - expect(columns[0].props[size]).toEqual(columnConfig.courseList.withSidebar[size]); - }); + const courseListCol = screen.getByText('test children').parentElement; + expect(courseListCol).toHaveClass('col-xl-8'); + const sidebarCol = courseListCol.nextSibling; + expect(sidebarCol).toHaveClass('sidebar-column', !isCollapsed && 'not-collapsed'); }); }; - const testNoSidebarLayout = () => { + const testNoSidebarLayout = ({ isCollapsed }) => { it('displays noSidebar width for course list column', () => { - const columns = el.instance.findByType(Row)[0].findByType(Col); - Object.keys(columnConfig.courseList.noSidebar).forEach(size => { - expect(columns[0].props[size]).toEqual(columnConfig.courseList.noSidebar[size]); - }); - }); - }; - const testSnapshot = () => { - test('snapshot', () => { - expect(el.snapshot).toMatchSnapshot(); + const courseListCol = screen.getByText('test children').parentElement; + expect(courseListCol).toHaveClass('col-xl-12'); + const sidebarCol = courseListCol.nextSibling; + expect(sidebarCol).toHaveClass('sidebar-column', !isCollapsed && 'not-collapsed'); }); }; describe('collapsed', () => { @@ -68,27 +79,18 @@ describe('DashboardLayout', () => { hooks.useDashboardLayoutData.mockReturnValueOnce({ ...hookProps, sidebarShowing: true }); }); testColumns(); - testSnapshot(); - testSidebarLayout(); + testSidebarLayout({ isCollapsed: true }); }); describe('sidebar not showing', () => { + beforeEach(() => { + hooks.useDashboardLayoutData.mockReturnValueOnce({ ...hookProps }); + }); testColumns(); - testSnapshot(); - testNoSidebarLayout(); - }); - it('does not show spacer component above widget sidebar', () => { - const columns = el.instance.findByType(Col); - expect(columns[1].findByType('h2').length).toEqual(0); + testNoSidebarLayout({ isCollapsed: true }); }); }); describe('not collapsed', () => { - const testWidgetSpacing = () => { - it('shows not-collapsed class on widget sidebar', () => { - const columns = el.instance.findByType(Col); - expect(columns[1].props.className).toContain('not-collapsed'); - }); - }; describe('sidebar showing', () => { beforeEach(() => { hooks.useDashboardLayoutData.mockReturnValueOnce({ @@ -98,18 +100,14 @@ describe('DashboardLayout', () => { }); }); testColumns(); - testSnapshot(); - testSidebarLayout(); - testWidgetSpacing(); + testSidebarLayout({ isCollapsed: false }); }); describe('sidebar not showing', () => { beforeEach(() => { hooks.useDashboardLayoutData.mockReturnValueOnce({ ...hookProps, isCollapsed: false }); }); testColumns(); - testSnapshot(); - testNoSidebarLayout(); - testWidgetSpacing(); + testNoSidebarLayout({ isCollapsed: false }); }); }); }); diff --git a/src/containers/Dashboard/LoadingView.test.jsx b/src/containers/Dashboard/LoadingView.test.jsx index 871bbdc3c..bfbace4cb 100644 --- a/src/containers/Dashboard/LoadingView.test.jsx +++ b/src/containers/Dashboard/LoadingView.test.jsx @@ -1,5 +1,4 @@ -import { shallow } from '@edx/react-unit-test-utils'; -import { Spinner } from '@openedx/paragon'; +import { render, screen } from '@testing-library/react'; import hooks from './hooks'; import LoadingView from './LoadingView'; @@ -10,14 +9,10 @@ jest.mock('./hooks', () => ({ const spinnerScreenReaderText = 'test-sr-text'; describe('LoadingView', () => { - beforeEach(() => { - hooks.useDashboardMessages.mockReturnValueOnce({ spinnerScreenReaderText }); - }); - test('snapshot', () => { - expect(shallow().snapshot).toMatchSnapshot(); - }); it('renders spinner component with associated screen reader text', () => { - const wrapper = shallow(); - expect(wrapper.instance.findByType(Spinner)[0].props.screenReaderText).toEqual(spinnerScreenReaderText); + hooks.useDashboardMessages.mockReturnValueOnce({ spinnerScreenReaderText }); + render(); + const loader = screen.getByRole('status'); + expect(loader.children[0].innerHTML).toBe(spinnerScreenReaderText); }); }); diff --git a/src/containers/Dashboard/__snapshots__/DashboardLayout.test.jsx.snap b/src/containers/Dashboard/__snapshots__/DashboardLayout.test.jsx.snap deleted file mode 100644 index c1cd8679f..000000000 --- a/src/containers/Dashboard/__snapshots__/DashboardLayout.test.jsx.snap +++ /dev/null @@ -1,197 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DashboardLayout collapsed sidebar not showing snapshot 1`] = ` - - - - test-children - - - - - - -`; - -exports[`DashboardLayout collapsed sidebar showing snapshot 1`] = ` - - - - test-children - - - - - - -`; - -exports[`DashboardLayout not collapsed sidebar not showing snapshot 1`] = ` - - - - test-children - - - - - - -`; - -exports[`DashboardLayout not collapsed sidebar showing snapshot 1`] = ` - - - - test-children - - - - - - -`; diff --git a/src/containers/Dashboard/__snapshots__/LoadingView.test.jsx.snap b/src/containers/Dashboard/__snapshots__/LoadingView.test.jsx.snap deleted file mode 100644 index cbf1a1226..000000000 --- a/src/containers/Dashboard/__snapshots__/LoadingView.test.jsx.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LoadingView snapshot 1`] = ` -
- -
-`; diff --git a/src/containers/Dashboard/__snapshots__/index.test.jsx.snap b/src/containers/Dashboard/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 9fdbbbddd..000000000 --- a/src/containers/Dashboard/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,69 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Dashboard snapshots courses loaded, show select session modal snapshot 1`] = ` -
-

- test-page-title -

- - - - -
- - - -
-
-`; - -exports[`Dashboard snapshots courses still loading snapshot 1`] = ` -
-

- test-page-title -

-
- -
-
-`; - -exports[`Dashboard snapshots there are no courses snapshot 1`] = ` -
-

- test-page-title -

- - - -
- - - -
-
-`; diff --git a/src/containers/Dashboard/hooks.js b/src/containers/Dashboard/hooks.js index 73ddf34d1..9ececa6f9 100644 --- a/src/containers/Dashboard/hooks.js +++ b/src/containers/Dashboard/hooks.js @@ -1,26 +1,20 @@ import React from 'react'; import { useWindowSize, breakpoints } from '@openedx/paragon'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import { apiHooks } from 'hooks'; -import { StrictDict } from 'utils'; +import { useIntl } from '@openedx/frontend-base'; +import { StrictDict } from '../../utils'; -import appMessages from 'messages'; +import appMessages from '../../messages'; import * as module from './hooks'; export const state = StrictDict({ sidebarShowing: (val) => React.useState(val), // eslint-disable-line }); -export const useInitializeDashboard = () => { - const initialize = apiHooks.useInitializeApp(); - React.useEffect(() => { initialize(); }, []); // eslint-disable-line -}; - export const useDashboardMessages = () => { const { formatMessage } = useIntl(); return { - spinnerScreenReaderText: formatMessage(appMessages.loadingSR), - pageTitle: formatMessage(appMessages.pageTitle), + spinnerScreenReaderText: formatMessage(appMessages['learner-dash.loadingSR']), + pageTitle: formatMessage(appMessages['learner-dash.title']), }; }; @@ -37,6 +31,5 @@ export const useDashboardLayoutData = () => { export default { useDashboardLayoutData, - useInitializeDashboard, useDashboardMessages, }; diff --git a/src/containers/Dashboard/hooks.test.js b/src/containers/Dashboard/hooks.test.js index cce1b6a5f..43ced6361 100644 --- a/src/containers/Dashboard/hooks.test.js +++ b/src/containers/Dashboard/hooks.test.js @@ -1,29 +1,34 @@ -import React from 'react'; - -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import { useWindowSize, breakpoints } from '@openedx/paragon'; -import { apiHooks } from 'hooks'; -import { MockUseState } from 'testUtils'; +import { MockUseState } from '@src/testUtils'; -import appMessages from 'messages'; +import appMessages from '@src/messages'; import * as hooks from './hooks'; jest.mock('@openedx/paragon', () => ({ + ...jest.requireActual('@openedx/paragon'), useWindowSize: jest.fn(), breakpoints: {}, })); -jest.mock('hooks', () => ({ - apiHooks: { - useInitializeApp: jest.fn(), - }, +jest.mock('@openedx/frontend-base', () => { + const { formatMessage } = jest.requireActual('@src/testUtils'); + return { + ...jest.requireActual('@openedx/frontend-base'), + useIntl: () => ({ + formatMessage, + }), + }; +}); + +jest.mock('react', () => ({ + ...jest.requireActual('react'), + useEffect: jest.fn((cb, prereqs) => ({ useEffect: { cb, prereqs } })), })); const state = new MockUseState(hooks); -const initializeApp = jest.fn(); -apiHooks.useInitializeApp.mockReturnValue(initializeApp); useWindowSize.mockReturnValue({ width: 20 }); breakpoints.large = { maxWidth: 30 }; describe('CourseCard hooks', () => { @@ -38,7 +43,9 @@ describe('CourseCard hooks', () => { }); describe('useDashboardLayoutData', () => { - beforeEach(() => { state.mock(); }); + beforeEach(() => { + state.mock(); + }); describe('behavior', () => { it('initializes sidebarShowing to default true value', () => { hooks.useDashboardLayoutData(); @@ -61,25 +68,15 @@ describe('CourseCard hooks', () => { }); }); }); - describe('useInitializeDashboard', () => { - it('dispatches initialize thunk action on component load', () => { - hooks.useInitializeDashboard(); - const [cb, prereqs] = React.useEffect.mock.calls[0]; - expect(prereqs).toEqual([]); - expect(initializeApp).not.toHaveBeenCalled(); - cb(); - expect(initializeApp).toHaveBeenCalledWith(); - }); - }); describe('useDashboardMessages', () => { it('returns spinner screen reader text', () => { expect(hooks.useDashboardMessages().spinnerScreenReaderText).toEqual( - formatMessage(appMessages.loadingSR), + formatMessage(appMessages['learner-dash.loadingSR']), ); }); it('returns page title', () => { expect(hooks.useDashboardMessages().pageTitle).toEqual( - formatMessage(appMessages.pageTitle), + formatMessage(appMessages['learner-dash.title']), ); }); }); diff --git a/src/containers/Dashboard/index.jsx b/src/containers/Dashboard/index.jsx index ebdb1ef78..8c61b4988 100644 --- a/src/containers/Dashboard/index.jsx +++ b/src/containers/Dashboard/index.jsx @@ -1,10 +1,10 @@ -import React from 'react'; +import React, { useMemo } from 'react'; -import { reduxHooks } from 'hooks'; -import { RequestKeys } from 'data/constants/requests'; -import SelectSessionModal from 'containers/SelectSessionModal'; -import CoursesPanel from 'containers/CoursesPanel'; -import DashboardModalSlot from 'plugin-slots/DashboardModalSlot'; +import { useSelectSessionModal } from '@src/data/context'; +import { useInitializeLearnerHome } from '@src/data/hooks'; +import SelectSessionModal from '../../containers/SelectSessionModal'; +import CoursesPanel from '../../containers/CoursesPanel'; +import DashboardModalSlot from '../../slots/DashboardModalSlot'; import LoadingView from './LoadingView'; import DashboardLayout from './DashboardLayout'; @@ -12,30 +12,35 @@ import hooks from './hooks'; import './index.scss'; export const Dashboard = () => { - hooks.useInitializeDashboard(); + const { data, isPending } = useInitializeLearnerHome(); const { pageTitle } = hooks.useDashboardMessages(); - const hasCourses = reduxHooks.useHasCourses(); - const initIsPending = reduxHooks.useRequestIsPending(RequestKeys.initialize); - const showSelectSessionModal = reduxHooks.useShowSelectSessionModal(); + const { selectSessionModal } = useSelectSessionModal(); + const showSelectSessionModal = selectSessionModal.cardId !== null; + + const hasCourses = useMemo(() => data?.courses?.length > 0, [data]); return ( -
-

{pageTitle}

- {!initIsPending && ( - <> - - {(hasCourses && showSelectSessionModal) && } - - )} -
- {initIsPending - ? () - : ( - - - +
+
+
+

{pageTitle}

+ {!isPending && ( + <> + + {(hasCourses && showSelectSessionModal) && } + )} -
+
+ {isPending + ? () + : ( + + + + )} +
+
+
); }; diff --git a/src/containers/Dashboard/index.scss b/src/containers/Dashboard/index.scss index be09d4631..0943a8930 100644 --- a/src/containers/Dashboard/index.scss +++ b/src/containers/Dashboard/index.scss @@ -1,29 +1,29 @@ -@import "@openedx/paragon/scss/core/core"; +@use "@openedx/paragon/styles/css/core/custom-media-breakpoints.css"; .course-list-column { - padding: 0 map-get($spacers, 4); + padding: 0 var(--pgn-spacing-spacer-4); } .sidebar-column { - padding: 0 map-get($spacers, 3) 0 map-get($spacers, 1); + padding: 0 var(--pgn-spacing-spacer-3) 0 var(--pgn-spacing-spacer-1); &.not-collapsed { - padding-top: map-get($spacers, 2); + padding-top: var(--pgn-spacing-spacer-2); & >:first-child { - margin-top: map-get($spacers, 5\.5); + margin-top: var(--pgn-spacing-spacer-5-5); } } } -@include media-breakpoint-down(lg) { +@media (--pgn-size-breakpoint-max-width-lg) { .sidebar-column { // grid are inheriting dir="ltr" from the body, so we need to override it [dir=ltr] & { - padding: 0 map-get($spacers, 3); + padding: 0 var(--pgn-spacing-spacer-3); } [dir=rtl] & { - padding: 0 map-get($spacers, 3); + padding: 0 var(--pgn-spacing-spacer-3); } } -} \ No newline at end of file +} diff --git a/src/containers/Dashboard/index.test.jsx b/src/containers/Dashboard/index.test.jsx index 71d83a67f..8e009f9cf 100644 --- a/src/containers/Dashboard/index.test.jsx +++ b/src/containers/Dashboard/index.test.jsx @@ -1,121 +1,76 @@ -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; +import { useSelectSessionModal } from '@src/data/context'; +import { useInitializeLearnerHome } from '@src/data/hooks'; -import { reduxHooks } from 'hooks'; - -import SelectSessionModal from 'containers/SelectSessionModal'; -import CoursesPanel from 'containers/CoursesPanel'; - -import DashboardLayout from './DashboardLayout'; -import LoadingView from './LoadingView'; import hooks from './hooks'; import Dashboard from '.'; -jest.mock('hooks', () => ({ - reduxHooks: { - useHasCourses: jest.fn(), - useShowSelectSessionModal: jest.fn(), - useRequestIsPending: jest.fn(), - }, +jest.mock('@src/data/context', () => ({ + useSelectSessionModal: jest.fn(), })); -jest.mock('plugin-slots/DashboardModalSlot', () => 'DashboardModalSlot'); -jest.mock('containers/CoursesPanel', () => 'CoursesPanel'); -jest.mock('./LoadingView', () => 'LoadingView'); -jest.mock('./DashboardLayout', () => 'DashboardLayout'); +jest.mock('@src/data/hooks', () => ({ + useInitializeLearnerHome: jest.fn(), +})); jest.mock('./hooks', () => ({ useInitializeDashboard: jest.fn(), useDashboardMessages: jest.fn(), })); +jest.mock('../../slots/DashboardModalSlot', () => jest.fn(() =>
DashboardModalSlot
)); +jest.mock('@src/containers/CoursesPanel', () => jest.fn(() =>
CoursesPanel
)); +jest.mock('./LoadingView', () => jest.fn(() =>
LoadingView
)); +jest.mock('@src/containers/SelectSessionModal', () => jest.fn(() =>
SelectSessionModal
)); +jest.mock('./DashboardLayout', () => jest.fn(() =>
DashboardLayout
)); + const pageTitle = 'test-page-title'; describe('Dashboard', () => { - beforeEach(() => { + const createWrapper = (props = {}) => { + const { + hasCourses = true, + initIsPending = true, + showSelectSessionModal = true, + } = props; hooks.useDashboardMessages.mockReturnValue({ pageTitle }); - }); - const createWrapper = ({ - hasCourses, - initIsPending, - showSelectSessionModal, - }) => { - reduxHooks.useHasCourses.mockReturnValueOnce(hasCourses); - reduxHooks.useRequestIsPending.mockReturnValueOnce(initIsPending); - reduxHooks.useShowSelectSessionModal.mockReturnValueOnce(showSelectSessionModal); - return shallow(); + const dataMocked = { data: hasCourses ? { courses: [1, 2] } : { courses: [] }, isPending: initIsPending }; + useInitializeLearnerHome.mockReturnValue(dataMocked); + useSelectSessionModal.mockReturnValue({ selectSessionModal: showSelectSessionModal ? { cardId: 1 } : null }); + return render(); }; - let wrapper; - describe('snapshots', () => { - const testTitle = () => { - test('page title is displayed in sr-only h1 tag', () => { - const heading = wrapper.instance.findByType('h1')[0]; - expect(heading.props.className).toEqual('sr-only'); - expect(heading.children[0].el).toEqual(pageTitle); - }); - }; - const testSnapshot = () => { - test('snapshot', () => { - expect(wrapper.snapshot).toMatchSnapshot(); - }); - }; - const testContent = (el) => { - expect(wrapper.instance.findByTestId('dashboard-content')[0].children[0]).toMatchObject(shallow(el)); - }; - - const renderString = (show) => (show ? 'renders' : 'does not render'); - const testView = ({ - props, - content: [contentName, contentEl], - showSelectSessionModal, - }) => { - beforeEach(() => { wrapper = createWrapper(props); }); - testTitle(); - testSnapshot(); - it(`renders ${contentName}`, () => { - testContent(contentEl); - }); - it(`${renderString(showSelectSessionModal)} select session modal`, () => { - expect(wrapper.instance.findByType(SelectSessionModal).length).toEqual(showSelectSessionModal ? 1 : 0); + describe('render', () => { + it('page title is displayed in sr-only h1 tag', () => { + createWrapper(); + const heading = screen.getByText(pageTitle); + expect(heading).toHaveClass('sr-only'); + }); + describe('initIsPending false', () => { + it('should render DashboardModalSlot', () => { + createWrapper({ initIsPending: false }); + const dashboardModalSlot = screen.getByText('DashboardModalSlot'); + expect(dashboardModalSlot).toBeInTheDocument(); }); - }; - describe('courses still loading', () => { - testView({ - props: { - hasCourses: false, - initIsPending: true, - showSelectSessionModal: false, - }, - content: ['LoadingView', ], - showSelectSessionModal: false, + it('should render SelectSessionModal', () => { + createWrapper({ initIsPending: false }); + const selectSessionModal = screen.getByText('SelectSessionModal'); + expect(selectSessionModal).toBeInTheDocument(); }); }); - - describe('courses loaded, show select session modal', () => { - testView({ - props: { - hasCourses: true, - initIsPending: false, - showSelectSessionModal: true, - }, - content: ['LoadedView', ( - - )], - showSelectSessionModal: true, + describe('courses still loading', () => { + it('should render LoadingView', () => { + createWrapper({ initIsPending: true }); + const loadingView = screen.getByText('LoadingView'); + expect(loadingView).toBeInTheDocument(); }); }); - - describe('there are no courses', () => { - testView({ - props: { - hasCourses: false, - initIsPending: false, - showSelectSessionModal: false, - }, - content: ['Dashboard layout with no courses sidebar and content', ( - - )], - showSelectSessionModal: false, + describe('courses loaded', () => { + it('should show dashboard layout', () => { + createWrapper({ initIsPending: false }); + const dashboardLayout = screen.getByText('DashboardLayout'); + expect(dashboardLayout).toBeInTheDocument(); }); }); }); diff --git a/src/containers/EmailSettingsModal/__snapshots__/index.test.jsx.snap b/src/containers/EmailSettingsModal/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 43e447b7c..000000000 --- a/src/containers/EmailSettingsModal/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,133 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EmailSettingsModal render snapshot: emails disabled, show: false 1`] = ` - -
-

- Receive course emails? -

- - Course emails are off - -

- Course emails include important information about your course from instructors. -

- - - - -
-
-`; - -exports[`EmailSettingsModal render snapshot: emails disabled, show: true 1`] = ` - -
-

- Receive course emails? -

- - Course emails are off - -

- Course emails include important information about your course from instructors. -

- - - - -
-
-`; - -exports[`EmailSettingsModal render snapshot: emails enabled, show: true 1`] = ` - -
-

- Receive course emails? -

- - Course emails are on - -

- Course emails include important information about your course from instructors. -

- - - - -
-
-`; diff --git a/src/containers/EmailSettingsModal/hooks.js b/src/containers/EmailSettingsModal/hooks.js index 7e7397dda..5a42fc679 100644 --- a/src/containers/EmailSettingsModal/hooks.js +++ b/src/containers/EmailSettingsModal/hooks.js @@ -1,7 +1,8 @@ import React from 'react'; -import { StrictDict } from 'utils'; -import { reduxHooks, apiHooks } from 'hooks'; +import { StrictDict } from '@src/utils'; +import { useUpdateEmailSettings } from '@src/data/hooks'; +import { useCourseData } from '@src/hooks'; import * as module from './hooks'; @@ -13,13 +14,17 @@ export const useEmailData = ({ closeModal, cardId, }) => { - const { hasOptedOutOfEmail } = reduxHooks.useCardEnrollmentData(cardId); + const courseData = useCourseData(cardId); + const hasOptedOutOfEmail = courseData?.enrollment?.hasOptedOutOfEmail || false; + const courseId = courseData?.courseRun?.courseId; const [isOptedOut, setIsOptedOut] = module.state.toggle(hasOptedOutOfEmail); - const updateEmailSettings = apiHooks.useUpdateEmailSettings(cardId); + const { mutate: updateEmailSettings } = useUpdateEmailSettings(); const onToggle = () => setIsOptedOut(!isOptedOut); const save = () => { - updateEmailSettings(!isOptedOut); - closeModal(); + updateEmailSettings( + { courseId, enable: !isOptedOut }, + { onSuccess: () => closeModal() }, + ); }; return { diff --git a/src/containers/EmailSettingsModal/hooks.test.js b/src/containers/EmailSettingsModal/hooks.test.js deleted file mode 100644 index c70185341..000000000 --- a/src/containers/EmailSettingsModal/hooks.test.js +++ /dev/null @@ -1,71 +0,0 @@ -import { MockUseState } from 'testUtils'; -import { reduxHooks, apiHooks } from 'hooks'; - -import * as hooks from './hooks'; - -jest.mock('hooks', () => ({ - reduxHooks: { - useCardEnrollmentData: jest.fn(), - }, - apiHooks: { - useUpdateEmailSettings: jest.fn(), - }, -})); - -const cardId = 'my-test-course-number'; -const closeModal = jest.fn(); -const updateEmailSettings = jest.fn(); -apiHooks.useUpdateEmailSettings.mockReturnValue(updateEmailSettings); - -const state = new MockUseState(hooks); - -describe('EmailSettingsModal hooks', () => { - let out; - describe('state values', () => { - state.testGetter(state.keys.toggle); - }); - beforeEach(() => { - jest.clearAllMocks(); - }); - describe('useEmailData', () => { - beforeEach(() => { - state.mock(); - reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ hasOptedOutOfEmail: true }); - out = hooks.useEmailData({ closeModal, cardId }); - }); - afterEach(state.restore); - - it('loads enrollment data based on course number', () => { - expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(cardId); - }); - - it('initializes toggle value to cardData.hasOptedOutOfEmail', () => { - state.expectInitializedWith(state.keys.toggle, true); - expect(out.isOptedOut).toEqual(true); - - reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ hasOptedOutOfEmail: false }); - out = hooks.useEmailData({ closeModal, cardId }); - state.expectInitializedWith(state.keys.toggle, false); - expect(out.isOptedOut).toEqual(false); - }); - it('initializes email settings hok with cardId', () => { - expect(apiHooks.useUpdateEmailSettings).toHaveBeenCalledWith(cardId); - }); - describe('onToggle - returned callback', () => { - it('sets toggle state value to opposite current value', () => { - out.onToggle(); - expect(state.setState.toggle).toHaveBeenCalledWith(!out.isOptedOut); - }); - }); - describe('save', () => { - it('calls updateEmailSettings', () => { - out.save(); - expect(updateEmailSettings).toHaveBeenCalledWith(!out.isOptedOut); - }); - it('calls closeModal', () => { - out.save(); - expect(closeModal).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/src/containers/EmailSettingsModal/hooks.test.jsx b/src/containers/EmailSettingsModal/hooks.test.jsx new file mode 100644 index 000000000..781262f86 --- /dev/null +++ b/src/containers/EmailSettingsModal/hooks.test.jsx @@ -0,0 +1,134 @@ +import { renderHook, act } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { useCourseData } from '@src/hooks'; +import * as api from '@src/data/services/lms/api'; + +import { useEmailData } from './hooks'; + +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(() => ({ + enrollment: {}, + })), +})); + +jest.mock('@src/data/services/lms/api', () => ({ + updateEmailSettings: jest.fn(), +})); + +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, + }); + const wrapper = ({ children }) => {children}; + return wrapper; +}; + +const cardId = 'my-test-course-number'; +const closeModal = jest.fn(); + +describe('EmailSettingsModal hooks', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('useEmailData', () => { + it('loads enrollment data based on course number', () => { + useCourseData.mockReturnValue({ enrollment: { hasOptedOutOfEmail: true } }); + + renderHook(() => useEmailData({ closeModal, cardId }), { + wrapper: createWrapper(), + }); + + expect(useCourseData).toHaveBeenCalledWith(cardId); + }); + + it('initializes toggle value to cardData.hasOptedOutOfEmail when true', () => { + useCourseData.mockReturnValue({ enrollment: { hasOptedOutOfEmail: true } }); + + const { result } = renderHook(() => useEmailData({ closeModal, cardId }), { + wrapper: createWrapper(), + }); + + expect(result.current.isOptedOut).toEqual(true); + }); + + it('initializes toggle value to cardData.hasOptedOutOfEmail when false', () => { + useCourseData.mockReturnValue({ enrollment: { hasOptedOutOfEmail: false } }); + + const { result } = renderHook(() => useEmailData({ closeModal, cardId }), { + wrapper: createWrapper(), + }); + + expect(result.current.isOptedOut).toEqual(false); + }); + + it('initializes toggle value to false when hasOptedOutOfEmail is undefined', () => { + useCourseData.mockReturnValue({ enrollment: {} }); + + const { result } = renderHook(() => useEmailData({ closeModal, cardId }), { + wrapper: createWrapper(), + }); + + expect(result.current.isOptedOut).toEqual(false); + }); + + it('toggles state value when onToggle is called', () => { + useCourseData.mockReturnValue({ enrollment: { hasOptedOutOfEmail: true } }); + + const { result } = renderHook(() => useEmailData({ closeModal, cardId }), { + wrapper: createWrapper(), + }); + + expect(result.current.isOptedOut).toEqual(true); + + act(() => { + result.current.onToggle(); + }); + + expect(result.current.isOptedOut).toEqual(false); + + act(() => { + result.current.onToggle(); + }); + + expect(result.current.isOptedOut).toEqual(true); + }); + + it('calls updateEmailSettings api and closeModal when save is called', async () => { + const courseId = 'test-course-id'; + useCourseData.mockReturnValue({ enrollment: { hasOptedOutOfEmail: true }, courseRun: { courseId } }); + api.updateEmailSettings.mockResolvedValue({}); + + const { result } = renderHook(() => useEmailData({ closeModal, cardId }), { + wrapper: createWrapper(), + }); + + await act(async () => { + result.current.save(); + }); + + const expectedArg = { courseId, enable: !result.current.isOptedOut }; + expect(api.updateEmailSettings).toHaveBeenCalledWith(expectedArg); + expect(closeModal).toHaveBeenCalled(); + }); + + it('calls updateEmailSettings with enable:true when isOptedOut is false', async () => { + const courseId = 'test-course-id'; + useCourseData.mockReturnValue({ enrollment: { hasOptedOutOfEmail: false }, courseRun: { courseId } }); + api.updateEmailSettings.mockResolvedValue({}); + + const { result } = renderHook(() => useEmailData({ closeModal, cardId }), { + wrapper: createWrapper(), + }); + + await act(async () => { + result.current.save(); + }); + + expect(api.updateEmailSettings).toHaveBeenCalledWith({ + courseId, + enable: true, + }); + }); + }); +}); diff --git a/src/containers/EmailSettingsModal/index.jsx b/src/containers/EmailSettingsModal/index.jsx index 8e85f9d5f..13d541da0 100644 --- a/src/containers/EmailSettingsModal/index.jsx +++ b/src/containers/EmailSettingsModal/index.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import { ActionRow, Button, @@ -9,7 +9,7 @@ import { ModalDialog, } from '@openedx/paragon'; -import { nullMethod } from 'utils'; +import { nullMethod } from '../../utils'; import useEmailData from './hooks'; import messages from './messages'; diff --git a/src/containers/EmailSettingsModal/index.test.jsx b/src/containers/EmailSettingsModal/index.test.jsx index e20082f6f..6032933a2 100644 --- a/src/containers/EmailSettingsModal/index.test.jsx +++ b/src/containers/EmailSettingsModal/index.test.jsx @@ -1,8 +1,9 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; import hooks from './hooks'; import EmailSettingsModal from '.'; +import messages from './messages'; jest.mock('./hooks', () => ({ __esModule: true, @@ -28,7 +29,7 @@ describe('EmailSettingsModal', () => { describe('behavior', () => { beforeEach(() => { hooks.mockReturnValueOnce(hookProps); - shallow(); + render(); }); it('calls hook w/ closeModal and cardId from props', () => { expect(hooks).toHaveBeenCalledWith({ @@ -38,20 +39,36 @@ describe('EmailSettingsModal', () => { }); }); describe('render', () => { - test('snapshot: emails disabled, show: false', () => { - hooks.mockReturnValueOnce(hookProps); - expect(shallow().snapshot).toMatchSnapshot(); + it('emails disabled, show: false', () => { + hooks.mockReturnValue(hookProps); + render(); + const modal = screen.queryByRole('dialog'); + expect(modal).toBeNull(); }); - test('snapshot: emails disabled, show: true', () => { + it('emails disabled, show: true', () => { hooks.mockReturnValueOnce(hookProps); - expect(shallow().snapshot).toMatchSnapshot(); + render(); + const modal = screen.getByRole('dialog'); + const heading = screen.getByText(messages.header.defaultMessage); + const emailsMsg = screen.getByText(messages.emailsOff.defaultMessage); + expect(modal).toBeInTheDocument(); + expect(heading).toBeInTheDocument(); + expect(emailsMsg).toBeInTheDocument(); }); - test('snapshot: emails enabled, show: true', () => { + it('emails enabled, show: true', () => { hooks.mockReturnValueOnce({ ...hookProps, isOptedOut: false, }); - expect(shallow().snapshot).toMatchSnapshot(); + render(); + const emailsMsg = screen.getByText(messages.emailsOn.defaultMessage); + const description = screen.getByText(messages.description.defaultMessage); + const buttonNeverMind = screen.getByRole('button', { name: messages.nevermind.defaultMessage }); + const buttonSave = screen.getByRole('button', { name: messages.save.defaultMessage }); + expect(emailsMsg).toBeInTheDocument(); + expect(description).toBeInTheDocument(); + expect(buttonNeverMind).toBeInTheDocument(); + expect(buttonSave).toBeInTheDocument(); }); }); }); diff --git a/src/containers/EmailSettingsModal/messages.js b/src/containers/EmailSettingsModal/messages.js index 8c97f0615..7e04d651d 100644 --- a/src/containers/EmailSettingsModal/messages.js +++ b/src/containers/EmailSettingsModal/messages.js @@ -1,5 +1,4 @@ -/* eslint-disable quotes */ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ header: { diff --git a/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/__snapshots__/index.test.jsx.snap b/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 9cff65af5..000000000 --- a/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,72 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ConfirmEmailBanner snapshot Show on unverified 1`] = ` - - - - Confirm Now - , - } - } - /> - - - I've confirmed my email - - } - hasCloseButton={false} - heroNode={ - - confirm email background - - } - isOpen={[MockFunction showConfirmModal]} - onClose={[MockFunction closeConfirmModal]} - title="" - > -

- Confirm your email -

-

- We've sent you an email to verify your account. Please check your inbox and click on the big red button to confirm and keep learning. -

-
-
-`; - -exports[`ConfirmEmailBanner snapshot do not show on already verified 1`] = `null`; diff --git a/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/hooks.test.js b/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/hooks.test.js deleted file mode 100644 index 6d8c562ff..000000000 --- a/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/hooks.test.js +++ /dev/null @@ -1,77 +0,0 @@ -import { MockUseState } from 'testUtils'; -import { reduxHooks, apiHooks } from 'hooks'; - -import * as hooks from './hooks'; - -jest.mock('hooks', () => ({ - reduxHooks: { - useEmailConfirmationData: jest.fn(), - }, - apiHooks: { - useSendConfirmEmail: jest.fn(), - }, -})); - -const sendConfirmEmail = jest.fn(); -apiHooks.useSendConfirmEmail.mockReturnValue(sendConfirmEmail); - -const emailConfirmation = { - isNeeded: true, -}; - -const state = new MockUseState(hooks); - -describe('ConfirmEmailBanner hooks', () => { - let out; - describe('state values', () => { - state.testGetter(state.keys.showPageBanner); - state.testGetter(state.keys.showConfirmModal); - }); - beforeEach(() => { - jest.clearAllMocks(); - }); - describe('useEmailConfirmationData', () => { - beforeEach(() => state.mock()); - afterEach(state.restore); - - test('show page banner on unverified email', () => { - reduxHooks.useEmailConfirmationData.mockReturnValueOnce({ ...emailConfirmation }); - out = hooks.useConfirmEmailBannerData(); - expect(out.isNeeded).toEqual(emailConfirmation.isNeeded); - reduxHooks.useEmailConfirmationData.mockReturnValueOnce({ isNeeded: false }); - }); - - test('hide page banner on verified email', () => { - reduxHooks.useEmailConfirmationData.mockReturnValueOnce({ isNeeded: false }); - out = hooks.useConfirmEmailBannerData(); - expect(out.isNeeded).toEqual(false); - }); - }); - - describe('behavior', () => { - beforeEach(() => { - state.mock(); - reduxHooks.useEmailConfirmationData.mockReturnValueOnce({ ...emailConfirmation }); - out = hooks.useConfirmEmailBannerData(); - }); - afterEach(state.restore); - test('closePageBanner', () => { - out.closePageBanner(); - expect(state.values.showPageBanner).toEqual(false); - }); - test('closeConfirmModal', () => { - out.closeConfirmModal(); - expect(state.values.showConfirmModal).toEqual(false); - }); - test('openConfirmModalButtonClick', () => { - out.openConfirmModalButtonClick(); - expect(state.values.showConfirmModal).toEqual(true); - expect(sendConfirmEmail).toBeCalled(); - }); - test('userConfirmEmailButtonClick', () => { - out.userConfirmEmailButtonClick(); - expect(state.values.showConfirmModal).toEqual(false); - expect(state.values.showPageBanner).toEqual(false); - }); - }); -}); diff --git a/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/index.test.jsx b/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/index.test.jsx deleted file mode 100644 index 958484c91..000000000 --- a/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/index.test.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - -import hooks from './hooks'; -import ConfirmEmailBanner from '.'; - -jest.mock('./hooks', () => ({ - __esModule: true, - default: jest.fn(), -})); - -const hookProps = { - isNeeded: true, - showPageBanner: jest.fn().mockName('showPageBanner'), - closePageBanner: jest.fn().mockName('closePageBanner'), - showConfirmModal: jest.fn().mockName('showConfirmModal'), - closeConfirmModal: jest.fn().mockName('closeConfirmModal'), - openConfirmModalButtonClick: jest.fn().mockName('openConfirmModalButtonClick'), - userConfirmEmailButtonClick: jest.fn().mockName('userConfirmEmailButtonClick'), -}; - -describe('ConfirmEmailBanner', () => { - describe('snapshot', () => { - test('do not show on already verified', () => { - hooks.mockReturnValueOnce({ ...hookProps, isNeeded: false }); - const el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - }); - test('Show on unverified', () => { - hooks.mockReturnValueOnce({ ...hookProps }); - const el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - }); - }); -}); diff --git a/src/containers/LearnerDashboardHeader/LearnerDashboardMenu.jsx b/src/containers/LearnerDashboardHeader/LearnerDashboardMenu.jsx deleted file mode 100644 index f13177ffa..000000000 --- a/src/containers/LearnerDashboardHeader/LearnerDashboardMenu.jsx +++ /dev/null @@ -1,77 +0,0 @@ -import { getConfig } from '@edx/frontend-platform'; - -import urls from 'data/services/lms/urls'; - -import messages from './messages'; - -const getLearnerHeaderMenu = ( - formatMessage, - courseSearchUrl, - authenticatedUser, - exploreCoursesClick, -) => ({ - mainMenu: [ - { - type: 'item', - href: '/', - content: formatMessage(messages.course), - isActive: true, - }, - ...(getConfig().ENABLE_PROGRAMS ? [{ - type: 'item', - href: `${urls.programsUrl()}`, - content: formatMessage(messages.program), - }] : []), - ...(!getConfig().NON_BROWSABLE_COURSES ? [{ - type: 'item', - href: `${urls.baseAppUrl(courseSearchUrl)}`, - content: formatMessage(messages.discoverNew), - onClick: (e) => { - exploreCoursesClick(e); - }, - }] - : []), - ], - secondaryMenu: [ - ...(getConfig().SUPPORT_URL ? [{ - type: 'item', - href: `${getConfig().SUPPORT_URL}`, - content: formatMessage(messages.help), - }] : []), - ], - userMenu: [ - { - heading: '', - items: [ - { - type: 'item', - href: `${getConfig().ACCOUNT_PROFILE_URL}/u/${authenticatedUser?.username}`, - content: formatMessage(messages.profile), - }, - { - type: 'item', - href: `${getConfig().ACCOUNT_SETTINGS_URL}`, - content: formatMessage(messages.account), - }, - ...(getConfig().ORDER_HISTORY_URL ? [{ - type: 'item', - href: getConfig().ORDER_HISTORY_URL, - content: formatMessage(messages.orderHistory), - }] : []), - ], - }, - { - heading: '', - items: [ - { - type: 'item', - href: `${getConfig().LOGOUT_URL}`, - content: formatMessage(messages.signOut), - }, - ], - }, - ], -} -); - -export default getLearnerHeaderMenu; diff --git a/src/containers/LearnerDashboardHeader/__snapshots__/index.test.jsx.snap b/src/containers/LearnerDashboardHeader/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 34bcdc1f9..000000000 --- a/src/containers/LearnerDashboardHeader/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,61 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LearnerDashboardHeader render 1`] = ` - - -
- - -`; diff --git a/src/containers/LearnerDashboardHeader/hooks.js b/src/containers/LearnerDashboardHeader/hooks.js deleted file mode 100644 index 5367ab3b5..000000000 --- a/src/containers/LearnerDashboardHeader/hooks.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import track from 'tracking'; -import { StrictDict } from 'utils'; -import { linkNames } from 'tracking/constants'; - -import getLearnerHeaderMenu from './LearnerDashboardMenu'; - -export const state = StrictDict({ - isOpen: (val) => React.useState(val), // eslint-disable-line -}); - -export const findCoursesNavClicked = (href) => track.findCourses.findCoursesClicked(href, { - linkName: linkNames.learnerHomeNavExplore, -}); - -export const useLearnerDashboardHeaderMenu = ({ - courseSearchUrl, authenticatedUser, exploreCoursesClick, -}) => { - const { formatMessage } = useIntl(); - return getLearnerHeaderMenu(formatMessage, courseSearchUrl, authenticatedUser, exploreCoursesClick); -}; - -export default { - findCoursesNavClicked, - useLearnerDashboardHeaderMenu, -}; diff --git a/src/containers/LearnerDashboardHeader/hooks.test.js b/src/containers/LearnerDashboardHeader/hooks.test.js deleted file mode 100644 index a07a8efcf..000000000 --- a/src/containers/LearnerDashboardHeader/hooks.test.js +++ /dev/null @@ -1,47 +0,0 @@ -import track from 'tracking'; -import { linkNames } from 'tracking/constants'; - -import { MockUseState } from 'testUtils'; - -import * as hooks from './hooks'; - -const state = new MockUseState(hooks); - -const { - findCoursesNavClicked, - useLearnerDashboardHeaderMenu, -} = hooks; - -jest.mock('tracking', () => ({ - findCourses: { - findCoursesClicked: jest.fn(), - }, -})); - -const url = 'http://example.com'; - -describe('LearnerDashboardHeader hooks', () => { - describe('state values', () => { - state.testGetter(state.keys.isOpen); - }); - - describe('findCoursesNavClicked', () => { - test('calls tracking with nav link name', () => { - findCoursesNavClicked(url); - expect(track.findCourses.findCoursesClicked).toHaveBeenCalledWith(url, { - linkName: linkNames.learnerHomeNavExplore, - }); - }); - }); - - describe('getLearnerDashboardHeaderMenu', () => { - test('calls header menu data hook', () => { - const courseSearchUrl = '/courses'; - const authenticatedUser = { - username: 'test', - }; - const learnerHomeHeaderMenu = useLearnerDashboardHeaderMenu({ courseSearchUrl, authenticatedUser }); - expect(learnerHomeHeaderMenu.mainMenu.length).toBe(2); - }); - }); -}); diff --git a/src/containers/LearnerDashboardHeader/index.jsx b/src/containers/LearnerDashboardHeader/index.jsx deleted file mode 100644 index 2cd167658..000000000 --- a/src/containers/LearnerDashboardHeader/index.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; - -import MasqueradeBar from 'containers/MasqueradeBar'; -import { AppContext } from '@edx/frontend-platform/react'; -import Header from '@edx/frontend-component-header'; -import { reduxHooks } from 'hooks'; -import urls from 'data/services/lms/urls'; - -import ConfirmEmailBanner from './ConfirmEmailBanner'; - -import { useLearnerDashboardHeaderMenu, findCoursesNavClicked } from './hooks'; - -import './index.scss'; - -export const LearnerDashboardHeader = () => { - const { authenticatedUser } = React.useContext(AppContext); - const { courseSearchUrl } = reduxHooks.usePlatformSettingsData(); - - const exploreCoursesClick = () => { - findCoursesNavClicked(urls.baseAppUrl(courseSearchUrl)); - }; - - const learnerHomeHeaderMenu = useLearnerDashboardHeaderMenu({ - courseSearchUrl, - authenticatedUser, - exploreCoursesClick, - }); - - return ( - <> - -
- - - ); -}; - -LearnerDashboardHeader.propTypes = {}; - -export default LearnerDashboardHeader; diff --git a/src/containers/LearnerDashboardHeader/index.scss b/src/containers/LearnerDashboardHeader/index.scss deleted file mode 100644 index d8bd8da1b..000000000 --- a/src/containers/LearnerDashboardHeader/index.scss +++ /dev/null @@ -1,38 +0,0 @@ -.dropdown-menu-collapse { - width: 100vw; - position: absolute; - left: 0; -} - -.learner-variant-header { - a { - // needed to make the link not resize the header - border-bottom: 2px solid transparent; - } - .course-link { - border-bottom: 2px solid !important; - } - - .course-link:hover { - border-bottom: inherit !important; - } -} - -.nav-small-menu { - > * { - justify-content: flex-start !important; - - border-radius: 0 !important; - border-top: 1px solid #ddd !important; - - &::after { - content: '\00BB'; - padding-left: 10px; - } - } -} - -.logo { - // copy from legacy dashboard - height: 40px; -} diff --git a/src/containers/LearnerDashboardHeader/index.test.jsx b/src/containers/LearnerDashboardHeader/index.test.jsx deleted file mode 100644 index 6c1e281ee..000000000 --- a/src/containers/LearnerDashboardHeader/index.test.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import { mergeConfig } from '@edx/frontend-platform'; -import { shallow } from '@edx/react-unit-test-utils'; -import Header from '@edx/frontend-component-header'; - -import urls from 'data/services/lms/urls'; -import LearnerDashboardHeader from '.'; -import { findCoursesNavClicked } from './hooks'; - -jest.mock('hooks', () => ({ - reduxHooks: { - usePlatformSettingsData: jest.fn(() => ({ - courseSearchUrl: '/course-search-url', - })), - }, -})); -jest.mock('./hooks', () => ({ - ...jest.requireActual('./hooks'), - findCoursesNavClicked: jest.fn(), -})); -jest.mock('containers/MasqueradeBar', () => 'MasqueradeBar'); -jest.mock('./ConfirmEmailBanner', () => 'ConfirmEmailBanner'); -jest.mock('@edx/frontend-component-header', () => 'Header'); - -describe('LearnerDashboardHeader', () => { - test('render', () => { - mergeConfig({ ORDER_HISTORY_URL: 'test-url' }); - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); - expect(wrapper.instance.findByType('ConfirmEmailBanner')).toHaveLength(1); - expect(wrapper.instance.findByType('MasqueradeBar')).toHaveLength(1); - expect(wrapper.instance.findByType(Header)).toHaveLength(1); - wrapper.instance.findByType(Header)[0].props.mainMenuItems[1].onClick(); - expect(findCoursesNavClicked).toHaveBeenCalledWith(urls.baseAppUrl('/course-search-url')); - expect(wrapper.instance.findByType(Header)[0].props.secondaryMenuItems.length).toBe(0); - }); - - test('should display Help link if SUPPORT_URL is set', () => { - mergeConfig({ SUPPORT_URL: 'http://localhost:18000/support' }); - const wrapper = shallow(); - expect(wrapper.instance.findByType(Header)[0].props.secondaryMenuItems.length).toBe(1); - }); - test('should display Programs link if it is enabled by configuration', () => { - mergeConfig({ ENABLE_PROGRAMS: true }); - const wrapper = shallow(); - expect(wrapper.instance.findByType(Header)[0].props.mainMenuItems.length).toBe(3); - }); - test('should not display Discover New tab if it is disabled by configuration', () => { - mergeConfig({ NON_BROWSABLE_COURSES: true }); - const wrapper = shallow(); - expect(wrapper.instance.findByType(Header)[0].props.mainMenuItems.length).toBe(2); - }); -}); diff --git a/src/containers/MasqueradeBar/__snapshots__/index.test.jsx.snap b/src/containers/MasqueradeBar/__snapshots__/index.test.jsx.snap deleted file mode 100644 index e401b0b80..000000000 --- a/src/containers/MasqueradeBar/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,220 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MasqueradeBar snapshot can masquerade 1`] = ` -
-
- - - - View as: - - - - - - -
-
-`; - -exports[`MasqueradeBar snapshot can masquerade with input 1`] = ` -
-
- - - - View as: - - - - - - -
-
-`; - -exports[`MasqueradeBar snapshot cannot masquerade 1`] = `null`; - -exports[`MasqueradeBar snapshot is masquerading failed with error 1`] = ` -
-
- - - - View as: - - - - - - - -
-
-`; - -exports[`MasqueradeBar snapshot is masquerading pending 1`] = ` -
-
- - - - View as: - - - - - - -
-
-`; - -exports[`MasqueradeBar snapshot is masquerading with input 1`] = ` -
-
- - - - Viewing as: - - - test - - -
-
-`; diff --git a/src/containers/MasqueradeBar/hooks.js b/src/containers/MasqueradeBar/hooks.js deleted file mode 100644 index 4310c2dab..000000000 --- a/src/containers/MasqueradeBar/hooks.js +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import { useIntl } from '@edx/frontend-platform/i18n'; - -import { apiHooks, reduxHooks } from 'hooks'; -import { StrictDict } from 'utils'; -import * as module from './hooks'; - -import messages from './messages'; - -export const state = StrictDict({ - masqueradeInput: (val) => React.useState(val), // eslint-disable-line -}); - -export const useMasqueradeInput = () => { - const [masqueradeInput, setMasqueradeInput] = module.state.masqueradeInput(''); - const handleMasqueradeInputChange = (e) => setMasqueradeInput(e.target.value); - return { - handleMasqueradeInputChange, - masqueradeInput, - }; -}; - -const masqueradeErrorMessageMap = { - 404: messages.NoStudentFound, -}; - -export const getMasqueradeErrorMessage = (errorStatus) => { - if (errorStatus == null) { - return null; - } - return masqueradeErrorMessageMap[errorStatus] || messages.UnknownError; -}; - -export const useMasqueradeBarData = ({ - authenticatedUser, -}) => { - const { formatMessage } = useIntl(); - const handleMasqueradeAs = apiHooks.useMasqueradeAs(); - const handleClearMasquerade = apiHooks.useClearMasquerade(); - - const { - isMasquerading, - isMasqueradingFailed, - isMasqueradingPending, - masqueradeErrorStatus, - } = reduxHooks.useMasqueradeData(); - const { masqueradeInput, handleMasqueradeInputChange } = module.useMasqueradeInput(); - - const masqueradeErrorMessage = getMasqueradeErrorMessage(masqueradeErrorStatus); - const handleMasqueradeSubmit = (user) => (e) => { - handleMasqueradeAs(user); - e.preventDefault(); - }; - - return { - canMasquerade: authenticatedUser?.administrator, - isMasquerading, - isMasqueradingFailed, - isMasqueradingPending, - masqueradeErrorMessage, - masqueradeInput, - handleMasqueradeSubmit, - handleClearMasquerade, - handleMasqueradeInputChange, - formatMessage, - }; -}; - -export default useMasqueradeBarData; diff --git a/src/containers/MasqueradeBar/hooks.test.js b/src/containers/MasqueradeBar/hooks.test.js deleted file mode 100644 index e1c35f185..000000000 --- a/src/containers/MasqueradeBar/hooks.test.js +++ /dev/null @@ -1,102 +0,0 @@ -import { MockUseState } from 'testUtils'; -import { apiHooks, reduxHooks } from 'hooks'; - -import * as hooks from './hooks'; -import messages from './messages'; - -jest.mock('hooks', () => ({ - apiHooks: { - useMasqueradeAs: jest.fn(), - useClearMasquerade: jest.fn(), - }, - reduxHooks: { - useMasqueradeData: jest.fn(), - }, -})); - -const masqueradeAs = jest.fn(); -const clearMasquerade = jest.fn(); -apiHooks.useMasqueradeAs.mockReturnValue(masqueradeAs); -apiHooks.useClearMasquerade.mockReturnValue(clearMasquerade); -const state = new MockUseState(hooks); -const testValue = 'test-value'; - -describe('MasqueradeBar hooks', () => { - const authenticatedUser = { - administrator: true, - }; - const defaultMasqueradeData = { - isMasquerading: false, - isMasqueradingFailed: false, - isMasqueradingPending: false, - masqueradeErrorStatus: null, - }; - const createHook = (masqueradeData = {}, user = undefined) => { - reduxHooks.useMasqueradeData.mockReturnValueOnce({ - ...defaultMasqueradeData, - ...masqueradeData, - }); - return hooks.useMasqueradeBarData({ authenticatedUser: user || authenticatedUser }); - }; - - describe('state values', () => { - state.testGetter(state.keys.masqueradeInput); - }); - describe('useMasqueradeBarData', () => { - beforeEach(() => state.mock()); - afterEach(state.restore); - test('canMasquerade', () => { - const out = createHook(); - expect(out.canMasquerade).toEqual(true); - }); - test('cannotMasquerade', () => { - const out = createHook({}, { administrator: false }); - expect(out.canMasquerade).toEqual(false); - }); - test('masqueradeErrorStatus', () => { - let out = createHook(); - expect(out.masqueradeErrorMessage).toBeNull(); - out = createHook({ masqueradeErrorStatus: 0 }); - expect(out.masqueradeErrorMessage).not.toBeNull(); - }); - test('isMasqueradePending', () => { - let out = createHook(); - expect(out.isMasqueradingPending).toEqual(false); - out = createHook({ isMasqueradingPending: true }); - expect(out.isMasqueradingPending).toEqual(true); - }); - test('handleMasqueradeInputChange', () => { - const out = createHook(); - expect(state.stateVals.masqueradeInput).toEqual(''); - out.handleMasqueradeInputChange({ target: { value: testValue } }); - expect(state.setState.masqueradeInput).toHaveBeenCalledWith(testValue); - }); - test('handleMasqueradeSubmit', () => { - const out = createHook(); - const preventDefault = jest.fn(); - // make sure submit doesn't refresh the page - out.handleMasqueradeSubmit(testValue)({ - preventDefault, - }); - expect(masqueradeAs).toHaveBeenCalledWith(testValue); - expect(preventDefault).toHaveBeenCalled(); - }); - test('handleClearMasquerade', () => { - const out = createHook(); - out.handleClearMasquerade(); - expect(clearMasquerade).toHaveBeenCalled(); - }); - }); - - describe('getMasqueradeErrorMessage', () => { - test('null', () => { - expect(hooks.getMasqueradeErrorMessage()).toBeNull(); - }); - test('404', () => { - expect(hooks.getMasqueradeErrorMessage(404)).toEqual(messages.NoStudentFound); - }); - test('default', () => { - expect(hooks.getMasqueradeErrorMessage(500)).toEqual(messages.UnknownError); - }); - }); -}); diff --git a/src/containers/MasqueradeBar/index.scss b/src/containers/MasqueradeBar/index.scss deleted file mode 100644 index e98059fba..000000000 --- a/src/containers/MasqueradeBar/index.scss +++ /dev/null @@ -1,38 +0,0 @@ -@import "@openedx/paragon/scss/core/core"; - -.masquerade-bar { - display: flex; - align-items: flex-start; - padding: map-get($spacers, 3); - margin-bottom: map-get($spacers, 2); - - .masquerade-form-label { - padding: map-get($spacers, 2) map-get($spacers, 3); - display: flex; - align-items: center; - margin-bottom: 0; - white-space: nowrap; - - &>.pgn__icon { - margin-right: map-get($spacers, 3); - } - } - - .masquerade-form-input { - margin-bottom: 0; - flex-grow: 1; - max-width: map-get($grid-breakpoints, 'md'); - } - - .masquerade-chip { - padding: map-get($spacers, 2) map-get($spacers, 3); - font-size: $font-size-base; - } -} - -@include media-breakpoint-down(md) { - .masquerade-bar { - margin: auto; - padding: map-get($spacers, 3) map-get($spacers, 4); - } -} \ No newline at end of file diff --git a/src/containers/MasqueradeBar/index.test.jsx b/src/containers/MasqueradeBar/index.test.jsx deleted file mode 100644 index f2643cc1f..000000000 --- a/src/containers/MasqueradeBar/index.test.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; -import { formatMessage } from 'testUtils'; - -import MasqueradeBar from '.'; -import hooks from './hooks'; - -jest.mock('./hooks', () => ({ - useMasqueradeBarData: jest.fn(), -})); - -describe('MasqueradeBar', () => { - const masqueradeMockData = { - canMasquerade: true, - isMasquerading: false, - isMasqueradingFailed: false, - isMasqueradingPending: false, - masqueradeInput: '', - masqueradeErrorMessage: '', - handleMasqueradeInputChange: jest.fn().mockName('handleMasqueradeInputChange'), - handleClearMasquerade: jest.fn().mockName('handleClearMasquerade'), - handleMasqueradeSubmit: jest.fn().mockName('handleMasqueradeSubmit'), - formatMessage, - }; - - describe('snapshot', () => { - test('can masquerade', () => { - hooks.useMasqueradeBarData.mockReturnValueOnce(masqueradeMockData); - expect(shallow().snapshot).toMatchSnapshot(); - }); - test('can masquerade with input', () => { - hooks.useMasqueradeBarData.mockReturnValueOnce({ - ...masqueradeMockData, - masqueradeInput: 'test', - }); - expect(shallow().snapshot).toMatchSnapshot(); - }); - test('cannot masquerade', () => { - hooks.useMasqueradeBarData.mockReturnValueOnce({ - ...masqueradeMockData, - canMasquerade: false, - }); - expect(shallow().snapshot).toMatchSnapshot(); - }); - test('is masquerading with input', () => { - hooks.useMasqueradeBarData.mockReturnValueOnce({ - ...masqueradeMockData, - isMasquerading: true, - masqueradeInput: 'test', - }); - expect(shallow().snapshot).toMatchSnapshot(); - }); - test('is masquerading failed with error', () => { - hooks.useMasqueradeBarData.mockReturnValueOnce({ - ...masqueradeMockData, - isMasqueradingFailed: true, - masqueradeErrorMessage: 'test-error', - }); - expect(shallow().snapshot).toMatchSnapshot(); - }); - test('is masquerading pending', () => { - hooks.useMasqueradeBarData.mockReturnValueOnce({ - ...masqueradeMockData, - isMasqueradingPending: true, - }); - expect(shallow().snapshot).toMatchSnapshot(); - }); - }); -}); diff --git a/src/containers/RelatedProgramsModal/__snapshots__/index.test.jsx.snap b/src/containers/RelatedProgramsModal/__snapshots__/index.test.jsx.snap deleted file mode 100644 index e2d06eaee..000000000 --- a/src/containers/RelatedProgramsModal/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,169 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RelatedProgramsModal snapshot: closed 1`] = ` - - - Related Programs - - - -

- Are you looking to expand your knowledge? Enrolling in a Program lets you take a series of courses in the subject that you're interested in -

- - - - - - - - - - - - - -
-
-`; - -exports[`RelatedProgramsModal snapshot: open 1`] = ` - - - Related Programs - - - -

- Are you looking to expand your knowledge? Enrolling in a Program lets you take a series of courses in the subject that you're interested in -

- - - - - - - - - - - - - -
-
-`; diff --git a/src/containers/RelatedProgramsModal/components/ProgramCard.jsx b/src/containers/RelatedProgramsModal/components/ProgramCard.jsx index 101d9571f..959dfad4a 100644 --- a/src/containers/RelatedProgramsModal/components/ProgramCard.jsx +++ b/src/containers/RelatedProgramsModal/components/ProgramCard.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import { Badge, Card, diff --git a/src/containers/RelatedProgramsModal/components/ProgramCard.test.jsx b/src/containers/RelatedProgramsModal/components/ProgramCard.test.jsx index 638c7f462..77a01c180 100644 --- a/src/containers/RelatedProgramsModal/components/ProgramCard.test.jsx +++ b/src/containers/RelatedProgramsModal/components/ProgramCard.test.jsx @@ -1,23 +1,43 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; import ProgramCard from './ProgramCard'; const props = { data: { numberOfCourses: 2, - bannerImgSrc: 'props.data.bannerImgSrc', - logoImgSrc: 'props.data.logoImgSrc', - title: 'props.data.title', - provider: 'props.data.provider', - programType: 'props.data.programType', - programUrl: 'props.data.programUrl', - programTypeUrl: 'props.data.programTypeUrl', + bannerImgSrc: 'test bannerImgSrc', + logoImgSrc: 'test logoImgSrc', + title: 'test title', + provider: 'test provider', + programType: 'test programType', + programUrl: 'test programUrl', + programTypeUrl: 'test programTypeUrl', }, }; describe('RelatedProgramsModal ProgramCard', () => { - test('snapshot', () => { - expect(shallow().snapshot).toMatchSnapshot(); + describe('renders', () => { + beforeEach(() => render()); + it('bannerImg and logo', () => { + const logo = screen.getByRole('img', { name: `${props.data.provider} logo` }); + const bannerImg = screen.getByRole('img', { name: /bannerAlt/i }); + expect(logo).toBeInTheDocument(); + expect(bannerImg).toBeInTheDocument(); + }); + it('title and subtitle', () => { + const title = screen.getByText(props.data.title); + const subtitle = screen.getByText(props.data.provider); + expect(title).toBeInTheDocument(); + expect(subtitle).toBeInTheDocument(); + }); + it('badge', () => { + const badge = screen.getByText(props.data.programType); + expect(badge).toBeInTheDocument(); + }); + it('courses number', () => { + const coursesNumber = screen.getByText(`${props.data.numberOfCourses} Courses`); + expect(coursesNumber).toBeInTheDocument(); + }); }); }); diff --git a/src/containers/RelatedProgramsModal/components/__snapshots__/ProgramCard.test.jsx.snap b/src/containers/RelatedProgramsModal/components/__snapshots__/ProgramCard.test.jsx.snap deleted file mode 100644 index 5c44d9adf..000000000 --- a/src/containers/RelatedProgramsModal/components/__snapshots__/ProgramCard.test.jsx.snap +++ /dev/null @@ -1,60 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RelatedProgramsModal ProgramCard snapshot 1`] = ` - - - - props.data.provider - - } - title={ - - props.data.title - - } - /> -
- - - - props.data.programType - -
- 2 Courses -
-
-
-`; diff --git a/src/containers/RelatedProgramsModal/components/messages.js b/src/containers/RelatedProgramsModal/components/messages.js index 04c19d318..de9d6daa8 100644 --- a/src/containers/RelatedProgramsModal/components/messages.js +++ b/src/containers/RelatedProgramsModal/components/messages.js @@ -17,7 +17,7 @@ export const messages = { bannerAlt: { id: 'learnerDashboard.programCard.bannerAlt', defaultMessage: '', - description: 'Program banner logo alt-text', + description: 'Program banner image alt-text', }, }; diff --git a/src/containers/RelatedProgramsModal/hooks.js b/src/containers/RelatedProgramsModal/hooks.js deleted file mode 100644 index 5c4105dac..000000000 --- a/src/containers/RelatedProgramsModal/hooks.js +++ /dev/null @@ -1,10 +0,0 @@ -import { reduxHooks } from 'hooks'; - -export const useProgramData = ({ - cardId, -}) => ({ - courseTitle: reduxHooks.useCardCourseData(cardId).title, - relatedPrograms: reduxHooks.useCardRelatedProgramsData(cardId).list, -}); - -export default useProgramData; diff --git a/src/containers/RelatedProgramsModal/index.jsx b/src/containers/RelatedProgramsModal/index.jsx index 3d5c10d4a..a5103f2db 100644 --- a/src/containers/RelatedProgramsModal/index.jsx +++ b/src/containers/RelatedProgramsModal/index.jsx @@ -2,12 +2,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import { Container, Row, Col, ModalDialog, } from '@openedx/paragon'; -import { reduxHooks } from 'hooks'; +import { useCourseData } from '@src/hooks'; import ProgramCard from './components/ProgramCard'; import messages from './messages'; import './index.scss'; @@ -18,8 +18,9 @@ export const RelatedProgramsModal = ({ cardId, }) => { const { formatMessage } = useIntl(); - const { courseName } = reduxHooks.useCardCourseData(cardId); - const relatedPrograms = reduxHooks.useCardRelatedProgramsData(cardId).list; + const courseData = useCourseData(cardId); + const courseName = courseData?.course?.courseName; + const relatedPrograms = courseData?.programs?.relatedPrograms || []; return ( 'ProgramCard'); -jest.mock('hooks', () => ({ - reduxHooks: { - useCardCourseData: jest.fn(), - useCardRelatedProgramsData: jest.fn(), - }, +jest.mock('./components/ProgramCard', () => jest.fn(() =>
ProgramCard
)); +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(), })); -const cardId = 'test-course-number'; +const cardId = 'test-card-id'; const courseData = { - courseTitle: 'hookProps.courseTitle', + courseName: 'test course', }; const programData = { list: [ @@ -41,20 +39,37 @@ const props = { describe('RelatedProgramsModal', () => { beforeEach(() => { - reduxHooks.useCardCourseData.mockReturnValueOnce(courseData); - reduxHooks.useCardRelatedProgramsData.mockReturnValueOnce(programData); + const mockedData = { + course: { + courseName: courseData.courseName, + }, + programs: { + relatedPrograms: programData.list, + }, + }; + useCourseData.mockReturnValueOnce(mockedData); }); it('initializes hooks with cardId', () => { - shallow(); - expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(cardId); - expect(reduxHooks.useCardRelatedProgramsData).toHaveBeenCalledWith(cardId); + render(); + expect(useCourseData).toHaveBeenCalledWith(cardId); }); - test('snapshot: open', () => { - expect(shallow().snapshot).toMatchSnapshot(); - }); - test('snapshot: closed', () => { - expect( - shallow().snapshot, - ).toMatchSnapshot(); + describe('renders', () => { + beforeEach(() => render()); + it('display header', () => { + const header = screen.getByRole('heading', { name: messages.header.defaultMessage }); + expect(header).toBeInTheDocument(); + }); + it('displays course name', () => { + const courseName = screen.getByText(courseData.courseName); + expect(courseName).toBeInTheDocument(); + }); + it('displays description', () => { + const description = screen.getByText((text) => text.includes('Are you looking to expand your knowledge?')); + expect(description).toBeInTheDocument(); + }); + it('displays program cards', () => { + const programCards = screen.getAllByText('ProgramCard'); + expect(programCards.length).toEqual(programData.list.length); + }); }); }); diff --git a/src/containers/RelatedProgramsModal/messages.js b/src/containers/RelatedProgramsModal/messages.js index 257aada94..986a0b675 100644 --- a/src/containers/RelatedProgramsModal/messages.js +++ b/src/containers/RelatedProgramsModal/messages.js @@ -1,5 +1,4 @@ -/* eslint-disable quotes */ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ header: { diff --git a/src/containers/SelectSessionModal/__snapshots__/index.test.jsx.snap b/src/containers/SelectSessionModal/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 1afb69c22..000000000 --- a/src/containers/SelectSessionModal/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,176 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SelectSessionModal snapshot empty modal with leave option 1`] = ` - -

- test-header -

- - - test-hint - - - - Leave session - - - - - - - -
-`; - -exports[`SelectSessionModal snapshot modal with leave option 1`] = ` - -

- test-header -

- - - test-hint - - - - 1/2/2000 - - - 1/2/2020 - - - 2/3/2000 - - - 2/3/2020 - - - 3/4/2000 - - - 3/4/2020 - - - Leave session - - - - - - - -
-`; - -exports[`SelectSessionModal snapshot modal without leave option 1`] = ` - -

- test-header -

- - - test-hint - - - - 1/2/2000 - - - 1/2/2020 - - - 2/3/2000 - - - 2/3/2020 - - - 3/4/2000 - - - 3/4/2020 - - - - - - - -
-`; diff --git a/src/containers/SelectSessionModal/constants.js b/src/containers/SelectSessionModal/constants.js index df33eac38..75e141c21 100644 --- a/src/containers/SelectSessionModal/constants.js +++ b/src/containers/SelectSessionModal/constants.js @@ -1,2 +1 @@ -/* eslint-disable import/prefer-default-export */ export const LEAVE_OPTION = 'leave'; diff --git a/src/containers/SelectSessionModal/hooks.js b/src/containers/SelectSessionModal/hooks.js index e0b030efd..0b3f26577 100644 --- a/src/containers/SelectSessionModal/hooks.js +++ b/src/containers/SelectSessionModal/hooks.js @@ -1,13 +1,14 @@ -import React from 'react'; +import React, { useMemo } from 'react'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; -import { StrictDict } from 'utils'; +import { StrictDict } from '@src/utils'; -import track from 'tracking'; - -import { reduxHooks, apiHooks } from 'hooks'; +import track from '@src/tracking'; +import { useCourseData } from '@src/hooks'; +import { useDeleteEntitlementEnrollment, useUpdateEntitlementEnrollment } from '@src/data/hooks'; +import { useSelectSessionModal } from '@src/data/context'; import { LEAVE_OPTION } from './constants'; import messages from './messages'; import * as module from './hooks'; @@ -18,18 +19,28 @@ export const state = StrictDict({ export const useSelectSessionModalData = () => { const { formatMessage } = useIntl(); - - const selectedCardId = reduxHooks.useSelectSessionModalData().cardId; + const { selectSessionModal, closeSelectSessionModal: closeSessionModal } = useSelectSessionModal(); + const selectedCardId = selectSessionModal.cardId; + const courseData = useCourseData(selectedCardId); const { availableSessions, isFulfilled, - } = reduxHooks.useCardEntitlementData(selectedCardId); - const { title: courseTitle } = reduxHooks.useCardCourseData(selectedCardId); - const { courseId } = reduxHooks.useCardCourseRunData(selectedCardId) || {}; - const { isEnrolled } = reduxHooks.useCardEnrollmentData(selectedCardId); - const leaveEntitlementSession = apiHooks.useLeaveEntitlementSession(selectedCardId); - const switchEntitlementEnrollment = apiHooks.useSwitchEntitlementEnrollment(selectedCardId); - const newEntitlementEnrollment = apiHooks.useNewEntitlementEnrollment(selectedCardId); + courseTitle, + courseId, + isEnrolled, + uuid, + isRefundable, + } = useMemo(() => ({ + availableSessions: courseData?.entitlement?.availableSessions || [], + isFulfilled: courseData?.entitlement?.isFulfilled || false, + courseTitle: courseData?.course?.title || courseData?.course?.courseName || '', + courseId: courseData?.courseRun?.courseId || null, + isEnrolled: courseData?.enrollment?.isEnrolled || false, + uuid: courseData?.entitlement?.uuid || null, + isRefundable: courseData?.entitlement?.isRefundable || false, + }), [courseData]); + const { mutate: leaveEntitlement } = useDeleteEntitlementEnrollment(); + const { mutate: switchEntitlementMutation } = useUpdateEntitlementEnrollment(); const [selectedSession, setSelectedSession] = module.state.selectedSession(courseId || null); @@ -42,7 +53,6 @@ export const useSelectSessionModalData = () => { header = formatMessage(messages.selectSessionHeader, { courseTitle }); hint = formatMessage(messages.selectSessionHint); } - const closeSessionModal = reduxHooks.useUpdateSelectSessionModalCallback(null); const trackNewSession = track.entitlements.newSession(selectedSession); const trackSwitchSession = track.entitlements.switchSession(selectedCardId, selectedSession); @@ -50,17 +60,17 @@ export const useSelectSessionModalData = () => { const handleSelection = ({ target: { value } }) => setSelectedSession(value); const handleSubmit = () => { + const onSuccess = () => closeSessionModal(); if (selectedSession === LEAVE_OPTION) { trackLeaveSession(); - leaveEntitlementSession(); + leaveEntitlement({ uuid, isRefundable }, { onSuccess }); } else if (isEnrolled) { trackSwitchSession(); - switchEntitlementEnrollment(selectedSession); + switchEntitlementMutation({ uuid, courseId: selectedSession }, { onSuccess }); } else { trackNewSession(); - newEntitlementEnrollment(selectedSession); + switchEntitlementMutation({ uuid, courseId: selectedSession }, { onSuccess }); } - closeSessionModal(); }; return { diff --git a/src/containers/SelectSessionModal/hooks.test.js b/src/containers/SelectSessionModal/hooks.test.js deleted file mode 100644 index e7e271b54..000000000 --- a/src/containers/SelectSessionModal/hooks.test.js +++ /dev/null @@ -1,194 +0,0 @@ -import { useIntl } from '@edx/frontend-platform/i18n'; -import track from 'tracking'; - -import { MockUseState } from 'testUtils'; -import { reduxHooks, apiHooks } from 'hooks'; - -import { LEAVE_OPTION } from './constants'; -import messages from './messages'; -import * as hooks from './hooks'; - -jest.mock('tracking', () => ({ - entitlements: { - newSession: jest.fn(), - switchSession: jest.fn(), - leaveSession: jest.fn(), - }, -})); -jest.mock('hooks', () => ({ - reduxHooks: { - useCardCourseData: jest.fn(), - useCardCourseRunData: jest.fn(), - useCardEnrollmentData: jest.fn(), - useCardEntitlementData: jest.fn(), - useSelectSessionModalData: jest.fn(), - useUpdateSelectSessionModalCallback: jest.fn(), - }, - apiHooks: { - useSwitchEntitlementEnrollment: jest.fn((...args) => ({ switchEntitlementEnrollment: args })), - useLeaveEntitlementSession: jest.fn((...args) => ({ leaveEntitlementSession: args })), - useNewEntitlementEnrollment: jest.fn((...args) => ({ newEntitlementEnrollment: args })), - }, -})); - -const updateSelectSessionModalCallback = jest.fn(); -reduxHooks.useUpdateSelectSessionModalCallback.mockReturnValue(updateSelectSessionModalCallback); -const newEntitlementEnrollment = jest.fn(); -apiHooks.useNewEntitlementEnrollment.mockReturnValue(newEntitlementEnrollment); -const switchEntitlementEnrollment = jest.fn(); -apiHooks.useSwitchEntitlementEnrollment.mockReturnValue(switchEntitlementEnrollment); -const leaveEntitlementSession = jest.fn(); -apiHooks.useLeaveEntitlementSession.mockReturnValue(leaveEntitlementSession); -const trackNewSession = jest.fn(); -track.entitlements.newSession.mockReturnValue(trackNewSession); -const trackLeaveSession = jest.fn(); -track.entitlements.leaveSession.mockReturnValue(trackLeaveSession); -const trackSwitchSession = jest.fn(); -track.entitlements.switchSession.mockReturnValue(trackSwitchSession); - -const state = new MockUseState(hooks); -const selectedCardId = 'test-selected-card-id'; -const courseTitle = 'course-title: brown fox'; -const uuid = 'test-uuid'; - -const entitlementData = { - availableSessions: [ - { startDate: '1/2/2000', endDate: '1/2/2020', cardId: 'session-id-1' }, - { startDate: '2/3/2000', endDate: '2/3/2020', cardId: 'session-id-2' }, - { startDate: '3/4/2000', endDate: '3/4/2020', cardId: 'session-id-3' }, - ], - isFullfilled: false, - uuid, -}; - -const testValue = 'test-value'; - -const courseId = 'test-course-id'; -reduxHooks.useCardCourseRunData.mockReturnValue({ courseId }); - -describe('SelectSessionModal hooks', () => { - let out; - - describe('state values', () => { - state.testGetter(state.keys.selectedSession); - }); - beforeEach(() => { - jest.clearAllMocks(); - }); - describe('useSelectSessionModalData', () => { - const runHook = ({ - course = {}, - courseRun = {}, - enrollment = {}, - entitlement = {}, - selectSession = {}, - }) => { - reduxHooks.useCardCourseData.mockReturnValueOnce({ title: courseTitle, ...course }); - reduxHooks.useCardCourseRunData.mockReturnValueOnce({ courseId, ...courseRun }); - reduxHooks.useCardEnrollmentData.mockReturnValueOnce({ isEnrolled: false, ...enrollment }); - reduxHooks.useCardEntitlementData.mockReturnValueOnce({ ...entitlementData, ...entitlement }); - reduxHooks.useSelectSessionModalData.mockReturnValueOnce({ cardId: selectedCardId, ...selectSession }); - out = hooks.useSelectSessionModalData(); - }; - beforeEach(() => { - state.mock(); - runHook({}); - }); - describe('initialization', () => { - it('loads redux data based on selected card id', () => { - expect(reduxHooks.useCardEntitlementData).toHaveBeenCalledWith(selectedCardId); - expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(selectedCardId); - expect(reduxHooks.useCardEnrollmentData).toHaveBeenCalledWith(selectedCardId); - }); - it('initializes enrollment hooks with selected card id', () => { - expect(apiHooks.useLeaveEntitlementSession).toHaveBeenCalledWith(selectedCardId); - expect(apiHooks.useNewEntitlementEnrollment).toHaveBeenCalledWith(selectedCardId); - expect(apiHooks.useSwitchEntitlementEnrollment).toHaveBeenCalledWith(selectedCardId); - }); - it('initializes selected session with courseId if available', () => { - state.expectInitializedWith(state.keys.selectedSession, courseId); - }); - it('initializes selected session with null if courseId not available', () => { - runHook({ courseRun: { courseId: undefined } }); - state.expectInitializedWith(state.keys.selectedSession, null); - }); - it('initializes update callback with null', () => { - expect(reduxHooks.useUpdateSelectSessionModalCallback).toHaveBeenCalledWith(null); - }); - it('initializes tracking methods', () => { - expect(track.entitlements.newSession).toHaveBeenCalledWith(courseId); - expect(track.entitlements.leaveSession).toHaveBeenCalledWith(selectedCardId); - expect(track.entitlements.switchSession).toHaveBeenCalledWith(selectedCardId, courseId); - }); - }); - - describe('output', () => { - const { formatMessage } = useIntl(); - describe('selectedSession', () => { - it('defaults to current courseId if enrolled', () => { - expect(out.selectedSession).toEqual(courseId); - }); - it('defaults to null if not enrolled', () => { - runHook({ enrollment: { isEnrolled: false }, courseRun: { courseId: undefined } }); - expect(out.selectedSession).toEqual(null); - }); - }); - describe('handleSelection', () => { - it('sets selected session with event target value', () => { - out.handleSelection({ target: { value: testValue } }); - expect(state.setState.selectedSession).toHaveBeenCalledWith(testValue); - }); - }); - describe('handleSubmit', () => { - describe('if LEAVE_OPTION is selected', () => { - it('calls and tracks leaveEntitlementSession', () => { - state.mockVal(state.keys.selectedSession, LEAVE_OPTION); - runHook({}); - out.handleSubmit(); - expect(leaveEntitlementSession).toHaveBeenCalledWith(); - expect(trackLeaveSession).toHaveBeenCalled(); - }); - }); - describe('if not enrolled in a session yet', () => { - it('calls and tracks newEntitlementEnrollment with selected card ID and session', () => { - state.mockVal(state.keys.selectedSession, testValue); - runHook({}); - out.handleSubmit(); - expect(newEntitlementEnrollment).toHaveBeenCalledWith(testValue); - expect(trackNewSession).toHaveBeenCalled(); - }); - }); - describe('if enrolled in a session already, selecting a new session', () => { - it('calls and tracks swtichEntitlementEnrollment w/ selected card ID and session', () => { - state.mockVal(state.keys.selectedSession, testValue); - runHook({ enrollment: { isEnrolled: true } }); - out.handleSubmit(); - expect(switchEntitlementEnrollment).toHaveBeenCalledWith(testValue); - expect(trackSwitchSession).toHaveBeenCalled(); - }); - }); - }); - test('showModal returns true if selectedCardId is not null or undefined', () => { - expect(out.showModal).toEqual(true); - runHook({ selectSession: { cardId: null } }); - expect(out.showModal).toEqual(false); - runHook({ selectSession: { cardId: undefined } }); - expect(out.showModal).toEqual(false); - }); - test('displays change or leave header and hint if fulfilled', () => { - expect(out.header).toEqual(formatMessage(messages.selectSessionHeader, { courseTitle })); - expect(out.hint).toEqual(formatMessage(messages.selectSessionHint)); - }); - test('displays select session header (w/ courseTitle) and hint if unfulfilled', () => { - runHook({ entitlement: { isFulfilled: true } }); - expect(out.header).toEqual(formatMessage(messages.changeOrLeaveHeader)); - expect(out.hint).toEqual(formatMessage(messages.changeOrLeaveHint)); - }); - test('closeSessionModal returns update callback wth dispatch and null card id', () => { - expect(out.closeSessionModal()).toEqual( - reduxHooks.useUpdateSelectSessionModalCallback(null)(), - ); - }); - }); - }); -}); diff --git a/src/containers/SelectSessionModal/hooks.test.jsx b/src/containers/SelectSessionModal/hooks.test.jsx new file mode 100644 index 000000000..94d4d1b76 --- /dev/null +++ b/src/containers/SelectSessionModal/hooks.test.jsx @@ -0,0 +1,288 @@ +import { renderHook, act } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { useIntl } from '@openedx/frontend-base'; +import { useCourseData } from '@src/hooks'; +import { useSelectSessionModal } from '@src/data/context'; +import * as api from '@src/data/services/lms/api'; + +import { useSelectSessionModalData } from './hooks'; +import { LEAVE_OPTION } from './constants'; +import messages from './messages'; + +jest.mock('@src/data/services/lms/api', () => ({ + deleteEntitlementEnrollment: jest.fn(), + updateEntitlementEnrollment: jest.fn(), +})); + +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(), +})); + +jest.mock('@src/data/context', () => ({ + useSelectSessionModal: jest.fn(), +})); + +jest.mock('@openedx/frontend-base', () => { + const { formatMessage } = jest.requireActual('@src/testUtils'); + return { + ...jest.requireActual('@openedx/frontend-base'), + useIntl: () => ({ formatMessage }), + logError: jest.fn(), + }; +}); + +jest.mock('@src/tracking', () => ({ + entitlements: { + newSession: jest.fn(() => jest.fn()), + switchSession: jest.fn(() => jest.fn()), + leaveSession: jest.fn(() => jest.fn()), + }, +})); + +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, + }); + const wrapper = ({ children }) => {children}; + return wrapper; +}; + +const selectedCardId = 'test-selected-card-id'; +const courseTitle = 'Test Course'; +const courseId = 'course-123'; +const uuid = 'entitlement-uuid'; + +const courseData = { + entitlement: { + availableSessions: [ + { startDate: '2024-01-01', endDate: '2024-06-01', cardId: 'session-1' }, + { startDate: '2024-07-01', endDate: '2024-12-01', cardId: 'session-2' }, + ], + isFulfilled: false, + uuid, + isRefundable: true, + }, + course: { + title: courseTitle, + }, + courseRun: { + courseId, + }, + enrollment: { + isEnrolled: false, + }, +}; + +const closeSelectSessionModal = jest.fn(); + +describe('SelectSessionModal hooks', () => { + beforeEach(() => { + jest.clearAllMocks(); + useCourseData.mockReturnValue(courseData); + useSelectSessionModal.mockReturnValue({ + selectSessionModal: { cardId: selectedCardId }, + closeSelectSessionModal, + }); + api.deleteEntitlementEnrollment.mockResolvedValue({}); + api.updateEntitlementEnrollment.mockResolvedValue({}); + }); + + describe('useSelectSessionModalData', () => { + it('loads data based on selected card id', () => { + renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + + expect(useCourseData).toHaveBeenCalledWith(selectedCardId); + }); + + it('initializes selectedSession with courseId if available', () => { + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + + expect(result.current.selectedSession).toBe(courseId); + }); + + it('initializes selectedSession with null if courseId not available', () => { + useCourseData.mockReturnValue({ + ...courseData, + courseRun: { courseId: null }, + }); + + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + + expect(result.current.selectedSession).toBe(null); + }); + + it('sets selected session with event target value', () => { + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.handleSelection({ target: { value: 'new-session-id' } }); + }); + + expect(result.current.selectedSession).toBe('new-session-id'); + }); + + it('calls deleteEntitlementEnrollment api when LEAVE_OPTION is selected', async () => { + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.handleSelection({ target: { value: LEAVE_OPTION } }); + }); + + await act(async () => { + result.current.handleSubmit(); + }); + + expect(api.deleteEntitlementEnrollment).toHaveBeenCalledWith({ + uuid, + isRefundable: true, + }); + }); + + it('calls switchEntitlementEnrollment api when not enrolled and selecting session', async () => { + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.handleSelection({ target: { value: 'new-session-id' } }); + }); + + await act(async () => { + result.current.handleSubmit(); + }); + + expect(api.updateEntitlementEnrollment).toHaveBeenCalledWith({ + uuid, + courseId: 'new-session-id', + }); + }); + + it('calls updateEntitlementEnrollment api when enrolled and switching sessions', async () => { + const enrolledCourseData = { + ...courseData, + enrollment: { isEnrolled: true }, + courseRun: { courseId: 'current-session-id' }, + }; + + useCourseData.mockReturnValue(enrolledCourseData); + + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.handleSelection({ target: { value: 'new-session-id' } }); + }); + + await act(async () => { + result.current.handleSubmit(); + }); + + expect(api.updateEntitlementEnrollment).toHaveBeenCalledWith({ + uuid, + courseId: 'new-session-id', + }); + }); + + it('shows modal when selectedCardId is not null', () => { + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + + expect(result.current.showModal).toBe(true); + }); + + it('hides modal when selectedCardId is null', () => { + useSelectSessionModal.mockReturnValue({ + selectSessionModal: { cardId: null }, + closeSelectSessionModal, + }); + + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + + expect(result.current.showModal).toBe(false); + }); + + it('displays select session header and hint when unfulfilled', () => { + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + const { formatMessage } = useIntl(); + + expect(result.current.header).toBe(formatMessage(messages.selectSessionHeader, { courseTitle })); + expect(result.current.hint).toBe(formatMessage(messages.selectSessionHint)); + }); + + it('displays change or leave header and hint when fulfilled', () => { + useCourseData.mockReturnValue({ + ...courseData, + entitlement: { + ...courseData.entitlement, + isFulfilled: true, + }, + }); + + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + const { formatMessage } = useIntl(); + + expect(result.current.header).toBe(formatMessage(messages.changeOrLeaveHeader)); + expect(result.current.hint).toBe(formatMessage(messages.changeOrLeaveHint)); + }); + + it('shows leave option when entitlement is fulfilled', () => { + useCourseData.mockReturnValue({ + ...courseData, + entitlement: { + ...courseData.entitlement, + isFulfilled: true, + }, + }); + + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + + expect(result.current.showLeaveOption).toBe(true); + }); + + it('hides leave option when entitlement is not fulfilled', () => { + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + + expect(result.current.showLeaveOption).toBe(false); + }); + + it('returns available sessions from entitlement data', () => { + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + + expect(result.current.availableSessions).toEqual(courseData.entitlement.availableSessions); + }); + + it('closes session modal when closeSessionModal is called', () => { + const { result } = renderHook(() => useSelectSessionModalData(), { + wrapper: createWrapper(), + }); + + result.current.closeSessionModal(); + + expect(closeSelectSessionModal).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/containers/SelectSessionModal/index.jsx b/src/containers/SelectSessionModal/index.jsx index e4aead1a1..f5179cd17 100644 --- a/src/containers/SelectSessionModal/index.jsx +++ b/src/containers/SelectSessionModal/index.jsx @@ -1,6 +1,6 @@ import React from 'react'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import { ActionRow, Button, @@ -8,8 +8,8 @@ import { ModalDialog, } from '@openedx/paragon'; -import { utilHooks } from 'hooks'; -import { nullMethod, dateFormatter } from 'utils'; +import { utilHooks } from '../../hooks'; +import { nullMethod, dateFormatter } from '../../utils'; import useSelectSessionModalData from './hooks'; import { LEAVE_OPTION } from './constants'; diff --git a/src/containers/SelectSessionModal/index.test.jsx b/src/containers/SelectSessionModal/index.test.jsx index 19958d506..5304333d3 100644 --- a/src/containers/SelectSessionModal/index.test.jsx +++ b/src/containers/SelectSessionModal/index.test.jsx @@ -1,8 +1,9 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - +import { render, screen } from '@testing-library/react'; +import { formatMessage } from '@src/testUtils'; +import { IntlProvider } from '@openedx/frontend-base'; import hooks from './hooks'; import SelectSessionModal from '.'; +import messages from './messages'; jest.mock('./hooks', () => ({ __esModule: true, @@ -25,29 +26,41 @@ const availableSessions = [ ]; describe('SelectSessionModal', () => { - describe('snapshot', () => { - test('empty modal with leave option ', () => { + describe('renders', () => { + it('empty modal with leave option ', () => { hooks.mockReturnValueOnce({ ...hookReturn, }); - expect(shallow().snapshot).toMatchSnapshot(); + render(); + const sessionOption = screen.queryByDisplayValue(availableSessions[0].courseId); + expect(sessionOption).toBeNull(); + const leaveOption = screen.getByRole('radio', { name: formatMessage(messages.leaveSessionOption) }); + expect(leaveOption).toBeInTheDocument(); }); - test('modal with leave option ', () => { + it('modal with leave option ', () => { hooks.mockReturnValueOnce({ ...hookReturn, - availableSessions: [...availableSessions], + availableSessions, }); - expect(shallow().snapshot).toMatchSnapshot(); + render(); + const sessionOption = screen.getByDisplayValue(availableSessions[0].courseId); + expect(sessionOption).toBeInTheDocument(); + const leaveOption = screen.getByRole('radio', { name: formatMessage(messages.leaveSessionOption) }); + expect(leaveOption).toBeInTheDocument(); }); - test('modal without leave option ', () => { + it('modal without leave option ', () => { hooks.mockReturnValueOnce({ ...hookReturn, availableSessions, showLeaveOption: false, }); - expect(shallow().snapshot).toMatchSnapshot(); + render(); + const sessionOption = screen.getByDisplayValue(availableSessions[0].courseId); + expect(sessionOption).toBeInTheDocument(); + const leaveOption = screen.queryByRole('radio', { name: formatMessage(messages.leaveSessionOption) }); + expect(leaveOption).toBeNull(); }); }); }); diff --git a/src/containers/SelectSessionModal/messages.js b/src/containers/SelectSessionModal/messages.js index ae69fa7ab..4c8ac1eb8 100644 --- a/src/containers/SelectSessionModal/messages.js +++ b/src/containers/SelectSessionModal/messages.js @@ -1,5 +1,4 @@ -/* eslint-disable quotes */ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ changeOrLeaveHeader: { diff --git a/src/containers/UnenrollConfirmModal/__snapshots__/index.test.jsx.snap b/src/containers/UnenrollConfirmModal/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 87ad3a8b5..000000000 --- a/src/containers/UnenrollConfirmModal/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,101 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UnenrollConfirmModal component snapshot: modalStates.confirm 1`] = ` - -
- -
-
-`; - -exports[`UnenrollConfirmModal component snapshot: modalStates.finished, reason given 1`] = ` - -
- -
-
-`; - -exports[`UnenrollConfirmModal component snapshot: modalStates.finished, reason skipped 1`] = ` - -
- -
-
-`; - -exports[`UnenrollConfirmModal component snapshot: modalStates.reason, should be fullscreen with no shadow 1`] = ` - -
- -
-
-`; diff --git a/src/containers/UnenrollConfirmModal/components/ConfirmPane.jsx b/src/containers/UnenrollConfirmModal/components/ConfirmPane.jsx index eea486964..d22521a8f 100644 --- a/src/containers/UnenrollConfirmModal/components/ConfirmPane.jsx +++ b/src/containers/UnenrollConfirmModal/components/ConfirmPane.jsx @@ -1,7 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { useCourseData } from '@src/hooks'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import { ActionRow, Button, @@ -10,13 +11,18 @@ import { import messages from './messages'; export const ConfirmPane = ({ + cardId, handleClose, handleConfirm, }) => { const { formatMessage } = useIntl(); + const courseData = useCourseData(cardId); + const courseName = courseData?.course?.courseName || ''; + const courseTitle = “{courseName}”; return ( <>

{formatMessage(messages.confirmHeader)}

+

{formatMessage(messages.confirmText, { courseTitle })}

@@ -29,7 +32,7 @@ export const FinishedPane = ({ }; FinishedPane.propTypes = { handleClose: PropTypes.func.isRequired, - gaveReason: PropTypes.bool.isRequired, + cardId: PropTypes.string.isRequired, }; export default FinishedPane; diff --git a/src/containers/UnenrollConfirmModal/components/FinishedPane.test.jsx b/src/containers/UnenrollConfirmModal/components/FinishedPane.test.jsx index 1ec5e35c0..ee9df70a6 100644 --- a/src/containers/UnenrollConfirmModal/components/FinishedPane.test.jsx +++ b/src/containers/UnenrollConfirmModal/components/FinishedPane.test.jsx @@ -1,21 +1,42 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { formatMessage } from '@src/testUtils'; +import { IntlProvider } from '@openedx/frontend-base'; +import { useCourseData } from '@src/hooks'; import { FinishedPane } from './FinishedPane'; +import messages from './messages'; + +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(), +})); + +const props = { + cardId: 'cardId', + handleClose: jest.fn().mockName('props.handleClose'), +}; describe('UnenrollConfirmModal FinishedPane', () => { - test('snapshot: gave reason', () => { - const props = { - gaveReason: true, - handleClose: jest.fn().mockName('props.handleClose'), - }; - expect(shallow().snapshot).toMatchSnapshot(); - }); - test('snapshot: did not give reason', () => { - const props = { - gaveReason: false, - handleClose: jest.fn().mockName('props.handleClose'), - }; - expect(shallow().snapshot).toMatchSnapshot(); + describe('gave reason', () => { + beforeEach(() => { + jest.clearAllMocks(); + useCourseData.mockReturnValue({ + course: { + courseName: 'Test Course', + }, + }); + render(); + }); + it('renders heading', () => { + const heading = screen.getByText(formatMessage(messages.finishHeading)); + expect(heading).toBeInTheDocument(); + }); + it('renders return button', () => { + const returnButton = screen.getByRole('button', { name: formatMessage(messages.finishReturn) }); + expect(returnButton).toBeInTheDocument(); + }); + it('Gave reason, display thanks message', () => { + const finishSuccessMessage = screen.getByText((text) => text.includes('Unenrollment Successful')); + expect(finishSuccessMessage).toBeInTheDocument(); + }); }); }); diff --git a/src/containers/UnenrollConfirmModal/components/ReasonPane.jsx b/src/containers/UnenrollConfirmModal/components/ReasonPane.jsx index ac106a2a0..d6db82b1a 100644 --- a/src/containers/UnenrollConfirmModal/components/ReasonPane.jsx +++ b/src/containers/UnenrollConfirmModal/components/ReasonPane.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import { ActionRow, Button, @@ -13,6 +13,7 @@ import messages from './messages'; export const ReasonPane = ({ reason, + handleClose, }) => { const { formatMessage } = useIntl(); const option = (key) => ( @@ -27,6 +28,7 @@ export const ReasonPane = ({ name="unenrollReason" onChange={reason.selectOption} value={reason.selected} + defaultValue={constants.reasonKeys.preferNotToSay} > {constants.order.map(option)} @@ -35,12 +37,13 @@ export const ReasonPane = ({ placeholder={formatMessage(constants.messages.customPlaceholder)} /> + {option(constants.reasonKeys.preferNotToSay)} - - @@ -50,7 +53,6 @@ export const ReasonPane = ({ ReasonPane.propTypes = { reason: PropTypes.shape({ value: PropTypes.string, - handleSkip: PropTypes.func, hasReason: PropTypes.bool, selectOption: PropTypes.func, customOption: PropTypes.shape({ @@ -60,6 +62,7 @@ ReasonPane.propTypes = { selected: PropTypes.string, handleSubmit: PropTypes.func.isRequired, }).isRequired, + handleClose: PropTypes.func.isRequired, }; export default ReasonPane; diff --git a/src/containers/UnenrollConfirmModal/components/ReasonPane.test.jsx b/src/containers/UnenrollConfirmModal/components/ReasonPane.test.jsx index df168ddbf..2cd92f0ba 100644 --- a/src/containers/UnenrollConfirmModal/components/ReasonPane.test.jsx +++ b/src/containers/UnenrollConfirmModal/components/ReasonPane.test.jsx @@ -1,7 +1,9 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { formatMessage } from '@src/testUtils'; +import { IntlProvider } from '@openedx/frontend-base'; import { ReasonPane } from './ReasonPane'; +import messages from './messages'; describe('UnenrollConfirmModal ReasonPane', () => { const props = { @@ -17,10 +19,25 @@ describe('UnenrollConfirmModal ReasonPane', () => { hasReason: true, }, }; - test('snapshot', () => { - expect(shallow().snapshot).toMatchSnapshot(); + it('render heading', () => { + render(); + const heading = screen.getByText(formatMessage(messages.reasonHeading)); + expect(heading).toBeInTheDocument(); }); - test('snapshot: no reason provided', () => { - expect(shallow().snapshot).toMatchSnapshot(); + it('render options', () => { + render(); + const radioButtons = screen.getAllByRole('radio'); + expect(radioButtons).toBeDefined(); + expect(radioButtons.length).toBe(11); + }); + it('render cancel button', () => { + render(); + const skipButton = screen.getByRole('button', { name: formatMessage(messages.confirmCancel) }); + expect(skipButton).toBeInTheDocument(); + }); + it('render submit button', () => { + render(); + const submitButton = screen.getByRole('button', { name: formatMessage(messages.reasonSubmit) }); + expect(submitButton).toBeInTheDocument(); }); }); diff --git a/src/containers/UnenrollConfirmModal/components/__snapshots__/ConfirmPane.test.jsx.snap b/src/containers/UnenrollConfirmModal/components/__snapshots__/ConfirmPane.test.jsx.snap deleted file mode 100644 index fddac21a5..000000000 --- a/src/containers/UnenrollConfirmModal/components/__snapshots__/ConfirmPane.test.jsx.snap +++ /dev/null @@ -1,22 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UnenrollConfirmModal ConfirmPane snapshot 1`] = ` - -

- Unenroll from course? -

- - - - -
-`; diff --git a/src/containers/UnenrollConfirmModal/components/__snapshots__/FinishedPane.test.jsx.snap b/src/containers/UnenrollConfirmModal/components/__snapshots__/FinishedPane.test.jsx.snap deleted file mode 100644 index 88a0dd191..000000000 --- a/src/containers/UnenrollConfirmModal/components/__snapshots__/FinishedPane.test.jsx.snap +++ /dev/null @@ -1,38 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UnenrollConfirmModal FinishedPane snapshot: did not give reason 1`] = ` - -

- You are unenrolled -

-

- This course will be removed from your dashboard. -

- - - -
-`; - -exports[`UnenrollConfirmModal FinishedPane snapshot: gave reason 1`] = ` - -

- You are unenrolled -

-

- Thank you for sharing your reason for unenrolling. - This course will be removed from your dashboard. -

- - - -
-`; diff --git a/src/containers/UnenrollConfirmModal/components/__snapshots__/ReasonPane.test.jsx.snap b/src/containers/UnenrollConfirmModal/components/__snapshots__/ReasonPane.test.jsx.snap deleted file mode 100644 index 3e2bec8a8..000000000 --- a/src/containers/UnenrollConfirmModal/components/__snapshots__/ReasonPane.test.jsx.snap +++ /dev/null @@ -1,183 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UnenrollConfirmModal ReasonPane snapshot 1`] = ` - -

- What's your main reason for unenrolling? -

- - - I don't have the academic or language prerequisites - - - The course material was too hard - - - This won't help me reach my goals - - - Something was broken - - - I don't have the time - - - I just wanted to browse the material - - - I don't have enough support - - - I am not happy with the quality of the content - - - The course material was too easy - - - - - - - - - -
-`; - -exports[`UnenrollConfirmModal ReasonPane snapshot: no reason provided 1`] = ` - -

- What's your main reason for unenrolling? -

- - - I don't have the academic or language prerequisites - - - The course material was too hard - - - This won't help me reach my goals - - - Something was broken - - - I don't have the time - - - I just wanted to browse the material - - - I don't have enough support - - - I am not happy with the quality of the content - - - The course material was too easy - - - - - - - - - -
-`; diff --git a/src/containers/UnenrollConfirmModal/components/messages.js b/src/containers/UnenrollConfirmModal/components/messages.js index 721032a64..426b5d8af 100644 --- a/src/containers/UnenrollConfirmModal/components/messages.js +++ b/src/containers/UnenrollConfirmModal/components/messages.js @@ -1,16 +1,20 @@ -/* eslint-disable quotes */ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ confirmHeader: { id: 'learner-dash.unenrollConfirm.confirm.header', description: 'Header for confirm unenroll modal', - defaultMessage: 'Unenroll from course?', + defaultMessage: 'Confirm Unenrollment', + }, + confirmText: { + id: 'learner-dash.unenrollConfirm.confirm.text', + description: 'Text for confirm unenroll modal', + defaultMessage: 'Are you sure you want to unenroll from the course {courseTitle} ?', }, confirmCancel: { id: 'learner-dash.unenrollConfirm.confirm.cancel', description: 'Cancel action for confirm unenroll modal', - defaultMessage: 'Never mind', + defaultMessage: 'Cancel', }, confirmUnenroll: { id: 'learner-dash.unenrollConfirm.confirm.unenroll', @@ -20,7 +24,7 @@ const messages = defineMessages({ reasonHeading: { id: 'learner-dash.unenrollConfirm.confirm.reason.heading', description: 'Heading for unenroll reason modal', - defaultMessage: `What's your main reason for unenrolling?`, + defaultMessage: 'Why are you unenrolling?', }, reasonSkip: { id: 'learner-dash.unenrollConfirm.confirm.reason.skip', @@ -30,27 +34,22 @@ const messages = defineMessages({ reasonSubmit: { id: 'learner-dash.unenrollConfirm.confirm.reason.submit', description: 'Submit action for unenroll reason modal', - defaultMessage: 'Submit reason', + defaultMessage: 'Unenroll', }, finishHeading: { id: 'learner-dash.unenrollConfirm.confirm.finish.heading', description: 'Heading for unenroll finish modal', - defaultMessage: 'You are unenrolled', - }, - finishThanksText: { - id: 'learner-dash.unenrollConfirm.confirm.finish.thanks-text', - description: 'Thank you message on unenroll modal for providing a reason', - defaultMessage: 'Thank you for sharing your reason for unenrolling. ', + defaultMessage: 'Unenrollment Successful', }, finishText: { id: 'learner-dash.unenrollConfirm.confirm.finish.text', description: 'Text for unenroll finish modal', - defaultMessage: 'This course will be removed from your dashboard.', + defaultMessage: 'You have been unenrolled from the course {courseTitle}', }, finishReturn: { id: 'learner-dash.unenrollConfirm.confirm.finish.return', description: 'Return action for unenroll finish modal', - defaultMessage: 'Return to dashboard', + defaultMessage: 'Ok', }, }); diff --git a/src/containers/UnenrollConfirmModal/constants.js b/src/containers/UnenrollConfirmModal/constants.js index 5ba6d0267..49e5eec77 100644 --- a/src/containers/UnenrollConfirmModal/constants.js +++ b/src/containers/UnenrollConfirmModal/constants.js @@ -1,6 +1,5 @@ -/* eslint-disable quotes */ -import { StrictDict } from 'utils'; -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { StrictDict } from '../../utils'; +import { defineMessages } from '@openedx/frontend-base'; export const reasonKeys = StrictDict({ prereqs: 'prereqs', @@ -13,18 +12,19 @@ export const reasonKeys = StrictDict({ quality: 'quality', easy: 'easy', custom: 'custom', + preferNotToSay: 'prefer-not-to-say', }); export const order = [ reasonKeys.prereqs, reasonKeys.difficulty, + reasonKeys.easy, reasonKeys.goals, reasonKeys.broken, reasonKeys.time, reasonKeys.browse, reasonKeys.support, reasonKeys.quality, - reasonKeys.easy, ]; const messages = defineMessages({ @@ -78,6 +78,11 @@ const messages = defineMessages({ description: 'Unenroll custom reason option placeholder text', defaultMessage: 'Other', }, + [reasonKeys.preferNotToSay]: { + id: 'learner-dash.unenrollConfirm.reasons.prefer-not-to-say', + description: 'Unenroll reason option - prefer not to say', + defaultMessage: 'I prefer not to say', + }, }); export default { diff --git a/src/containers/UnenrollConfirmModal/hooks/index.js b/src/containers/UnenrollConfirmModal/hooks/index.js index 3f0a219c3..45315bc6c 100644 --- a/src/containers/UnenrollConfirmModal/hooks/index.js +++ b/src/containers/UnenrollConfirmModal/hooks/index.js @@ -1,7 +1,9 @@ import React from 'react'; -import { StrictDict } from 'utils'; -import { apiHooks } from 'hooks'; +import { useAppConfig } from '@openedx/frontend-base'; +import { StrictDict } from '@src/utils'; +import { useCourseData } from '@src/hooks'; +import { useUnenrollFromCourse } from '@src/data/hooks'; import { useUnenrollReasons } from './reasons'; import * as module from '.'; @@ -18,13 +20,23 @@ export const modalStates = StrictDict({ export const useUnenrollData = ({ closeModal, cardId }) => { const [isConfirmed, setIsConfirmed] = module.state.confirmed(false); - const confirm = () => setIsConfirmed(true); const reason = useUnenrollReasons({ cardId }); - const refreshList = apiHooks.useInitializeApp(); + const appConfig = useAppConfig(); + const courseData = useCourseData(cardId); + const courseId = courseData?.courseRun?.courseId; + + const { mutate: unenrollFromCourse } = useUnenrollFromCourse(); + + const confirm = () => { + if (!appConfig.SHOW_UNENROLL_SURVEY) { + unenrollFromCourse({ courseId }); + } + setIsConfirmed(true); + }; let modalState; if (isConfirmed) { - modalState = (reason.isSubmitted || reason.isSkipped) + modalState = (reason.isSubmitted || !appConfig.SHOW_UNENROLL_SURVEY) ? modalStates.finished : modalStates.reason; } else { modalState = modalStates.confirm; @@ -35,17 +47,13 @@ export const useUnenrollData = ({ closeModal, cardId }) => { setIsConfirmed(false); reason.handleClear(); }; - const closeAndRefresh = () => { - refreshList(); - close(); - }; return { isConfirmed, confirm, reason, close, - closeAndRefresh, + closeAndRefresh: close, modalState, }; }; diff --git a/src/containers/UnenrollConfirmModal/hooks/index.test.js b/src/containers/UnenrollConfirmModal/hooks/index.test.js index 1db9a118e..da9c9b951 100644 --- a/src/containers/UnenrollConfirmModal/hooks/index.test.js +++ b/src/containers/UnenrollConfirmModal/hooks/index.test.js @@ -1,5 +1,8 @@ -import { apiHooks } from 'hooks'; -import { MockUseState } from 'testUtils'; +import { MockUseState } from '@src/testUtils'; +import { useAppConfig } from '@openedx/frontend-base'; + +import { useCourseData } from '@src/hooks'; +import { useUnenrollFromCourse } from '@src/data/hooks'; import * as reasons from './reasons'; import * as hooks from '.'; @@ -8,16 +11,23 @@ jest.mock('./reasons', () => ({ useUnenrollReasons: jest.fn(), })); -jest.mock('hooks', () => ({ - apiHooks: { - useInitializeApp: jest.fn(), - }, +jest.mock('@src/data/hooks', () => ({ + useUnenrollFromCourse: jest.fn(), +})); + +jest.mock('@src/hooks', () => ({ + useCourseData: jest.fn(), +})); + +jest.mock('@openedx/frontend-base', () => ({ + useAppConfig: jest.fn(), })); const state = new MockUseState(hooks); const testValue = 'test-value'; -const initializeApp = jest.fn(); -apiHooks.useInitializeApp.mockReturnValue(initializeApp); +const unenrollFromCourse = jest.fn(); +useUnenrollFromCourse.mockReturnValue({ mutate: unenrollFromCourse }); +useCourseData.mockReturnValue({ courseRun: { courseId: 'test-course-id' } }); let out; const mockReason = { @@ -31,6 +41,7 @@ const useUnenrollReasons = jest.fn(() => mockReason); describe('UnenrollConfirmModal hooks', () => { beforeEach(() => { reasons.useUnenrollReasons.mockImplementation(useUnenrollReasons); + useAppConfig.mockReturnValue({ SHOW_UNENROLL_SURVEY: true }); }); const closeModal = jest.fn(); const cardId = 'test-card-id'; @@ -68,33 +79,71 @@ describe('UnenrollConfirmModal hooks', () => { }); }); describe('closeAndRefresh', () => { - it('calls closeModal, sets isConfirmed to false, and calls reason.handleClear', () => { + it('behaves the same as close', () => { out.closeAndRefresh(); expect(closeModal).toHaveBeenCalled(); expect(state.setState.confirmed).toHaveBeenCalledWith(false); expect(mockReason.handleClear).toHaveBeenCalled(); }); - it('calls initializeApp api method', () => { - out.closeAndRefresh(); - expect(initializeApp).toHaveBeenCalled(); - }); }); - describe('modalState', () => { - it('returns modalStates.finished if confirmed and submitted', () => { + }); + + describe('SHOW_UNENROLL_SURVEY configuration tests', () => { + beforeEach(() => { + state.mock(); + jest.clearAllMocks(); + useCourseData.mockReturnValue({ courseRun: { courseId: 'test-course-id' } }); + useUnenrollFromCourse.mockReturnValue({ mutate: unenrollFromCourse }); + reasons.useUnenrollReasons.mockImplementation(useUnenrollReasons); + }); + afterEach(() => { + state.restore(); + }); + + describe('when SHOW_UNENROLL_SURVEY is true (default)', () => { + beforeEach(() => { + useAppConfig.mockReturnValue({ SHOW_UNENROLL_SURVEY: true }); + }); + + test('confirm does not call unenrollFromCourse immediately', () => { + out = createUseUnenrollData(); + out.confirm(); + expect(unenrollFromCourse).not.toHaveBeenCalled(); + expect(state.setState.confirmed).toHaveBeenCalledWith(true); + }); + + test('modalState returns reason when confirmed but not submitted', () => { + state.mockVal(state.keys.confirmed, true); + reasons.useUnenrollReasons.mockReturnValueOnce({ ...mockReason, isSubmitted: false }); + out = createUseUnenrollData(); + expect(out.modalState).toEqual(hooks.modalStates.reason); + }); + + test('modalState returns finished when confirmed and submitted', () => { state.mockVal(state.keys.confirmed, true); reasons.useUnenrollReasons.mockReturnValueOnce({ ...mockReason, isSubmitted: true }); out = createUseUnenrollData(); expect(out.modalState).toEqual(hooks.modalStates.finished); }); - it('returns modalStates.reason if confirmed and not submitted', () => { - state.mockVal(state.keys.confirmed, true); + }); + + describe('when SHOW_UNENROLL_SURVEY is false', () => { + beforeEach(() => { + useAppConfig.mockReturnValue({ SHOW_UNENROLL_SURVEY: false }); + }); + + test('confirm calls unenrollFromCourse immediately', () => { out = createUseUnenrollData(); - expect(out.modalState).toEqual(hooks.modalStates.reason); + out.confirm(); + expect(unenrollFromCourse).toHaveBeenCalled(); + expect(state.setState.confirmed).toHaveBeenCalledWith(true); }); - it('returns modalStates.confirm if not confirmed', () => { - state.mockVal(state.keys.confirmed, false); + + test('modalState returns finished when confirmed regardless of submission status', () => { + state.mockVal(state.keys.confirmed, true); + reasons.useUnenrollReasons.mockReturnValueOnce({ ...mockReason, isSubmitted: false }); out = createUseUnenrollData(); - expect(out.modalState).toEqual(hooks.modalStates.confirm); + expect(out.modalState).toEqual(hooks.modalStates.finished); }); }); }); diff --git a/src/containers/UnenrollConfirmModal/hooks/reasons.js b/src/containers/UnenrollConfirmModal/hooks/reasons.js index a80d3b4c0..aefa8960a 100644 --- a/src/containers/UnenrollConfirmModal/hooks/reasons.js +++ b/src/containers/UnenrollConfirmModal/hooks/reasons.js @@ -1,18 +1,19 @@ import React from 'react'; import { - apiHooks, - reduxHooks, + useCourseData, + useCourseTrackingEvent, utilHooks, -} from 'hooks'; -import { StrictDict } from 'utils'; -import track from 'tracking'; - + useEntitlementInfo, +} from '@src/hooks'; +import { StrictDict } from '@src/utils'; +import track from '@src/tracking'; +import { useUnenrollFromCourse } from '@src/data/hooks'; import * as module from './reasons'; +import constants from '../constants'; export const state = StrictDict({ customOption: (val) => React.useState(val), // eslint-disable-line - isSkipped: (val) => React.useState(val), // eslint-disable-line selectedReason: (val) => React.useState(val), // eslint-disable-line isSubmitted: (val) => React.useState(val), //eslint-disable-line }); @@ -20,42 +21,41 @@ export const state = StrictDict({ export const useUnenrollReasons = ({ cardId, }) => { + const courseData = useCourseData(cardId); + const { mutate: unenrollFromCourseMutation } = useUnenrollFromCourse(); // The selected option element from the menu - const [selectedReason, setSelectedReason] = module.state.selectedReason(null); + const [selectedReason, setSelectedReason] = module.state.selectedReason( + constants.reasonKeys.preferNotToSay, + ); // Custom option element entry value const [customOption, setCustomOption] = module.state.customOption(''); - // Did the user choose to skip selecting a reason? - const [isSkipped, setIsSkipped] = module.state.isSkipped(false); // Did the user submit an unenrollment reason const [isSubmitted, setIsSubmitted] = module.state.isSubmitted(false); - const { isEntitlement } = reduxHooks.useCardEntitlementData(cardId); + const { isEntitlement } = useEntitlementInfo(courseData); const submittedReason = selectedReason === 'custom' ? customOption : selectedReason; const hasReason = ![null, ''].includes(submittedReason); - const handleTrackReasons = reduxHooks.useTrackCourseEvent( + const handleTrackReasons = useCourseTrackingEvent( track.engagement.unenrollReason, cardId, submittedReason, isEntitlement, ); - const unenrollFromCourse = apiHooks.useUnenrollFromCourse(cardId); + const unenrollFromCourse = () => { + const courseId = courseData?.courseRun?.courseId; + unenrollFromCourseMutation({ courseId }); + }; const handleClear = () => { setSelectedReason(null); setCustomOption(''); - setIsSkipped(false); setIsSubmitted(false); }; - const handleSkip = () => { - setIsSkipped(true); - unenrollFromCourse(); - }; - const handleSubmit = (e) => { handleTrackReasons(e); setIsSubmitted(true); @@ -68,10 +68,8 @@ export const useUnenrollReasons = ({ return { customOption: { value: customOption, onChange: handleCustomOptionChange }, handleClear, - handleSkip, handleSubmit, hasReason, - isSkipped, isSubmitted, selectOption: handleSelectOption, submittedReason, diff --git a/src/containers/UnenrollConfirmModal/hooks/reasons.test.js b/src/containers/UnenrollConfirmModal/hooks/reasons.test.js deleted file mode 100644 index 334719295..000000000 --- a/src/containers/UnenrollConfirmModal/hooks/reasons.test.js +++ /dev/null @@ -1,192 +0,0 @@ -import { MockUseState } from 'testUtils'; -import track from 'tracking'; -import { - apiHooks, - reduxHooks, - utilHooks, -} from 'hooks'; - -import * as hooks from './reasons'; - -jest.mock('hooks', () => ({ - apiHooks: { - useUnenrollFromCourse: jest.fn((...args) => ({ unenrollFromCourse: args })), - }, - reduxHooks: { - useCardEntitlementData: jest.fn(), - useTrackCourseEvent: jest.fn(), - }, - utilHooks: { - useValueCallback: jest.fn((cb, prereqs) => ({ useValueCallback: { cb, prereqs } })), - }, -})); - -const state = new MockUseState(hooks); -const testValue = 'test-value'; -const testValue2 = 'test-value2'; -const unenrollFromCourse = jest.fn((...args) => ({ unenrollFromCourse: args })); -const trackCourseEvent = jest.fn((e) => ({ courseEvent: e })); -apiHooks.useUnenrollFromCourse.mockReturnValue(unenrollFromCourse); -reduxHooks.useTrackCourseEvent.mockReturnValue(trackCourseEvent); -let out; - -const cardId = 'test-card-id'; -const loadHook = (isEntitlement = false) => { - reduxHooks.useCardEntitlementData.mockReturnValue({ isEntitlement }); - out = hooks.useUnenrollReasons({ cardId }); -}; - -describe('UnenrollConfirmModal reasons hooks', () => { - describe('state fields', () => { - state.testGetter(state.keys.customOption); - state.testGetter(state.keys.isSkipped); - state.testGetter(state.keys.isSubmitted); - state.testGetter(state.keys.selectedReason); - }); - describe('useUnenrollReasons', () => { - beforeEach(() => { - jest.clearAllMocks(); - state.mock(); - loadHook(); - }); - afterEach(() => { - state.restore(); - }); - describe('behavior', () => { - describe('state fields', () => { - it('initializes selectedReason with null', () => { - state.expectInitializedWith(state.keys.selectedReason, null); - }); - it('initializes customOption with empty string', () => { - state.expectInitializedWith(state.keys.customOption, ''); - }); - it('initializes isSkipped with false', () => { - state.expectInitializedWith(state.keys.isSkipped, false); - }); - it('initializes isSubmitted with false', () => { - state.expectInitializedWith(state.keys.isSubmitted, false); - }); - }); - describe('useTrackCourseEvent inititalization', () => { - it('passes custom option if selectedReason is custom', () => { - state.mockVal(state.keys.selectedReason, 'custom'); - state.mockVal(state.keys.customOption, testValue); - loadHook(); - expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith( - track.engagement.unenrollReason, - cardId, - testValue, - false, // isEntitlement - ); - }); - it('passes selected reason if not custom', () => { - state.mockVal(state.keys.selectedReason, testValue2); - state.mockVal(state.keys.customOption, testValue); - loadHook(true); - expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith( - track.engagement.unenrollReason, - cardId, - testValue2, - true, // isEntitlement - ); - }); - }); - it('initializes card entitlement data with cardId', () => { - expect(reduxHooks.useCardEntitlementData).toHaveBeenCalledWith(cardId); - }); - it('initializes unenerollFromCourse event with cardId', () => { - expect(apiHooks.useUnenrollFromCourse).toHaveBeenCalledWith(cardId); - }); - }); - describe('output', () => { - describe('customOption', () => { - test('customOption.value returns custom option', () => { - state.mockVal(state.keys.customOption, testValue); - loadHook(); - expect(out.customOption.value).toEqual(testValue); - }); - test('customOption.onChange returns valueCallback for setCustomOption', () => { - expect(out.customOption.onChange).toEqual( - utilHooks.useValueCallback(state.setState.customOption), - ); - }); - }); - describe('hasReason', () => { - it('returns true if an option is selected other than custom', () => { - state.mockVal(state.keys.selectedReason, testValue); - loadHook(); - expect(out.hasReason).toEqual(true); - }); - it('returns true if custom option is selected and provided', () => { - state.mockVal(state.keys.selectedReason, 'custom'); - state.mockVal(state.keys.customOption, testValue2); - loadHook(); - expect(out.hasReason).toEqual(true); - }); - it('returns false if no option is selected', () => { - state.mockVal(state.keys.selectedReason, null); - loadHook(); - expect(out.hasReason).toEqual(false); - }); - it('returns false if custom option is selcted but not provided', () => { - state.mockVal(state.keys.selectedReason, 'custom'); - state.mockVal(state.keys.customOption, ''); - loadHook(); - expect(out.hasReason).toEqual(false); - }); - }); - describe('handleClear method', () => { - it('resets selected and submitted reasons, custom option and isSkipped', () => { - out.handleClear(); - expect(state.setState.selectedReason).toHaveBeenCalledWith(null); - expect(state.setState.customOption).toHaveBeenCalledWith(''); - expect(state.setState.isSkipped).toHaveBeenCalledWith(false); - expect(state.setState.isSubmitted).toHaveBeenCalledWith(false); - }); - }); - test('handleSkip sets isSkipped and isSubmitted, and unenrolls w/out a reason', () => { - out.handleSkip(); - expect(state.setState.isSkipped).toHaveBeenCalledWith(true); - expect(unenrollFromCourse).toHaveBeenCalledWith(); - }); - describe('handleSubmit', () => { - it('tracks reason event and calls unenroll action', () => { - state.mockVal(state.keys.selectedReason, testValue); - loadHook(); - expect(trackCourseEvent).not.toHaveBeenCalled(); - const event = { test: 'event' }; - out.handleSubmit(event); - expect(trackCourseEvent).toHaveBeenCalledWith(event); - expect(unenrollFromCourse).toHaveBeenCalledWith(); - }); - }); - test('isSkipped returns state value', () => { - state.mockVal(state.keys.isSkipped, testValue); - loadHook(); - expect(out.isSkipped).toEqual(testValue); - }); - test('isSubmitted returns state value', () => { - state.mockVal(state.keys.isSubmitted, testValue); - loadHook(); - expect(out.isSubmitted).toEqual(testValue); - }); - test('selectedOption returns valueCallback for setSelectedReason', () => { - expect(out.selectOption).toEqual( - utilHooks.useValueCallback(state.setState.selectedReason), - ); - }); - describe('submittedReason', () => { - it('returns the selected reason unless is custom, then shows custom option', () => { - state.mockVal(state.keys.selectedReason, testValue); - state.mockVal(state.keys.customOption, testValue2); - loadHook(); - expect(out.submittedReason).toEqual(testValue); - state.mockVal(state.keys.selectedReason, 'custom'); - state.mockVal(state.keys.customOption, testValue2); - loadHook(); - expect(out.submittedReason).toEqual(testValue2); - }); - }); - }); - }); -}); diff --git a/src/containers/UnenrollConfirmModal/hooks/reasons.test.jsx b/src/containers/UnenrollConfirmModal/hooks/reasons.test.jsx new file mode 100644 index 000000000..5be525f85 --- /dev/null +++ b/src/containers/UnenrollConfirmModal/hooks/reasons.test.jsx @@ -0,0 +1,262 @@ +import { renderHook, act } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import track from '@src/tracking'; +import { + useCourseData, + useCourseTrackingEvent, + useEntitlementInfo, +} from '@src/hooks'; +import * as api from '@src/data/services/lms/api'; + +import { useUnenrollReasons } from './reasons'; +import constants from '../constants'; + +jest.mock('@src/data/services/lms/api', () => ({ + unenrollFromCourse: jest.fn(), +})); + +jest.mock('@src/hooks', () => ({ + useCourseTrackingEvent: jest.fn(), + useCourseData: jest.fn(), + useEntitlementInfo: jest.fn(), + utilHooks: { + useValueCallback: jest.fn((cb) => cb), + }, +})); + +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, + }); + const wrapper = ({ children }) => {children}; + return wrapper; +}; + +const cardId = 'test-card-id'; +const courseData = { courseRun: { courseId: cardId } }; +const trackCourseEvent = jest.fn(); + +describe('UnenrollConfirmModal reasons hooks', () => { + beforeEach(() => { + jest.clearAllMocks(); + useCourseData.mockReturnValue(courseData); + useCourseTrackingEvent.mockReturnValue(trackCourseEvent); + useEntitlementInfo.mockReturnValue({ isEntitlement: false }); + api.unenrollFromCourse.mockResolvedValue({}); + }); + + describe('useUnenrollReasons', () => { + it('initializes selectedReason with preferNotToSay constant', () => { + const { result } = renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + expect(result.current.submittedReason).toBe(constants.reasonKeys.preferNotToSay); + }); + + it('initializes customOption with empty string', () => { + const { result } = renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + expect(result.current.customOption.value).toBe(''); + }); + + it('initializes isSubmitted with false', () => { + const { result } = renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + expect(result.current.isSubmitted).toBe(false); + }); + + it('passes custom option as track event value when selectedReason is custom', () => { + const { result } = renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.selectOption('custom'); + }); + + act(() => { + result.current.customOption.onChange('test-custom-value'); + }); + + renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + expect(useCourseTrackingEvent).toHaveBeenCalledWith( + track.engagement.unenrollReason, + cardId, + 'test-custom-value', + false, + ); + }); + + it('passes selected reason as track event value when not custom with entitlement', () => { + useEntitlementInfo.mockReturnValue({ isEntitlement: true }); + + const { result } = renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.selectOption('test-reason'); + }); + + renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + expect(useCourseTrackingEvent).toHaveBeenCalledWith( + track.engagement.unenrollReason, + cardId, + 'test-reason', + true, + ); + }); + + it('initializes card entitlement data with courseData', () => { + renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + expect(useEntitlementInfo).toHaveBeenCalledWith(courseData); + }); + + describe('customOption', () => { + it('returns current custom option value', () => { + const { result } = renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.customOption.onChange('test-value'); + }); + + expect(result.current.customOption.value).toBe('test-value'); + }); + }); + + describe('hasReason', () => { + it('returns true if an option is selected other than custom', () => { + const { result } = renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.selectOption('test-value'); + }); + + expect(result.current.hasReason).toBe(true); + }); + + it('returns true if custom option is selected and provided', () => { + const { result } = renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.selectOption('custom'); + }); + + act(() => { + result.current.customOption.onChange('test-value2'); + }); + + expect(result.current.hasReason).toBe(true); + }); + + it('returns false if no option is selected', () => { + const { result } = renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.selectOption(null); + }); + + expect(result.current.hasReason).toBe(false); + }); + + it('returns false if custom option is selected but not provided', () => { + const { result } = renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.selectOption('custom'); + }); + + expect(result.current.hasReason).toBe(false); + }); + }); + + describe('handleClear method', () => { + it('resets selected reason, custom option and isSubmitted', () => { + const { result } = renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.selectOption('test-reason'); + }); + + act(() => { + result.current.customOption.onChange('test-value'); + }); + + act(() => { + result.current.handleClear(); + }); + + expect(result.current.submittedReason).toBeNull(); + expect(result.current.customOption.value).toBe(''); + expect(result.current.isSubmitted).toBe(false); + }); + }); + + describe('handleSubmit', () => { + it('tracks reason event and calls unenroll api', async () => { + const { result } = renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + const event = { test: 'event' }; + + await act(async () => { + result.current.handleSubmit(event); + }); + + expect(trackCourseEvent).toHaveBeenCalledWith(event); + expect(api.unenrollFromCourse).toHaveBeenCalledWith({ courseId: cardId }); + }); + }); + + describe('submittedReason', () => { + it('returns the selected reason unless is custom, then shows custom option', () => { + const { result } = renderHook(() => useUnenrollReasons({ cardId }), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.selectOption('test-value'); + }); + + act(() => { + result.current.customOption.onChange('test-value2'); + }); + + expect(result.current.submittedReason).toBe('test-value'); + + act(() => { + result.current.selectOption('custom'); + }); + + expect(result.current.submittedReason).toBe('test-value2'); + }); + }); + }); +}); diff --git a/src/containers/UnenrollConfirmModal/index.jsx b/src/containers/UnenrollConfirmModal/index.jsx index df44276be..ce4231dd7 100644 --- a/src/containers/UnenrollConfirmModal/index.jsx +++ b/src/containers/UnenrollConfirmModal/index.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { ModalDialog } from '@openedx/paragon'; -import { nullMethod } from 'utils'; +import { nullMethod } from '../../utils'; import ConfirmPane from './components/ConfirmPane'; import ReasonPane from './components/ReasonPane'; @@ -38,13 +38,13 @@ export const UnenrollConfirmModal = ({ style={{ textAlign: 'start' }} > {(modalState === modalStates.confirm) && ( - + )} {(modalState === modalStates.finished) && ( - + )} {(modalState === modalStates.reason) && ( - + )}
diff --git a/src/containers/UnenrollConfirmModal/index.test.jsx b/src/containers/UnenrollConfirmModal/index.test.jsx index e0a470b1d..ce68be9d4 100644 --- a/src/containers/UnenrollConfirmModal/index.test.jsx +++ b/src/containers/UnenrollConfirmModal/index.test.jsx @@ -1,13 +1,11 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { formatMessage } from '@src/testUtils'; +import { IntlProvider } from '@openedx/frontend-base'; +import { useInitializeLearnerHome } from '@src/data/hooks'; import { UnenrollConfirmModal } from '.'; - import * as hooks from './hooks'; - -jest.mock('./components/ConfirmPane', () => 'ConfirmPane'); -jest.mock('./components/ReasonPane', () => 'ReasonPane'); -jest.mock('./components/FinishedPane', () => 'FinishedPane'); +import messages from './components/messages'; jest.mock('./hooks', () => ({ __esModule: true, @@ -15,11 +13,17 @@ jest.mock('./hooks', () => ({ useUnenrollData: jest.fn(), })); +jest.mock('@src/data/hooks', () => ({ + useInitializeLearnerHome: jest.fn(), +})); + +const mockRefreshList = jest.fn(); +useInitializeLearnerHome.mockReturnValue({ refetch: mockRefreshList }); + describe('UnenrollConfirmModal component', () => { const hookProps = { confirm: jest.fn().mockName('hooks.confirm'), reason: { - isSkipped: false, reasonProps: 'other', }, close: jest.fn().mockName('hooks.close'), @@ -33,29 +37,46 @@ describe('UnenrollConfirmModal component', () => { show: true, cardId, }; - test('hooks called with closeModal and cardId', () => { + it('hooks called with closeModal and cardId', () => { hooks.useUnenrollData.mockReturnValueOnce(hookProps); - shallow(); + render(); expect(hooks.useUnenrollData).toHaveBeenCalledWith({ closeModal, cardId }); }); - test('snapshot: modalStates.confirm', () => { + it('modalStates.confirm display correct component and to have class shadow', () => { hooks.useUnenrollData.mockReturnValueOnce(hookProps); - expect(shallow().snapshot).toMatchSnapshot(); + render(); + const confirmHeader = screen.getByText(formatMessage(messages.confirmHeader)); + expect(confirmHeader).toBeInTheDocument(); + const dialogContainer = screen.getByRole('dialog').firstElementChild; + expect(dialogContainer).toHaveClass('shadow'); }); - test('snapshot: modalStates.finished, reason given', () => { + it('modalStates.finished, reason given, display correct component', () => { hooks.useUnenrollData.mockReturnValueOnce({ ...hookProps, modalState: hooks.modalStates.finished }); - expect(shallow().snapshot).toMatchSnapshot(); + render(); + const finishHeading = screen.getByText(formatMessage(messages.finishHeading)); + expect(finishHeading).toBeInTheDocument(); + const finishMsg = screen.getByText((text) => text.includes('You have been unenrolled from the course')); + expect(finishMsg).toBeInTheDocument(); }); - test('snapshot: modalStates.finished, reason skipped', () => { + it('modalStates.finished, cancel unenrollment', () => { hooks.useUnenrollData.mockReturnValueOnce({ ...hookProps, modalState: hooks.modalStates.finished, - isSkipped: true, }); - expect(shallow().snapshot).toMatchSnapshot(); + render(); + const finishHeading = screen.getByText(formatMessage(messages.finishHeading)); + expect(finishHeading).toBeInTheDocument(); + const okButton = screen.queryByText((text) => text.includes('Ok')); + expect(okButton).toBeInTheDocument(); + const finishMsg = screen.queryByText('You have been unenrolled from the course'); + expect(finishMsg).toBeInTheDocument(); }); - test('snapshot: modalStates.reason, should be fullscreen with no shadow', () => { + it('modalStates.reason, should display correct component with no shadow', () => { hooks.useUnenrollData.mockReturnValueOnce({ ...hookProps, modalState: hooks.modalStates.reason }); - expect(shallow().snapshot).toMatchSnapshot(); + render(); + const reasonHeading = screen.getByText(formatMessage(messages.reasonHeading)); + expect(reasonHeading).toBeInTheDocument(); + const dialogContainer = screen.getByRole('dialog').firstElementChild; + expect(dialogContainer).not.toHaveClass('shadow'); }); }); diff --git a/src/containers/WidgetContainers/AppWrapper/index.jsx b/src/containers/WidgetContainers/AppWrapper/index.jsx deleted file mode 100644 index 72f4a9260..000000000 --- a/src/containers/WidgetContainers/AppWrapper/index.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import PropTypes from 'prop-types'; - -export const AppWrapper = ({ - children, -}) => children; -AppWrapper.propTypes = { - children: PropTypes.oneOfType([ - PropTypes.node, - PropTypes.arrayOf(PropTypes.node), - ]).isRequired, -}; - -export default AppWrapper; diff --git a/src/data/constants/app.js b/src/data/constants/app.js index 1a4b8cec4..f35604e35 100644 --- a/src/data/constants/app.js +++ b/src/data/constants/app.js @@ -1,7 +1,4 @@ -import { getConfig } from '@edx/frontend-platform'; -import { StrictDict } from 'utils'; - -export const routePath = `${getConfig().PUBLIC_PATH}:courseId`; +import { StrictDict } from '../../utils'; export const locationId = window.location.pathname.slice(1); export const SortKeys = StrictDict({ diff --git a/src/data/constants/app.test.js b/src/data/constants/app.test.js index 20c554ae0..a1935e362 100644 --- a/src/data/constants/app.test.js +++ b/src/data/constants/app.test.js @@ -1,24 +1,175 @@ -import * as platform from '@edx/frontend-platform'; import * as constants from './app'; +import { requestStatuses } from './credit'; +import fileConstants, { FileTypes, downloadSingleLimit, downloadAllLimit } from './files'; +import htmlKeysConstants, { buttonStates, htmlProps } from './htmlKeys'; jest.unmock('./app'); -jest.mock('@edx/frontend-platform', () => { - const PUBLIC_PATH = 'test-public-path'; - return { - getConfig: () => ({ PUBLIC_PATH }), - PUBLIC_PATH, - }; -}); - describe('app constants', () => { - test('route path draws from public path and adds courseId', () => { - expect(constants.routePath).toEqual(`${platform.PUBLIC_PATH}:courseId`); - }); test('locationId returns trimmed pathname', () => { const old = window.location; window.location = { pathName: '/somePath.jpg' }; expect(constants.locationId).toEqual(window.location.pathname.slice(1)); window.location = old; }); + + describe('credit constants', () => { + describe('requestStatuses', () => { + it('should have correct pending status', () => { + expect(requestStatuses.pending).toBe('pending'); + }); + + it('should have correct approved status', () => { + expect(requestStatuses.approved).toBe('approved'); + }); + + it('should have correct rejected status', () => { + expect(requestStatuses.rejected).toBe('rejected'); + }); + + it('should have all expected status properties', () => { + const expectedStatuses = ['pending', 'approved', 'rejected']; + const actualStatuses = Object.keys(requestStatuses); + + expect(actualStatuses).toEqual(expect.arrayContaining(expectedStatuses)); + expect(actualStatuses).toHaveLength(expectedStatuses.length); + }); + + it('should have string values for all statuses', () => { + Object.values(requestStatuses).forEach(status => { + expect(typeof status).toBe('string'); + }); + }); + }); + }); + + describe('files constants', () => { + describe('FileTypes', () => { + it('should have correct file type values', () => { + expect(FileTypes.pdf).toBe('pdf'); + expect(FileTypes.jpg).toBe('jpg'); + expect(FileTypes.jpeg).toBe('jpeg'); + expect(FileTypes.png).toBe('png'); + expect(FileTypes.bmp).toBe('bmp'); + expect(FileTypes.txt).toBe('txt'); + expect(FileTypes.gif).toBe('gif'); + expect(FileTypes.jfif).toBe('jfif'); + expect(FileTypes.pjpeg).toBe('pjpeg'); + expect(FileTypes.pjp).toBe('pjp'); + expect(FileTypes.svg).toBe('svg'); + }); + + it('should have all expected file types', () => { + const expectedTypes = ['pdf', 'jpg', 'jpeg', 'png', 'bmp', 'txt', 'gif', 'jfif', 'pjpeg', 'pjp', 'svg']; + const actualTypes = Object.keys(FileTypes); + + expect(actualTypes).toEqual(expect.arrayContaining(expectedTypes)); + expect(actualTypes).toHaveLength(expectedTypes.length); + }); + + it('should have string values for all file types', () => { + Object.values(FileTypes).forEach(fileType => { + expect(typeof fileType).toBe('string'); + }); + }); + }); + + describe('download limits', () => { + it('should have correct downloadSingleLimit value', () => { + expect(downloadSingleLimit).toBe(1610612736); + }); + + it('should have correct downloadAllLimit value', () => { + expect(downloadAllLimit).toBe(10737418240); + }); + + it('should have downloadAllLimit greater than downloadSingleLimit', () => { + expect(downloadAllLimit).toBeGreaterThan(downloadSingleLimit); + }); + + it('should have numeric values for download limits', () => { + expect(typeof downloadSingleLimit).toBe('number'); + expect(typeof downloadAllLimit).toBe('number'); + }); + }); + + describe('default export', () => { + it('should export FileTypes as default', () => { + expect(fileConstants).toBe(FileTypes); + }); + + it('should be the same as named export', () => { + expect(fileConstants).toEqual(FileTypes); + }); + }); + }); + + describe('htmlKeys constants', () => { + describe('buttonStates', () => { + it('should have correct button state values', () => { + expect(buttonStates.default).toBe('default'); + expect(buttonStates.pending).toBe('pending'); + expect(buttonStates.error).toBe('error'); + }); + + it('should have all expected button states', () => { + const expectedStates = ['default', 'pending', 'error']; + const actualStates = Object.keys(buttonStates); + + expect(actualStates).toEqual(expect.arrayContaining(expectedStates)); + expect(actualStates).toHaveLength(expectedStates.length); + }); + + it('should have string values for all button states', () => { + Object.values(buttonStates).forEach(state => { + expect(typeof state).toBe('string'); + }); + }); + }); + + describe('htmlProps', () => { + it('should have correct html property values', () => { + expect(htmlProps.disabled).toBe('disabled'); + expect(htmlProps.href).toBe('href'); + expect(htmlProps.onClick).toBe('onClick'); + expect(htmlProps.onChange).toBe('onChange'); + expect(htmlProps.onBlur).toBe('onBlur'); + expect(htmlProps.size).toBe('size'); + }); + + it('should have all expected html properties', () => { + const expectedProps = ['disabled', 'href', 'onClick', 'onChange', 'onBlur', 'size']; + const actualProps = Object.keys(htmlProps); + + expect(actualProps).toEqual(expect.arrayContaining(expectedProps)); + expect(actualProps).toHaveLength(expectedProps.length); + }); + + it('should have string values for all html properties', () => { + Object.values(htmlProps).forEach(prop => { + expect(typeof prop).toBe('string'); + }); + }); + }); + + describe('default export', () => { + it('should export buttonStates and htmlProps in default object', () => { + expect(htmlKeysConstants.buttonStates).toBe(buttonStates); + expect(htmlKeysConstants.htmlProps).toBe(htmlProps); + }); + + it('should have both properties', () => { + expect(htmlKeysConstants).toHaveProperty('buttonStates'); + expect(htmlKeysConstants).toHaveProperty('htmlProps'); + }); + + it('should only have expected properties', () => { + const expectedKeys = ['buttonStates', 'htmlProps']; + const actualKeys = Object.keys(htmlKeysConstants); + + expect(actualKeys).toEqual(expect.arrayContaining(expectedKeys)); + expect(actualKeys).toHaveLength(expectedKeys.length); + }); + }); + }); }); diff --git a/src/data/constants/credit.js b/src/data/constants/credit.js index a05a72252..b31d5e216 100644 --- a/src/data/constants/credit.js +++ b/src/data/constants/credit.js @@ -1,4 +1,4 @@ -import { StrictDict } from 'utils'; +import { StrictDict } from '../../utils'; export const requestStatuses = StrictDict({ pending: 'pending', diff --git a/src/data/constants/files.js b/src/data/constants/files.js index 46bc5a34f..22d2dd999 100644 --- a/src/data/constants/files.js +++ b/src/data/constants/files.js @@ -1,4 +1,4 @@ -import { StrictDict } from 'utils'; +import { StrictDict } from '@src/utils'; export const FileTypes = StrictDict({ pdf: 'pdf', diff --git a/src/data/constants/htmlKeys.js b/src/data/constants/htmlKeys.js index f384adace..2c051260f 100644 --- a/src/data/constants/htmlKeys.js +++ b/src/data/constants/htmlKeys.js @@ -1,4 +1,4 @@ -import { StrictDict } from 'utils'; +import { StrictDict } from '@src/utils'; export const buttonStates = StrictDict({ default: 'default', diff --git a/src/data/constants/requests.js b/src/data/constants/requests.js index d20142adf..c835bb14f 100644 --- a/src/data/constants/requests.js +++ b/src/data/constants/requests.js @@ -1,4 +1,4 @@ -import { StrictDict } from 'utils'; +import { StrictDict } from '../../utils'; export const RequestStates = StrictDict({ inactive: 'inactive', @@ -17,7 +17,6 @@ export const RequestKeys = StrictDict({ updateEmailSettings: 'updateEmailSettings', enrollEntitlementSession: 'enrollEntitlementSession', leaveEntitlementSession: 'leaveEntitlementSession', - masquerade: 'masquerade', recommendedCourses: 'recommendedCourses', }); diff --git a/src/data/context/Filters.test.tsx b/src/data/context/Filters.test.tsx new file mode 100644 index 000000000..66ceead0b --- /dev/null +++ b/src/data/context/Filters.test.tsx @@ -0,0 +1,771 @@ +import type { ReactNode } from 'react'; +import { renderHook, act } from '@testing-library/react'; +import { FiltersProvider, useFilters } from './FiltersProvider'; + +describe('FiltersProvider and useFilters', () => { + const createWrapper = () => function Wrapper({ children }: { children: ReactNode }) { + return {children}; + }; + + describe('useFilters hook', () => { + describe('initial state', () => { + it('should return initial filters state', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + expect(result.current.filters).toEqual([]); + expect(result.current.sortBy).toBe('enrolled'); + expect(result.current.pageNumber).toBe(1); + expect(typeof result.current.setFilters).toBe('function'); + expect(typeof result.current.addFilter).toBe('function'); + expect(typeof result.current.removeFilter).toBe('function'); + expect(typeof result.current.clearFilters).toBe('function'); + expect(typeof result.current.setSortBy).toBe('function'); + expect(typeof result.current.setPageNumber).toBe('function'); + }); + + it('should have all expected properties in context', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + const expectedProperties = [ + 'filters', + 'sortBy', + 'pageNumber', + 'setFilters', + 'addFilter', + 'removeFilter', + 'clearFilters', + 'setSortBy', + 'setPageNumber', + ]; + + expectedProperties.forEach(prop => { + expect(result.current).toHaveProperty(prop); + }); + }); + }); + + describe('error handling', () => { + it('should throw error when used outside of provider', () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + expect(() => { + renderHook(() => useFilters()); + }).toThrow('useFilters must be used within a FiltersProvider'); + + consoleErrorSpy.mockRestore(); + }); + + it('should throw error with correct message when context is null', () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + try { + renderHook(() => useFilters()); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toBe('useFilters must be used within a FiltersProvider'); + } + + consoleErrorSpy.mockRestore(); + }); + }); + }); + + describe('filters management', () => { + describe('setFilters', () => { + it('should set filters correctly', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + const testFilters = ['inProgress', 'upgraded']; + + act(() => { + result.current.setFilters(testFilters); + }); + + expect(result.current.filters).toEqual(testFilters); + }); + + it('should handle single filter', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setFilters(['notStarted']); + }); + + expect(result.current.filters).toEqual(['notStarted']); + }); + + it('should handle empty filters array', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + act(() => { + result.current.setFilters(['inProgress', 'done']); + }); + act(() => { + result.current.setFilters([]); + }); + + expect(result.current.filters).toEqual([]); + }); + + it('should replace existing filters completely', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setFilters(['inProgress']); + }); + + act(() => { + result.current.setFilters(['done', 'upgraded']); + }); + + expect(result.current.filters).toEqual(['done', 'upgraded']); + }); + + it('should handle multiple filter types', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + const multipleFilters = [ + 'inProgress', + 'notStarted', + 'done', + 'upgraded', + 'notEnrolled', + ]; + + act(() => { + result.current.setFilters(multipleFilters); + }); + + expect(result.current.filters).toEqual(multipleFilters); + }); + }); + + describe('addFilter', () => { + it('should add filter to empty filters array', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.addFilter('inProgress'); + }); + + expect(result.current.filters).toEqual(['inProgress']); + }); + + it('should add filter to existing filters', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setFilters(['inProgress']); + }); + + act(() => { + result.current.addFilter('upgraded'); + }); + + expect(result.current.filters).toEqual(['inProgress', 'upgraded']); + }); + + it('should add duplicate filters (no deduplication)', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.addFilter('inProgress'); + }); + + act(() => { + result.current.addFilter('inProgress'); + }); + + expect(result.current.filters).toEqual(['inProgress', 'inProgress']); + }); + + it('should add multiple filters sequentially', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + const filtersToAdd = ['inProgress', 'upgraded', 'done']; + + filtersToAdd.forEach((filter, index) => { + act(() => { + result.current.addFilter(filter); + }); + expect(result.current.filters).toHaveLength(index + 1); + expect(result.current.filters).toContain(filter); + }); + + expect(result.current.filters).toEqual(filtersToAdd); + }); + }); + + describe('removeFilter', () => { + it('should remove specific filter from filters array', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setFilters(['inProgress', 'upgraded', 'done']); + }); + + act(() => { + result.current.removeFilter('upgraded'); + }); + + expect(result.current.filters).toEqual(['inProgress', 'done']); + }); + + it('should handle removing non-existent filter', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setFilters(['inProgress', 'upgraded']); + }); + + act(() => { + result.current.removeFilter('nonExistent'); + }); + + expect(result.current.filters).toEqual(['inProgress', 'upgraded']); + }); + + it('should remove filter from empty array', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.removeFilter('inProgress'); + }); + + expect(result.current.filters).toEqual([]); + }); + + it('should remove last filter leaving empty array', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setFilters(['inProgress']); + }); + + act(() => { + result.current.removeFilter('inProgress'); + }); + + expect(result.current.filters).toEqual([]); + }); + }); + + describe('clearFilters', () => { + it('should clear all filters', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setFilters(['inProgress', 'upgraded', 'done']); + }); + + act(() => { + result.current.clearFilters(); + }); + + expect(result.current.filters).toEqual([]); + }); + + it('should clear filters when already empty', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.clearFilters(); + }); + + expect(result.current.filters).toEqual([]); + }); + + it('should not affect sortBy and pageNumber when clearing filters', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setFilters(['inProgress']); + result.current.setSortBy('title'); + result.current.setPageNumber(3); + }); + + act(() => { + result.current.clearFilters(); + }); + + expect(result.current.filters).toEqual([]); + expect(result.current.sortBy).toBe('title'); + expect(result.current.pageNumber).toBe(3); + }); + }); + }); + + describe('sortBy management', () => { + it('should have initial sortBy as "enrolled"', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + expect(result.current.sortBy).toBe('enrolled'); + }); + + it('should set sortBy to "title"', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setSortBy('title'); + }); + + expect(result.current.sortBy).toBe('title'); + }); + + it('should set sortBy to "enrolled"', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setSortBy('title'); + }); + + act(() => { + result.current.setSortBy('enrolled'); + }); + + expect(result.current.sortBy).toBe('enrolled'); + }); + + it('should change sortBy options multiple times', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + const sortSequence = ['title', 'enrolled', 'title', 'enrolled'] as const; + + sortSequence.forEach(sortOption => { + act(() => { + result.current.setSortBy(sortOption); + }); + expect(result.current.sortBy).toBe(sortOption); + }); + }); + }); + + describe('pageNumber management', () => { + it('should have initial pageNumber as 1', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + expect(result.current.pageNumber).toBe(1); + }); + + it('should set pageNumber correctly', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setPageNumber(5); + }); + + expect(result.current.pageNumber).toBe(5); + }); + + it('should handle zero and negative page numbers', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setPageNumber(0); + }); + expect(result.current.pageNumber).toBe(0); + + act(() => { + result.current.setPageNumber(-1); + }); + expect(result.current.pageNumber).toBe(-1); + }); + + it('should handle large page numbers', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setPageNumber(999999); + }); + + expect(result.current.pageNumber).toBe(999999); + }); + + it('should change pageNumber multiple times', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + const pageSequence = [2, 10, 1, 5, 3]; + + pageSequence.forEach(page => { + act(() => { + result.current.setPageNumber(page); + }); + expect(result.current.pageNumber).toBe(page); + }); + }); + }); + + describe('reducer functionality', () => { + it('should handle SET_FILTERS action correctly', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + const testFilters = ['filter1', 'filter2']; + + act(() => { + result.current.setFilters(testFilters); + }); + + expect(result.current.filters).toEqual(testFilters); + }); + + it('should handle ADD_FILTER action correctly', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.addFilter('testFilter'); + }); + + expect(result.current.filters).toEqual(['testFilter']); + }); + + it('should handle REMOVE_FILTER action correctly', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setFilters(['filter1', 'filter2', 'filter3']); + }); + + act(() => { + result.current.removeFilter('filter2'); + }); + + expect(result.current.filters).toEqual(['filter1', 'filter3']); + }); + + it('should handle CLEAR_FILTERS action correctly', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setFilters(['filter1', 'filter2']); + }); + + act(() => { + result.current.clearFilters(); + }); + + expect(result.current.filters).toEqual([]); + }); + + it('should handle SET_SORT_BY action correctly', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setSortBy('title'); + }); + + expect(result.current.sortBy).toBe('title'); + }); + + it('should handle SET_PAGE_NUMBER action correctly', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setPageNumber(10); + }); + + expect(result.current.pageNumber).toBe(10); + }); + }); + + describe('integration scenarios', () => { + it('should handle complete state management workflow', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + expect(result.current.filters).toEqual([]); + expect(result.current.sortBy).toBe('enrolled'); + expect(result.current.pageNumber).toBe(1); + act(() => { + result.current.addFilter('inProgress'); + result.current.addFilter('upgraded'); + }); + expect(result.current.filters).toEqual(['inProgress', 'upgraded']); + act(() => { + result.current.setSortBy('title'); + result.current.setPageNumber(2); + }); + expect(result.current.sortBy).toBe('title'); + expect(result.current.pageNumber).toBe(2); + act(() => { + result.current.removeFilter('inProgress'); + }); + expect(result.current.filters).toEqual(['upgraded']); + act(() => { + result.current.setFilters(['done', 'notEnrolled']); + }); + expect(result.current.filters).toEqual(['done', 'notEnrolled']); + act(() => { + result.current.clearFilters(); + }); + expect(result.current.filters).toEqual([]); + expect(result.current.sortBy).toBe('title'); + expect(result.current.pageNumber).toBe(2); + }); + + it('should handle realistic course filtering workflow', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + expect(result.current.pageNumber).toBe(1); + act(() => { + result.current.addFilter('inProgress'); + }); + expect(result.current.filters).toContain('inProgress'); + act(() => { + result.current.addFilter('upgraded'); + }); + expect(result.current.filters).toEqual(['inProgress', 'upgraded']); + act(() => { + result.current.setSortBy('title'); + }); + expect(result.current.sortBy).toBe('title'); + act(() => { + result.current.setPageNumber(2); + }); + expect(result.current.pageNumber).toBe(2); + act(() => { + result.current.removeFilter('inProgress'); + }); + expect(result.current.filters).toEqual(['upgraded']); + act(() => { + result.current.clearFilters(); + }); + expect(result.current.filters).toEqual([]); + expect(result.current.sortBy).toBe('title'); + expect(result.current.pageNumber).toBe(2); + }); + + it('should handle multiple providers independently', () => { + const wrapper1 = createWrapper(); + const wrapper2 = createWrapper(); + + const { result: result1 } = renderHook(() => useFilters(), { wrapper: wrapper1 }); + const { result: result2 } = renderHook(() => useFilters(), { wrapper: wrapper2 }); + act(() => { + result1.current.addFilter('filter1'); + result1.current.setSortBy('title'); + result1.current.setPageNumber(3); + }); + + act(() => { + result2.current.addFilter('filter2'); + result2.current.setSortBy('enrolled'); + result2.current.setPageNumber(5); + }); + expect(result1.current.filters).toEqual(['filter1']); + expect(result1.current.sortBy).toBe('title'); + expect(result1.current.pageNumber).toBe(3); + + expect(result2.current.filters).toEqual(['filter2']); + expect(result2.current.sortBy).toBe('enrolled'); + expect(result2.current.pageNumber).toBe(5); + }); + }); + + describe('provider functionality', () => { + it('should provide context value correctly', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + expect(result.current).toBeDefined(); + expect(result.current.filters).toBeDefined(); + expect(result.current.sortBy).toBeDefined(); + expect(result.current.pageNumber).toBeDefined(); + expect(typeof result.current.setFilters).toBe('function'); + expect(typeof result.current.addFilter).toBe('function'); + expect(typeof result.current.removeFilter).toBe('function'); + expect(typeof result.current.clearFilters).toBe('function'); + expect(typeof result.current.setSortBy).toBe('function'); + expect(typeof result.current.setPageNumber).toBe('function'); + }); + + it('should handle provider re-renders without losing state', () => { + const TestWrapper = ({ rerenderTrigger, children }: { rerenderTrigger: number, children: ReactNode }) => ( + +
+ {children} +
+
+ ); + + const { result, rerender } = renderHook(() => useFilters(), { + wrapper: ({ children }) => {children}, + }); + + act(() => { + result.current.setFilters(['persistentFilter']); + result.current.setSortBy('title'); + result.current.setPageNumber(5); + }); + + rerender({ rerenderTrigger: 2 }); + expect(result.current.filters).toEqual(['persistentFilter']); + expect(result.current.sortBy).toBe('title'); + expect(result.current.pageNumber).toBe(5); + }); + + it('should maintain function referential stability', () => { + const { result, rerender } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + const initialFunctions = { + setFilters: result.current.setFilters, + addFilter: result.current.addFilter, + removeFilter: result.current.removeFilter, + clearFilters: result.current.clearFilters, + setSortBy: result.current.setSortBy, + setPageNumber: result.current.setPageNumber, + }; + rerender(); + + expect(result.current.setFilters).toBe(initialFunctions.setFilters); + expect(result.current.addFilter).toBe(initialFunctions.addFilter); + expect(result.current.removeFilter).toBe(initialFunctions.removeFilter); + expect(result.current.clearFilters).toBe(initialFunctions.clearFilters); + expect(result.current.setSortBy).toBe(initialFunctions.setSortBy); + expect(result.current.setPageNumber).toBe(initialFunctions.setPageNumber); + }); + }); + + describe('memoization behavior', () => { + it('should memoize context value when state does not change', () => { + const { result, rerender } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + const firstContextValue = result.current; + rerender(); + expect(result.current.setFilters).toBe(firstContextValue.setFilters); + expect(result.current.addFilter).toBe(firstContextValue.addFilter); + expect(result.current.removeFilter).toBe(firstContextValue.removeFilter); + expect(result.current.clearFilters).toBe(firstContextValue.clearFilters); + expect(result.current.setSortBy).toBe(firstContextValue.setSortBy); + expect(result.current.setPageNumber).toBe(firstContextValue.setPageNumber); + }); + + it('should update memoized value when state changes', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + const initialValue = result.current; + + act(() => { + result.current.addFilter('newFilter'); + }); + expect(result.current.filters).not.toEqual(initialValue.filters); + expect(result.current.filters).toContain('newFilter'); + }); + }); + + describe('edge cases and type safety', () => { + it('should handle empty string filters', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.addFilter(''); + }); + + expect(result.current.filters).toEqual(['']); + }); + + it('should handle special character filters', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + + const specialFilters = ['filter-with-dash', 'filter_with_underscore', 'filter with space', 'filter!@#$%']; + + specialFilters.forEach(filter => { + act(() => { + result.current.addFilter(filter); + }); + }); + + expect(result.current.filters).toEqual(specialFilters); + }); + + it('should maintain type safety for SortOption', () => { + const { result } = renderHook(() => useFilters(), { + wrapper: createWrapper(), + }); + const validSortOptions: ('enrolled' | 'title')[] = ['enrolled', 'title']; + + validSortOptions.forEach(option => { + act(() => { + result.current.setSortBy(option); + }); + expect(result.current.sortBy).toBe(option); + }); + }); + }); +}); diff --git a/src/data/context/FiltersProvider.tsx b/src/data/context/FiltersProvider.tsx new file mode 100644 index 000000000..f0502859b --- /dev/null +++ b/src/data/context/FiltersProvider.tsx @@ -0,0 +1,66 @@ +import React, { + createContext, useContext, useState, useMemo, useCallback, +} from 'react'; + +type SortOption = 'enrolled' | 'title'; + +interface FiltersContextType { + filters: string[], + sortBy: SortOption, + pageNumber: number, + setFilters: (newFilters: string[]) => void, + addFilter: (filter: string) => void, + removeFilter: (filter: string) => void, + clearFilters: () => void, + setSortBy: (sortBy: SortOption) => void, + setPageNumber: (pageNumber: number) => void, +} + +const FiltersContext = createContext(null); + +export const FiltersProvider = ({ children }: { children: React.ReactNode }) => { + const [filters, setFilters] = useState([]); + const [sortBy, setSortBy] = useState('enrolled'); + const [pageNumber, setPageNumber] = useState(1); + + const addFilter = useCallback((filter: string) => { + setFilters(prev => [...prev, filter]); + }, []); + + const removeFilter = useCallback((filter: string) => { + setFilters(prev => prev.filter(item => item !== filter)); + }, []); + + const clearFilters = useCallback(() => { + setFilters([]); + }, []); + + const contextValue = useMemo( + () => ({ + filters, + sortBy, + pageNumber, + setFilters, + addFilter, + removeFilter, + clearFilters, + setSortBy, + setPageNumber, + }), + [filters, sortBy, pageNumber, addFilter, removeFilter, clearFilters], + ); + + return ( + + {children} + + ); +}; + +export const useFilters = () => { + const context = useContext(FiltersContext); + if (!context) { + throw new Error('useFilters must be used within a FiltersProvider'); + } + return context; +}; diff --git a/src/data/context/Masquerade.test.tsx b/src/data/context/Masquerade.test.tsx new file mode 100644 index 000000000..1765c5e6e --- /dev/null +++ b/src/data/context/Masquerade.test.tsx @@ -0,0 +1,579 @@ +import type { ReactNode } from 'react'; +import { renderHook, act } from '@testing-library/react'; +import { MasqueradeProvider, useMasquerade } from './MasqueradeProvider'; + +describe('MasqueradeProvider and useMasquerade', () => { + const createWrapper = () => function Wrapper({ children }: { children: ReactNode }) { + return {children}; + }; + + describe('useMasquerade hook', () => { + describe('initial state', () => { + it('should return initial masquerade state', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + expect(result.current.masqueradeUser).toBeUndefined(); + expect(typeof result.current.setMasqueradeUser).toBe('function'); + }); + + it('should have expected properties in context', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const expectedProperties = ['masqueradeUser', 'setMasqueradeUser']; + + expectedProperties.forEach(prop => { + expect(result.current).toHaveProperty(prop); + }); + }); + }); + + describe('error handling', () => { + it('should throw error when used outside of provider', () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + expect(() => { + renderHook(() => useMasquerade()); + }).toThrow('useMasquerade must be used within a MasqueradeProvider'); + + consoleErrorSpy.mockRestore(); + }); + + it('should throw error with correct message when context is null', () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + try { + renderHook(() => useMasquerade()); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toBe('useMasquerade must be used within a MasqueradeProvider'); + } + + consoleErrorSpy.mockRestore(); + }); + }); + }); + + describe('masquerade user management', () => { + describe('setMasqueradeUser', () => { + it('should set masquerade user to string value', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const testUser = 'test-user@example.com'; + + act(() => { + result.current.setMasqueradeUser(testUser); + }); + + expect(result.current.masqueradeUser).toBe(testUser); + }); + + it('should set masquerade user to undefined', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setMasqueradeUser('initial-user'); + }); + + act(() => { + result.current.setMasqueradeUser(undefined); + }); + + expect(result.current.masqueradeUser).toBeUndefined(); + }); + + it('should handle email format users', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const emailUser = 'student@university.edu'; + + act(() => { + result.current.setMasqueradeUser(emailUser); + }); + + expect(result.current.masqueradeUser).toBe(emailUser); + }); + + it('should handle username format users', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const usernameUser = 'student123'; + + act(() => { + result.current.setMasqueradeUser(usernameUser); + }); + + expect(result.current.masqueradeUser).toBe(usernameUser); + }); + + it('should handle empty string users', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setMasqueradeUser(''); + }); + + expect(result.current.masqueradeUser).toBe(''); + }); + + it('should handle users with special characters', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const specialUser = 'user.name+tag@domain-name.co.uk'; + + act(() => { + result.current.setMasqueradeUser(specialUser); + }); + + expect(result.current.masqueradeUser).toBe(specialUser); + }); + + it('should replace existing masquerade user', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setMasqueradeUser('first-user'); + }); + + act(() => { + result.current.setMasqueradeUser('second-user'); + }); + + expect(result.current.masqueradeUser).toBe('second-user'); + }); + + it('should handle multiple sequential updates', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const users = ['user1@test.com', 'user2@test.com', 'user3@test.com']; + + users.forEach(user => { + act(() => { + result.current.setMasqueradeUser(user); + }); + expect(result.current.masqueradeUser).toBe(user); + }); + }); + + it('should handle unicode characters in usernames', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const unicodeUser = 'José.García@université.fr'; + + act(() => { + result.current.setMasqueradeUser(unicodeUser); + }); + + expect(result.current.masqueradeUser).toBe(unicodeUser); + }); + + it('should handle very long usernames', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const longUser = `${'a'.repeat(100)}@example.com`; + + act(() => { + result.current.setMasqueradeUser(longUser); + }); + + expect(result.current.masqueradeUser).toBe(longUser); + }); + }); + + describe('masquerade workflow scenarios', () => { + it('should handle start masquerade workflow', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + expect(result.current.masqueradeUser).toBeUndefined(); + + act(() => { + result.current.setMasqueradeUser('target-student@university.edu'); + }); + + expect(result.current.masqueradeUser).toBe('target-student@university.edu'); + }); + + it('should handle stop masquerade workflow', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setMasqueradeUser('masqueraded-user'); + }); + + act(() => { + result.current.setMasqueradeUser(undefined); + }); + + expect(result.current.masqueradeUser).toBeUndefined(); + }); + + it('should handle switch masquerade user workflow', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setMasqueradeUser('first-student'); + }); + expect(result.current.masqueradeUser).toBe('first-student'); + + act(() => { + result.current.setMasqueradeUser('second-student'); + }); + expect(result.current.masqueradeUser).toBe('second-student'); + + act(() => { + result.current.setMasqueradeUser(undefined); + }); + expect(result.current.masqueradeUser).toBeUndefined(); + }); + + it('should handle admin masquerade session', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + const user = 'user123@institution.edu'; + + act(() => { + result.current.setMasqueradeUser(user); + }); + expect(result.current.masqueradeUser).toBe(user); + + act(() => { + result.current.setMasqueradeUser(undefined); + }); + expect(result.current.masqueradeUser).toBeUndefined(); + }); + }); + }); + + describe('reducer functionality', () => { + it('should handle SET_MASQUERADE_USER action with string payload', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setMasqueradeUser('test-user'); + }); + + expect(result.current.masqueradeUser).toBe('test-user'); + }); + + it('should handle SET_MASQUERADE_USER action with undefined payload', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setMasqueradeUser('user'); + }); + + act(() => { + result.current.setMasqueradeUser(undefined); + }); + + expect(result.current.masqueradeUser).toBeUndefined(); + }); + + it('should maintain state immutability', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.setMasqueradeUser('immutable-test'); + }); + + const currentUser = result.current.masqueradeUser; + + act(() => { + result.current.setMasqueradeUser('new-user'); + }); + + expect(currentUser).toBe('immutable-test'); + expect(result.current.masqueradeUser).toBe('new-user'); + }); + + it('should handle rapid successive updates', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const updates = ['user1', 'user2', 'user3', undefined, 'user4']; + + act(() => { + updates.forEach(user => { + result.current.setMasqueradeUser(user); + }); + }); + + expect(result.current.masqueradeUser).toBe('user4'); + }); + }); + + describe('provider functionality', () => { + it('should provide context value correctly', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + expect(result.current).toBeDefined(); + expect(result.current.masqueradeUser).toBeUndefined(); + expect(typeof result.current.setMasqueradeUser).toBe('function'); + }); + + it('should handle provider re-renders without losing state', () => { + const TestWrapper = ({ rerenderTrigger, children }: { rerenderTrigger: number, children: ReactNode }) => ( + +
+ {children} +
+
+ ); + + const { result, rerender } = renderHook(() => useMasquerade(), { + wrapper: ({ children }) => {children}, + }); + + act(() => { + result.current.setMasqueradeUser('persistent-user'); + }); + + rerender({ rerenderTrigger: 2 }); + + expect(result.current.masqueradeUser).toBe('persistent-user'); + }); + + it('should maintain function referential stability', () => { + const { result, rerender } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const initialSetFunction = result.current.setMasqueradeUser; + + rerender(); + + expect(result.current.setMasqueradeUser).toBe(initialSetFunction); + }); + + it('should handle multiple providers independently', () => { + const wrapper1 = createWrapper(); + const wrapper2 = createWrapper(); + + const { result: result1 } = renderHook(() => useMasquerade(), { wrapper: wrapper1 }); + const { result: result2 } = renderHook(() => useMasquerade(), { wrapper: wrapper2 }); + + act(() => { + result1.current.setMasqueradeUser('user1'); + }); + + act(() => { + result2.current.setMasqueradeUser('user2'); + }); + + expect(result1.current.masqueradeUser).toBe('user1'); + expect(result2.current.masqueradeUser).toBe('user2'); + }); + }); + + describe('memoization behavior', () => { + it('should memoize context value when state does not change', () => { + const { result, rerender } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const firstSetFunction = result.current.setMasqueradeUser; + + rerender(); + + expect(result.current.setMasqueradeUser).toBe(firstSetFunction); + }); + + it('should update memoized value when state changes', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const initialUser = result.current.masqueradeUser; + + act(() => { + result.current.setMasqueradeUser('changed-user'); + }); + + expect(result.current.masqueradeUser).not.toBe(initialUser); + expect(result.current.masqueradeUser).toBe('changed-user'); + }); + }); + + describe('integration scenarios', () => { + it('should handle realistic masquerade admin workflow', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + expect(result.current.masqueradeUser).toBeUndefined(); + + act(() => { + result.current.setMasqueradeUser('john.doe@university.edu'); + }); + expect(result.current.masqueradeUser).toBe('john.doe@university.edu'); + + act(() => { + result.current.setMasqueradeUser('jane.smith@university.edu'); + }); + expect(result.current.masqueradeUser).toBe('jane.smith@university.edu'); + + act(() => { + result.current.setMasqueradeUser(undefined); + }); + expect(result.current.masqueradeUser).toBeUndefined(); + }); + + it('should handle development testing workflow', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const testUsers = [ + 'test-student-1@test.edu', + 'test-instructor@test.edu', + 'test-admin@test.edu', + ]; + + testUsers.forEach(testUser => { + act(() => { + result.current.setMasqueradeUser(testUser); + }); + expect(result.current.masqueradeUser).toBe(testUser); + + act(() => { + result.current.setMasqueradeUser(undefined); + }); + expect(result.current.masqueradeUser).toBeUndefined(); + }); + }); + + it('should handle concurrent masquerade sessions in different contexts', () => { + const wrapper1 = createWrapper(); + const wrapper2 = createWrapper(); + + const { result: admin1 } = renderHook(() => useMasquerade(), { wrapper: wrapper1 }); + const { result: admin2 } = renderHook(() => useMasquerade(), { wrapper: wrapper2 }); + + act(() => { + admin1.current.setMasqueradeUser('student1@university.edu'); + admin2.current.setMasqueradeUser('student2@university.edu'); + }); + + expect(admin1.current.masqueradeUser).toBe('student1@university.edu'); + expect(admin2.current.masqueradeUser).toBe('student2@university.edu'); + + act(() => { + admin1.current.setMasqueradeUser(undefined); + }); + + expect(admin1.current.masqueradeUser).toBeUndefined(); + expect(admin2.current.masqueradeUser).toBe('student2@university.edu'); + }); + }); + + describe('type safety and edge cases', () => { + it('should handle type-safe user parameter', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const stringUser = 'typed-user@test.com'; + const undefinedUser = undefined; + + act(() => { + result.current.setMasqueradeUser(stringUser); + }); + expect(result.current.masqueradeUser).toBe(stringUser); + + act(() => { + result.current.setMasqueradeUser(undefinedUser); + }); + expect(result.current.masqueradeUser).toBe(undefinedUser); + }); + + it('should handle whitespace-only usernames', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const whitespaceUser = ' '; + + act(() => { + result.current.setMasqueradeUser(whitespaceUser); + }); + + expect(result.current.masqueradeUser).toBe(whitespaceUser); + }); + + it('should handle tab and newline characters', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const specialCharsUser = 'user\t\nwith\rspecial@chars.com'; + + act(() => { + result.current.setMasqueradeUser(specialCharsUser); + }); + + expect(result.current.masqueradeUser).toBe(specialCharsUser); + }); + + it('should maintain state consistency across multiple updates', () => { + const { result } = renderHook(() => useMasquerade(), { + wrapper: createWrapper(), + }); + + const updates = [ + 'user1', + undefined, + 'user2', + '', + 'user3', + undefined, + 'final-user', + ]; + + updates.forEach(update => { + act(() => { + result.current.setMasqueradeUser(update); + }); + expect(result.current.masqueradeUser).toBe(update); + }); + }); + }); +}); diff --git a/src/data/context/MasqueradeProvider.tsx b/src/data/context/MasqueradeProvider.tsx new file mode 100644 index 000000000..f424ef35c --- /dev/null +++ b/src/data/context/MasqueradeProvider.tsx @@ -0,0 +1,37 @@ +import React, { + createContext, useContext, useMemo, useState, ReactNode, +} from 'react'; + +interface MasqueradeContextType { + masqueradeUser: string | undefined, + setMasqueradeUser: (user: string | undefined) => void, +} + +const MasqueradeContext = createContext(null); + +interface MasqueradeProviderProps { + children: ReactNode, +} + +export const MasqueradeProvider: React.FC = ({ children }) => { + const [masqueradeUser, setMasqueradeUser] = useState(undefined); + + const contextValue = useMemo(() => ({ + masqueradeUser, + setMasqueradeUser, + }), [masqueradeUser]); + + return ( + + {children} + + ); +}; + +export const useMasquerade = (): MasqueradeContextType => { + const context = useContext(MasqueradeContext); + if (!context) { + throw new Error('useMasquerade must be used within a MasqueradeProvider'); + } + return context; +}; diff --git a/src/data/context/SelectSession.test.tsx b/src/data/context/SelectSession.test.tsx new file mode 100644 index 000000000..ccd85ab44 --- /dev/null +++ b/src/data/context/SelectSession.test.tsx @@ -0,0 +1,663 @@ +import type { ReactNode } from 'react'; +import { renderHook, act } from '@testing-library/react'; +import { SelectSessionModalProvider, useSelectSessionModal } from './SelectSessionProvider'; + +describe('SelectSessionModalProvider and useSelectSessionModal', () => { + const createWrapper = () => function Wrapper({ children }: { children: ReactNode }) { + return {children}; + }; + + describe('useSelectSessionModal hook', () => { + describe('initial state', () => { + it('should return initial select session modal state', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + expect(result.current.selectSessionModal.cardId).toBeNull(); + expect(typeof result.current.updateSelectSessionModal).toBe('function'); + expect(typeof result.current.closeSelectSessionModal).toBe('function'); + }); + + it('should have all expected properties in context', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const expectedProperties = [ + 'selectSessionModal', + 'updateSelectSessionModal', + 'closeSelectSessionModal', + ]; + + expectedProperties.forEach(prop => { + expect(result.current).toHaveProperty(prop); + }); + }); + + it('should have selectSessionModal with cardId property', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + expect(result.current.selectSessionModal).toHaveProperty('cardId'); + expect(result.current.selectSessionModal.cardId).toBeNull(); + }); + }); + + describe('error handling', () => { + it('should throw error when used outside of provider', () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + expect(() => { + renderHook(() => useSelectSessionModal()); + }).toThrow('useSelectSessionModal must be used within a SelectSessionModalProvider'); + + consoleErrorSpy.mockRestore(); + }); + + it('should throw error with correct message when context is null', () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + try { + renderHook(() => useSelectSessionModal()); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toBe('useSelectSessionModal must be used within a SelectSessionModalProvider'); + } + + consoleErrorSpy.mockRestore(); + }); + }); + }); + + describe('modal state management', () => { + describe('updateSelectSessionModal', () => { + it('should set cardId to string value', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const testCardId = 'card-123'; + + act(() => { + result.current.updateSelectSessionModal(testCardId); + }); + + expect(result.current.selectSessionModal.cardId).toBe(testCardId); + }); + + it('should set cardId to null', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.updateSelectSessionModal('card-123'); + }); + + act(() => { + result.current.updateSelectSessionModal(null); + }); + + expect(result.current.selectSessionModal.cardId).toBeNull(); + }); + + it('should handle various cardId formats', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const cardIds = [ + 'card-1', + 'card-abc-123', + 'course-card-uuid-456', + 'simple-id', + 'id_with_underscores', + 'id-with-dashes', + '12345', + 'a', + ]; + + cardIds.forEach(cardId => { + act(() => { + result.current.updateSelectSessionModal(cardId); + }); + expect(result.current.selectSessionModal.cardId).toBe(cardId); + }); + }); + + it('should handle empty string cardId', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.updateSelectSessionModal(''); + }); + + expect(result.current.selectSessionModal.cardId).toBe(''); + }); + + it('should handle long cardId strings', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const longCardId = `card-${'a'.repeat(100)}`; + + act(() => { + result.current.updateSelectSessionModal(longCardId); + }); + + expect(result.current.selectSessionModal.cardId).toBe(longCardId); + }); + + it('should handle special characters in cardId', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const specialCardId = 'card@123#$%^&*()'; + + act(() => { + result.current.updateSelectSessionModal(specialCardId); + }); + + expect(result.current.selectSessionModal.cardId).toBe(specialCardId); + }); + + it('should handle unicode characters in cardId', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const unicodeCardId = 'card-节点-123'; + + act(() => { + result.current.updateSelectSessionModal(unicodeCardId); + }); + + expect(result.current.selectSessionModal.cardId).toBe(unicodeCardId); + }); + + it('should replace existing cardId', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.updateSelectSessionModal('first-card'); + }); + + act(() => { + result.current.updateSelectSessionModal('second-card'); + }); + + expect(result.current.selectSessionModal.cardId).toBe('second-card'); + }); + + it('should handle multiple sequential updates', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const cardIds = ['card-1', 'card-2', 'card-3', null, 'card-4']; + + cardIds.forEach(cardId => { + act(() => { + result.current.updateSelectSessionModal(cardId); + }); + expect(result.current.selectSessionModal.cardId).toBe(cardId); + }); + }); + }); + + describe('closeSelectSessionModal', () => { + it('should set cardId to null when modal is closed', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.updateSelectSessionModal('card-123'); + }); + + act(() => { + result.current.closeSelectSessionModal(); + }); + + expect(result.current.selectSessionModal.cardId).toBeNull(); + }); + + it('should close modal when cardId is already null', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.closeSelectSessionModal(); + }); + + expect(result.current.selectSessionModal.cardId).toBeNull(); + }); + + it('should close modal multiple times without error', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.closeSelectSessionModal(); + result.current.closeSelectSessionModal(); + result.current.closeSelectSessionModal(); + }); + + expect(result.current.selectSessionModal.cardId).toBeNull(); + }); + + it('should work correctly after multiple open and close cycles', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const cycles = [ + 'card-1', + 'card-2', + 'card-3', + ]; + + cycles.forEach(cardId => { + act(() => { + result.current.updateSelectSessionModal(cardId); + }); + expect(result.current.selectSessionModal.cardId).toBe(cardId); + + act(() => { + result.current.closeSelectSessionModal(); + }); + expect(result.current.selectSessionModal.cardId).toBeNull(); + }); + }); + }); + }); + + describe('reducer functionality', () => { + it('should handle UPDATE_SELECT_SESSION_MODAL action correctly', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.updateSelectSessionModal('test-card'); + }); + + expect(result.current.selectSessionModal.cardId).toBe('test-card'); + }); + + it('should handle CLOSE_SELECT_SESSION_MODAL action correctly', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.updateSelectSessionModal('test-card'); + }); + + act(() => { + result.current.closeSelectSessionModal(); + }); + + expect(result.current.selectSessionModal.cardId).toBeNull(); + }); + + it('should maintain state immutability', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.updateSelectSessionModal('immutable-test'); + }); + + const currentState = result.current.selectSessionModal; + + act(() => { + result.current.updateSelectSessionModal('new-card'); + }); + + expect(currentState.cardId).toBe('immutable-test'); + expect(result.current.selectSessionModal.cardId).toBe('new-card'); + expect(result.current.selectSessionModal).not.toBe(currentState); + }); + + it('should handle rapid successive updates', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const updates = ['card1', 'card2', 'card3', null, 'card4']; + + act(() => { + updates.forEach(cardId => { + result.current.updateSelectSessionModal(cardId); + }); + }); + + expect(result.current.selectSessionModal.cardId).toBe('card4'); + }); + + it('should create new state object on each update', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const initialState = result.current.selectSessionModal; + + act(() => { + result.current.updateSelectSessionModal('new-card'); + }); + + const updatedState = result.current.selectSessionModal; + + expect(updatedState).not.toBe(initialState); + expect(updatedState.cardId).not.toBe(initialState.cardId); + }); + }); + + describe('provider functionality', () => { + it('should provide context value correctly', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + expect(result.current).toBeDefined(); + expect(result.current.selectSessionModal).toBeDefined(); + expect(result.current.selectSessionModal.cardId).toBeNull(); + expect(typeof result.current.updateSelectSessionModal).toBe('function'); + expect(typeof result.current.closeSelectSessionModal).toBe('function'); + }); + + it('should handle provider re-renders without losing state', () => { + const TestWrapper = ({ rerenderTrigger, children }: { rerenderTrigger: number, children: ReactNode }) => ( + +
+ {children} +
+
+ ); + + const { result, rerender } = renderHook(() => useSelectSessionModal(), { + wrapper: ({ children }) => {children}, + }); + + act(() => { + result.current.updateSelectSessionModal('persistent-card'); + }); + + rerender({ rerenderTrigger: 2 }); + + expect(result.current.selectSessionModal.cardId).toBe('persistent-card'); + }); + + it('should maintain function referential stability', () => { + const { result, rerender } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const initialFunctions = { + updateSelectSessionModal: result.current.updateSelectSessionModal, + closeSelectSessionModal: result.current.closeSelectSessionModal, + }; + + rerender(); + + expect(result.current.updateSelectSessionModal).toBe(initialFunctions.updateSelectSessionModal); + expect(result.current.closeSelectSessionModal).toBe(initialFunctions.closeSelectSessionModal); + }); + + it('should handle multiple providers independently', () => { + const wrapper1 = createWrapper(); + const wrapper2 = createWrapper(); + + const { result: result1 } = renderHook(() => useSelectSessionModal(), { wrapper: wrapper1 }); + const { result: result2 } = renderHook(() => useSelectSessionModal(), { wrapper: wrapper2 }); + + act(() => { + result1.current.updateSelectSessionModal('card1'); + }); + + act(() => { + result2.current.updateSelectSessionModal('card2'); + }); + + expect(result1.current.selectSessionModal.cardId).toBe('card1'); + expect(result2.current.selectSessionModal.cardId).toBe('card2'); + }); + }); + + describe('memoization behavior', () => { + it('should memoize context value when state does not change', () => { + const { result, rerender } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const initialFunctions = { + updateSelectSessionModal: result.current.updateSelectSessionModal, + closeSelectSessionModal: result.current.closeSelectSessionModal, + }; + + rerender(); + + expect(result.current.updateSelectSessionModal).toBe(initialFunctions.updateSelectSessionModal); + expect(result.current.closeSelectSessionModal).toBe(initialFunctions.closeSelectSessionModal); + }); + + it('should update memoized value when selectSessionModal state changes', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const initialCardId = result.current.selectSessionModal.cardId; + + act(() => { + result.current.updateSelectSessionModal('changed-card'); + }); + + expect(result.current.selectSessionModal.cardId).not.toBe(initialCardId); + expect(result.current.selectSessionModal.cardId).toBe('changed-card'); + }); + }); + + describe('integration scenarios', () => { + it('should handle realistic modal workflow', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + expect(result.current.selectSessionModal.cardId).toBeNull(); + + act(() => { + result.current.updateSelectSessionModal('course-card-123'); + }); + expect(result.current.selectSessionModal.cardId).toBe('course-card-123'); + + act(() => { + result.current.closeSelectSessionModal(); + }); + expect(result.current.selectSessionModal.cardId).toBeNull(); + }); + + it('should handle session selection workflow for different courses', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const courseCards = [ + 'course-cs101-card', + 'course-math201-card', + 'course-physics301-card', + ]; + + courseCards.forEach(cardId => { + act(() => { + result.current.updateSelectSessionModal(cardId); + }); + expect(result.current.selectSessionModal.cardId).toBe(cardId); + + act(() => { + result.current.closeSelectSessionModal(); + }); + expect(result.current.selectSessionModal.cardId).toBeNull(); + }); + }); + + it('should handle switching between different cards without closing', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const cardSequence = [ + 'card-A', + 'card-B', + 'card-C', + 'card-D', + ]; + + cardSequence.forEach(cardId => { + act(() => { + result.current.updateSelectSessionModal(cardId); + }); + expect(result.current.selectSessionModal.cardId).toBe(cardId); + }); + + act(() => { + result.current.closeSelectSessionModal(); + }); + expect(result.current.selectSessionModal.cardId).toBeNull(); + }); + + it('should handle entitlement selection modal workflow', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const entitlementCard = 'entitlement-card-uuid-456'; + + act(() => { + result.current.updateSelectSessionModal(entitlementCard); + }); + expect(result.current.selectSessionModal.cardId).toBe(entitlementCard); + + act(() => { + result.current.updateSelectSessionModal(null); + }); + expect(result.current.selectSessionModal.cardId).toBeNull(); + + act(() => { + result.current.updateSelectSessionModal(entitlementCard); + }); + expect(result.current.selectSessionModal.cardId).toBe(entitlementCard); + + act(() => { + result.current.closeSelectSessionModal(); + }); + expect(result.current.selectSessionModal.cardId).toBeNull(); + }); + + it('should handle concurrent modal operations in different contexts', () => { + const wrapper1 = createWrapper(); + const wrapper2 = createWrapper(); + + const { result: result1 } = renderHook(() => useSelectSessionModal(), { wrapper: wrapper1 }); + const { result: result2 } = renderHook(() => useSelectSessionModal(), { wrapper: wrapper2 }); + + act(() => { + result1.current.updateSelectSessionModal('modal-1-card'); + result2.current.updateSelectSessionModal('modal-2-card'); + }); + + expect(result1.current.selectSessionModal.cardId).toBe('modal-1-card'); + expect(result2.current.selectSessionModal.cardId).toBe('modal-2-card'); + + act(() => { + result1.current.closeSelectSessionModal(); + }); + + expect(result1.current.selectSessionModal.cardId).toBeNull(); + expect(result2.current.selectSessionModal.cardId).toBe('modal-2-card'); + }); + }); + + describe('type safety and edge cases', () => { + it('should handle type-safe cardId parameter', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const stringCardId = 'typed-card-123'; + const nullCardId = null; + + act(() => { + result.current.updateSelectSessionModal(stringCardId); + }); + expect(result.current.selectSessionModal.cardId).toBe(stringCardId); + + act(() => { + result.current.updateSelectSessionModal(nullCardId); + }); + expect(result.current.selectSessionModal.cardId).toBe(nullCardId); + }); + + it('should maintain state consistency across multiple updates', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const updates = [ + 'card-1', + null, + 'card-2', + '', + 'card-3', + null, + 'final-card', + ]; + + updates.forEach(update => { + act(() => { + result.current.updateSelectSessionModal(update); + }); + expect(result.current.selectSessionModal.cardId).toBe(update); + }); + }); + + it('should handle whitespace-only cardIds', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const whitespaceCardId = ' '; + + act(() => { + result.current.updateSelectSessionModal(whitespaceCardId); + }); + + expect(result.current.selectSessionModal.cardId).toBe(whitespaceCardId); + }); + + it('should handle newlines and tabs in cardIds', () => { + const { result } = renderHook(() => useSelectSessionModal(), { + wrapper: createWrapper(), + }); + + const specialCharsCardId = 'card\t\nwith\rspecial-chars'; + + act(() => { + result.current.updateSelectSessionModal(specialCharsCardId); + }); + + expect(result.current.selectSessionModal.cardId).toBe(specialCharsCardId); + }); + }); +}); diff --git a/src/data/context/SelectSessionProvider.tsx b/src/data/context/SelectSessionProvider.tsx new file mode 100644 index 000000000..02b47bec7 --- /dev/null +++ b/src/data/context/SelectSessionProvider.tsx @@ -0,0 +1,85 @@ +import React, { + createContext, useContext, useReducer, useMemo, ReactNode, + useCallback, +} from 'react'; + +interface SelectSessionModalState { + cardId: string | null, +} + +interface SelectSessionModalContextType { + selectSessionModal: SelectSessionModalState, + updateSelectSessionModal: (cardId: string | null) => void, + closeSelectSessionModal: () => void, +} + +const SelectSessionModalContext = createContext(null); + +interface State { + selectSessionModal: SelectSessionModalState, +} + +type Action = + | { type: 'UPDATE_SELECT_SESSION_MODAL', payload: string | null } + | { type: 'CLOSE_SELECT_SESSION_MODAL' }; + +const initialState: State = { + selectSessionModal: { cardId: null }, +}; + +const selectSessionModalReducer = (state: State, action: Action): State => { + switch (action.type) { + case 'UPDATE_SELECT_SESSION_MODAL': + return { + ...state, + selectSessionModal: { cardId: action.payload }, + }; + case 'CLOSE_SELECT_SESSION_MODAL': + return { + ...state, + selectSessionModal: { cardId: null }, + }; + /* istanbul ignore next */ + default: + return state; + } +}; + +interface SelectSessionModalProviderProps { + children: ReactNode, +} + +export const SelectSessionModalProvider: React.FC = ({ children }) => { + const [state, dispatch] = useReducer(selectSessionModalReducer, initialState); + + const updateSelectSessionModal = useCallback((cardId: string | null) => { + dispatch({ type: 'UPDATE_SELECT_SESSION_MODAL', payload: cardId }); + }, []); + + const closeSelectSessionModal = useCallback(() => { + dispatch({ type: 'CLOSE_SELECT_SESSION_MODAL' }); + }, []); + + const contextValue = useMemo( + () => ({ + selectSessionModal: state.selectSessionModal, + updateSelectSessionModal, + closeSelectSessionModal, + }), + [closeSelectSessionModal, state.selectSessionModal, updateSelectSessionModal], + ); + + return ( + + {children} + + ); +}; + +export const useSelectSessionModal = (): SelectSessionModalContextType => { + const context = useContext(SelectSessionModalContext); + if (!context) { + throw new Error('useSelectSessionModal must be used within a SelectSessionModalProvider'); + } + return context; +}; diff --git a/src/data/context/index.test.tsx b/src/data/context/index.test.tsx new file mode 100644 index 000000000..b869d37de --- /dev/null +++ b/src/data/context/index.test.tsx @@ -0,0 +1,53 @@ +import { render, screen } from '@testing-library/react'; +import ContextProviders from './index'; +import { useFilters } from './FiltersProvider'; +import { useSelectSessionModal } from './SelectSessionProvider'; + +const TestComponent = () => { + const filters = useFilters(); + const selectSessionModal = useSelectSessionModal(); + + return ( +
+
{filters ? 'Filters Available' : 'Filters Not Available'}
+
{selectSessionModal ? 'SelectSession Available' : 'SelectSession Not Available'}
+
+ ); +}; + +describe('ContextProviders', () => { + it('should render children', () => { + render( + +
Test Child
+
, + ); + + expect(screen.getByText('Test Child')).toBeInTheDocument(); + }); + + it('should provide all context providers to children', () => { + render( + + + , + ); + + expect(screen.getByText('Filters Available')).toBeInTheDocument(); + expect(screen.getByText('SelectSession Available')).toBeInTheDocument(); + }); + + it('should render multiple children', () => { + render( + +
First Child
+
Second Child
+
Third Child
+
, + ); + + expect(screen.getByText('First Child')).toBeInTheDocument(); + expect(screen.getByText('Second Child')).toBeInTheDocument(); + expect(screen.getByText('Third Child')).toBeInTheDocument(); + }); +}); diff --git a/src/data/context/index.tsx b/src/data/context/index.tsx new file mode 100644 index 000000000..cd5159b37 --- /dev/null +++ b/src/data/context/index.tsx @@ -0,0 +1,21 @@ +import type { ReactNode } from 'react'; +import { MasqueradeProvider, useMasquerade } from './MasqueradeProvider'; +import { FiltersProvider, useFilters } from './FiltersProvider'; +import { SelectSessionModalProvider, useSelectSessionModal } from './SelectSessionProvider'; + +interface ContextProvidersProps { + children: ReactNode, +} + +const ContextProviders = ({ children }: ContextProvidersProps) => ( + + + {children} + + +); + +export { + MasqueradeProvider, useMasquerade, useFilters, useSelectSessionModal, +}; +export default ContextProviders; diff --git a/src/data/contexts/GlobalDataContext.tsx b/src/data/contexts/GlobalDataContext.tsx new file mode 100644 index 000000000..6ea6c5926 --- /dev/null +++ b/src/data/contexts/GlobalDataContext.tsx @@ -0,0 +1,31 @@ +import { createContext, Dispatch, SetStateAction } from 'react'; + +interface EmailConfirmation { + isNeeded: boolean, + sendEmailUrl: string, +} + +interface PlatformSettings { + courseSearchUrl: string, +} + +interface GlobalDataContextType { + emailConfirmation: EmailConfirmation, + platformSettings: PlatformSettings, + setEmailConfirmation: Dispatch> | null, + setPlatformSettings: Dispatch> | null, +} + +const GlobalDataContext = createContext({ + emailConfirmation: { + isNeeded: false, + sendEmailUrl: '', + }, + platformSettings: { + courseSearchUrl: '', + }, + setEmailConfirmation: null, + setPlatformSettings: null, +}); + +export default GlobalDataContext; diff --git a/src/data/contexts/GlobalDataProvider.jsx b/src/data/contexts/GlobalDataProvider.jsx new file mode 100644 index 000000000..657985ab7 --- /dev/null +++ b/src/data/contexts/GlobalDataProvider.jsx @@ -0,0 +1,23 @@ +import { useState, useMemo } from 'react'; + +import GlobalDataContext from './GlobalDataContext'; + +export default function GlobalDataProvider({ children }) { + const [emailConfirmation, setEmailConfirmation] = useState({ isNeeded: false, sendEmailUrl: '' }); + const [platformSettings, setPlatformSettings] = useState({ courseSearchUrl: '' }); + + const value = useMemo(() => ({ + emailConfirmation, + platformSettings, + setEmailConfirmation, + setPlatformSettings, + }), [emailConfirmation, platformSettings]); + + return ( + + {children} + + ); +} + + diff --git a/src/data/hooks/index.ts b/src/data/hooks/index.ts new file mode 100644 index 000000000..f9e06ec45 --- /dev/null +++ b/src/data/hooks/index.ts @@ -0,0 +1,19 @@ +import { useInitializeLearnerHome } from './queryHooks'; +import { + useUnenrollFromCourse, + useUpdateEntitlementEnrollment, + useDeleteEntitlementEnrollment, + useUpdateEmailSettings, + useCreateCreditRequest, + useSendConfirmEmail, +} from './mutationHooks'; + +export { + useInitializeLearnerHome, + useUnenrollFromCourse, + useUpdateEntitlementEnrollment, + useDeleteEntitlementEnrollment, + useUpdateEmailSettings, + useCreateCreditRequest, + useSendConfirmEmail, +}; diff --git a/src/data/hooks/mutationHooks.test.tsx b/src/data/hooks/mutationHooks.test.tsx new file mode 100644 index 000000000..ba18ffd99 --- /dev/null +++ b/src/data/hooks/mutationHooks.test.tsx @@ -0,0 +1,346 @@ +import { renderHook } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { logError } from '@openedx/frontend-base'; +import { + useUnenrollFromCourse, + useUpdateEntitlementEnrollment, + useDeleteEntitlementEnrollment, + useUpdateEmailSettings, + useCreateCreditRequest, + useSendConfirmEmail, +} from './mutationHooks'; +import * as api from '../services/lms/api'; + +// Mock external dependencies +jest.mock('@openedx/frontend-base', () => ({ + ...jest.requireActual('@openedx/frontend-base'), + logError: jest.fn(), +})); +jest.mock('@src/data/context'); +jest.mock('@src/data/services/lms/api'); + +const mockLogError = logError as jest.MockedFunction; + +// Create a test wrapper with QueryClient +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + gcTime: 0, + }, + mutations: { + retry: false, + }, + }, + }); + + return function Wrapper({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); + }; +}; + +describe('mutationHooks', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('useUnenrollFromCourse', () => { + it('should unenroll successfully and invalidate queries', async () => { + const queryClient = new QueryClient(); + const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries'); + (api.unenrollFromCourse as jest.Mock).mockResolvedValue({}); + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + + ); + + const { result } = renderHook(() => useUnenrollFromCourse(), { wrapper }); + + await result.current.mutateAsync({ courseId: 'test-course-id' }); + + expect(api.unenrollFromCourse).toHaveBeenCalledWith({ courseId: 'test-course-id' }); + expect(invalidateQueriesSpy).toHaveBeenCalledWith({ + queryKey: expect.arrayContaining(['learner-dashboard', 'initialize']), + }); + }); + + it('should log error when unenroll fails', async () => { + const error = new Error('Network error'); + (api.unenrollFromCourse as jest.Mock).mockRejectedValue(error); + + const { result } = renderHook(() => useUnenrollFromCourse(), { + wrapper: createWrapper(), + }); + + await expect(result.current.mutateAsync({ courseId: 'test-course-id' })) + .rejects.toThrow('Network error'); + + expect(mockLogError).toHaveBeenCalledWith( + 'Failed to unenroll from course test-course-id:', + error, + ); + }); + }); + + describe('useUpdateEntitlementEnrollment', () => { + it('should update entitlement enrollment successfully', async () => { + const queryClient = new QueryClient(); + const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries'); + (api.updateEntitlementEnrollment as jest.Mock).mockResolvedValue({}); + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + + ); + + const { result } = renderHook(() => useUpdateEntitlementEnrollment(), { wrapper }); + + await result.current.mutateAsync({ uuid: 'test-uuid', courseId: 'test-course' }); + + expect(api.updateEntitlementEnrollment).toHaveBeenCalledWith({ + uuid: 'test-uuid', + courseId: 'test-course', + }); + expect(invalidateQueriesSpy).toHaveBeenCalled(); + }); + + it('should log error when update fails', async () => { + const error = new Error('Update failed'); + (api.updateEntitlementEnrollment as jest.Mock).mockRejectedValue(error); + + const { result } = renderHook(() => useUpdateEntitlementEnrollment(), { + wrapper: createWrapper(), + }); + + await expect(result.current.mutateAsync({ uuid: 'test-uuid', courseId: 'test-course' })) + .rejects.toThrow('Update failed'); + + expect(mockLogError).toHaveBeenCalledWith( + 'Failed to update entitlement enrollment for UUID test-uuid:', + error, + ); + }); + }); + + describe('useDeleteEntitlementEnrollment', () => { + it('should delete entitlement enrollment successfully', async () => { + const queryClient = new QueryClient(); + const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries'); + (api.deleteEntitlementEnrollment as jest.Mock).mockResolvedValue({}); + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + + ); + + const { result } = renderHook(() => useDeleteEntitlementEnrollment(), { wrapper }); + + await result.current.mutateAsync({ uuid: 'test-uuid', isRefundable: true }); + + expect(api.deleteEntitlementEnrollment).toHaveBeenCalledWith({ + uuid: 'test-uuid', + isRefundable: true, + }); + expect(invalidateQueriesSpy).toHaveBeenCalled(); + }); + + it('should log error when deletion fails', async () => { + const error = new Error('Deletion failed'); + (api.deleteEntitlementEnrollment as jest.Mock).mockRejectedValue(error); + + const { result } = renderHook(() => useDeleteEntitlementEnrollment(), { + wrapper: createWrapper(), + }); + + await expect(result.current.mutateAsync({ uuid: 'test-uuid', isRefundable: false })) + .rejects.toThrow('Deletion failed'); + + expect(mockLogError).toHaveBeenCalledWith( + 'Failed to delete entitlement enrollment for UUID test-uuid:', + error, + ); + }); + }); + + describe('useUpdateEmailSettings', () => { + it('should update email settings successfully', async () => { + const queryClient = new QueryClient(); + const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries'); + (api.updateEmailSettings as jest.Mock).mockResolvedValue({}); + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + + ); + + const { result } = renderHook(() => useUpdateEmailSettings(), { wrapper }); + + await result.current.mutateAsync({ courseId: 'test-course', enable: true }); + + expect(api.updateEmailSettings).toHaveBeenCalledWith({ + courseId: 'test-course', + enable: true, + }); + expect(invalidateQueriesSpy).toHaveBeenCalled(); + }); + + it('should log error when email settings update fails', async () => { + const error = new Error('Email settings update failed'); + (api.updateEmailSettings as jest.Mock).mockRejectedValue(error); + + const { result } = renderHook(() => useUpdateEmailSettings(), { + wrapper: createWrapper(), + }); + + await expect(result.current.mutateAsync({ courseId: 'test-course', enable: false })) + .rejects.toThrow('Email settings update failed'); + + expect(mockLogError).toHaveBeenCalledWith( + 'Failed to update email settings for course test-course:', + error, + ); + }); + }); + + describe('useCreateCreditRequest', () => { + it('should create credit request successfully', async () => { + const queryClient = new QueryClient(); + const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries'); + (api.createCreditRequest as jest.Mock).mockResolvedValue({}); + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + + ); + + const { result } = renderHook(() => useCreateCreditRequest(), { wrapper }); + + const creditParams = { + providerId: 'test-provider', + courseId: 'test-course', + username: 'test-user', + }; + + await result.current.mutateAsync(creditParams); + + expect(api.createCreditRequest).toHaveBeenCalledWith(creditParams); + expect(invalidateQueriesSpy).toHaveBeenCalled(); + }); + + it('should log error when credit request creation fails', async () => { + const error = new Error('Credit request failed'); + (api.createCreditRequest as jest.Mock).mockRejectedValue(error); + + const { result } = renderHook(() => useCreateCreditRequest(), { + wrapper: createWrapper(), + }); + + const creditParams = { + providerId: 'test-provider', + courseId: 'test-course', + username: 'test-user', + }; + + await expect(result.current.mutateAsync(creditParams)) + .rejects.toThrow('Credit request failed'); + + expect(mockLogError).toHaveBeenCalledWith( + 'Failed to create credit request for course test-course with provider test-provider:', + error, + ); + }); + }); + + describe('useSendConfirmEmail', () => { + it('should send confirmation email successfully', async () => { + const queryClient = new QueryClient(); + const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries'); + (api.sendConfirmEmail as jest.Mock).mockResolvedValue({}); + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + + ); + + const sendEmailUrl = 'https://example.com/send-email'; + const { result } = renderHook(() => useSendConfirmEmail(sendEmailUrl), { wrapper }); + + await result.current.mutateAsync(); + + expect(api.sendConfirmEmail).toHaveBeenCalledWith(sendEmailUrl); + expect(invalidateQueriesSpy).toHaveBeenCalled(); + }); + + it('should log error when sending confirmation email fails', async () => { + const error = new Error('Email sending failed'); + (api.sendConfirmEmail as jest.Mock).mockRejectedValue(error); + + const sendEmailUrl = 'https://example.com/send-email'; + const { result } = renderHook(() => useSendConfirmEmail(sendEmailUrl), { + wrapper: createWrapper(), + }); + + await expect(result.current.mutateAsync()) + .rejects.toThrow('Email sending failed'); + + expect(mockLogError).toHaveBeenCalledWith( + 'Failed to send confirmation email:', + error, + ); + }); + }); + + describe('Query invalidation behavior', () => { + it('should invalidate correct queries for all mutation hooks', async () => { + const queryClient = new QueryClient(); + const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries'); + + const wrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + + ); + + // Mock successful API responses + (api.unenrollFromCourse as jest.Mock).mockResolvedValue({}); + (api.updateEntitlementEnrollment as jest.Mock).mockResolvedValue({}); + (api.updateEmailSettings as jest.Mock).mockResolvedValue({}); + (api.createCreditRequest as jest.Mock).mockResolvedValue({}); + (api.sendConfirmEmail as jest.Mock).mockResolvedValue({}); + + // Test each hook that should invalidate queries + const hooks = [ + { hook: useUnenrollFromCourse, params: { courseId: 'test' } }, + { hook: useUpdateEntitlementEnrollment, params: { uuid: 'test', courseId: 'test' } }, + { hook: useUpdateEmailSettings, params: { courseId: 'test', enable: true } }, + { hook: useCreateCreditRequest, params: { providerId: 'test', courseId: 'test', username: 'test' } }, + { hook: useSendConfirmEmail, params: undefined, arg: 'https://test.com' }, + ]; + + for (const { hook, params, arg } of hooks) { + invalidateQueriesSpy.mockClear(); + // @ts-expect-error handle varying params + const { result } = renderHook(() => (arg ? hook(arg) : hook()), { wrapper }); + + // @ts-expect-error handle varying mutateAsync params + await result.current.mutateAsync(params); + + expect(invalidateQueriesSpy).toHaveBeenCalledWith({ + queryKey: expect.arrayContaining(['learner-dashboard', 'initialize']), + }); + } + }); + }); +}); diff --git a/src/data/hooks/mutationHooks.ts b/src/data/hooks/mutationHooks.ts new file mode 100644 index 000000000..c09395e41 --- /dev/null +++ b/src/data/hooks/mutationHooks.ts @@ -0,0 +1,131 @@ +import { useQueryClient, useMutation } from '@tanstack/react-query'; +import { logError } from '@openedx/frontend-base'; +import { + createCreditRequest, + deleteEntitlementEnrollment, + sendConfirmEmail, + unenrollFromCourse, + updateEmailSettings, + updateEntitlementEnrollment, +} from '@src/data/services/lms/api'; +import { learnerDashboardQueryKeys, learnerDashboardMutationKeys } from './queryKeys'; + +interface UpdateEntitlementProps { + uuid: string, + courseId: string, +} + +interface DeleteEntitlementParams { + uuid: string, + isRefundable: boolean, +} + +interface UpdateEmailSettingsParams { + courseId: string, + enable: boolean, +} + +interface CreditParams { + providerId: string, + courseId: string, + username: string, +} + +const useUnenrollFromCourse = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: learnerDashboardMutationKeys.unenrollFromCourse(), + mutationFn: ({ courseId }: { courseId: string }) => unenrollFromCourse({ courseId }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: learnerDashboardQueryKeys.initializeBase() }); + }, + onError: (error, variables) => { + logError(`Failed to unenroll from course ${variables.courseId}:`, error); + }, + }); +}; + +const useUpdateEntitlementEnrollment = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: learnerDashboardMutationKeys.updateEntitlementEnrollment(), + mutationFn: ({ uuid, courseId }: UpdateEntitlementProps) => updateEntitlementEnrollment({ uuid, courseId }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: learnerDashboardQueryKeys.initializeBase() }); + }, + onError: (error, variables) => { + logError(`Failed to update entitlement enrollment for UUID ${variables.uuid}:`, error); + }, + }); +}; + +const useDeleteEntitlementEnrollment = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: learnerDashboardMutationKeys.deleteEntitlementEnrollment(), + mutationFn: (params: DeleteEntitlementParams) => deleteEntitlementEnrollment(params), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: learnerDashboardQueryKeys.initializeBase() }); + }, + onError: (error, variables) => { + logError(`Failed to delete entitlement enrollment for UUID ${variables.uuid}:`, error); + }, + }); +}; + +const useUpdateEmailSettings = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: learnerDashboardMutationKeys.updateEmailSettings(), + mutationFn: ({ courseId, enable }: UpdateEmailSettingsParams) => updateEmailSettings({ courseId, enable }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: learnerDashboardQueryKeys.initializeBase() }); + }, + onError: (error, variables) => { + logError(`Failed to update email settings for course ${variables.courseId}:`, error); + }, + }); +}; + +const useCreateCreditRequest = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: learnerDashboardMutationKeys.createCreditRequest(), + mutationFn: (props: CreditParams) => createCreditRequest(props), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: learnerDashboardQueryKeys.initializeBase() }); + }, + onError: (error, variables) => { + logError(`Failed to create credit request for course ${variables.courseId} with provider ${variables.providerId}:`, error); + }, + }); +}; + +const useSendConfirmEmail = (sendEmailUrl: string) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: learnerDashboardMutationKeys.sendConfirmEmail(sendEmailUrl), + mutationFn: () => sendConfirmEmail(sendEmailUrl), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: learnerDashboardQueryKeys.initializeBase() }); + }, + onError: (error) => { + logError('Failed to send confirmation email:', error); + }, + }); +}; + +export { + useUnenrollFromCourse, + useUpdateEntitlementEnrollment, + useDeleteEntitlementEnrollment, + useUpdateEmailSettings, + useCreateCreditRequest, + useSendConfirmEmail, +}; diff --git a/src/data/hooks/queryHooks.test.tsx b/src/data/hooks/queryHooks.test.tsx new file mode 100644 index 000000000..3c1528de5 --- /dev/null +++ b/src/data/hooks/queryHooks.test.tsx @@ -0,0 +1,222 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { useMasquerade } from '@src/data/context'; +import { + useInitializeLearnerHome, +} from './index'; +import { learnerDashboardQueryKeys } from './queryKeys'; +import * as api from '../services/lms/api'; + +// Mock external dependencies +jest.mock('@openedx/frontend-base', () => ({ + ...jest.requireActual('@openedx/frontend-base'), + logError: jest.fn(), +})); +jest.mock('@src/data/context'); +jest.mock('@src/data/services/lms/api'); +jest.mock('@src/utils/dataTransformers', () => ({ + getTransformedCourseDataObject: jest.fn((courses) => { + const result = {}; + (courses || []).forEach((c, i) => { + result[`card-${i}`] = { ...c, cardId: `card-${i}` }; + }); + return result; + }), +})); +jest.mock('@src/data/contexts/GlobalDataContext', () => { + const { createContext } = jest.requireActual('react'); + return { + __esModule: true, + default: createContext({ + setEmailConfirmation: jest.fn(), + setPlatformSettings: jest.fn(), + }), + }; +}); + +const mockUseMasquerade = useMasquerade as jest.MockedFunction; + +// Create a test wrapper with QueryClient +const createWrapper = (queryClient?: QueryClient) => { + const client = queryClient || new QueryClient({ + defaultOptions: { + queries: { + retry: false, + retryDelay: 0, + gcTime: 0, + }, + mutations: { + retry: false, + }, + }, + }); + + return function Wrapper({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); + }; +}; + +describe('queryHooks', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('useInitializeLearnerHome', () => { + const mockQueryData = { courses: ['query-course'], user: 'query-user' }; + const mockNormalUserData = { courses: ['normal-course'], user: 'normal-user', coursesByCardId: {} }; + + it('should fetch and return data with coursesByCardId for normal user', async () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: undefined, + setMasqueradeUser(): void { + throw new Error('Function not implemented.'); + }, + }); + const mockApiData = { + courses: [{ id: 'course-1' }, { id: 'course-2' }], + emailConfirmation: { isNeeded: false }, + platformSettings: { supportEmail: 'test@example.com' }, + }; + (api.initializeList as jest.Mock).mockResolvedValue(mockApiData); + + const { result } = renderHook(() => useInitializeLearnerHome(), { + wrapper: createWrapper(), + }); + + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + }); + + expect(api.initializeList).toHaveBeenCalledWith(undefined); + expect(result.current.data).toMatchObject(mockApiData); + expect(result.current.data?.coursesByCardId).toEqual({ + 'card-0': { id: 'course-1', cardId: 'card-0' }, + 'card-1': { id: 'course-2', cardId: 'card-1' }, + }); + }); + + it('should use query data when masquerading and query succeeds', async () => { + const masqueradeUser = 'test-user'; + mockUseMasquerade.mockReturnValue({ + masqueradeUser, + setMasqueradeUser(): void { + throw new Error('Function not implemented.'); + }, + }); + (api.initializeList as jest.Mock).mockResolvedValue(mockQueryData); + + const { result } = renderHook(() => useInitializeLearnerHome(), { + wrapper: createWrapper(), + }); + + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + }); + + expect(api.initializeList).toHaveBeenCalledWith(masqueradeUser); + expect(result.current.data).toMatchObject(mockQueryData); + expect(result.current.data).toHaveProperty('coursesByCardId'); + }); + + it('should fall back to cached normal-user data when masquerading fails', async () => { + const masqueradeUser = 'test-user'; + mockUseMasquerade.mockReturnValue({ + masqueradeUser, + setMasqueradeUser(): void { + throw new Error('Function not implemented.'); + }, + }); + const error: any = new Error('API Error'); + error.response = { status: 403 }; + (api.initializeList as jest.Mock).mockRejectedValue(error); + + // Don't use gcTime: 0 here — we need the seeded cache entry to persist + // for the fallback lookup via queryClient.getQueryData() + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false, retryDelay: 0 }, mutations: { retry: false } }, + }); + queryClient.setQueryData( + learnerDashboardQueryKeys.initialize(undefined), + mockNormalUserData, + ); + + const { result } = renderHook(() => useInitializeLearnerHome(), { + wrapper: createWrapper(queryClient), + }); + + await waitFor(() => { + expect(result.current.isError).toBe(true); + }); + + expect(api.initializeList).toHaveBeenCalledWith(masqueradeUser); + expect(result.current.data).toEqual(mockNormalUserData); + }); + + it('should not retry on 4xx errors', async () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: undefined, + setMasqueradeUser(): void { + throw new Error('Function not implemented.'); + }, + }); + const error: any = new Error('Forbidden'); + error.response = { status: 403 }; + (api.initializeList as jest.Mock).mockRejectedValue(error); + + const { result } = renderHook(() => useInitializeLearnerHome(), { + wrapper: createWrapper(), + }); + + await waitFor(() => { + expect(result.current.isError).toBe(true); + }); + + // 4xx errors should not be retried — only 1 call + expect(api.initializeList).toHaveBeenCalledTimes(1); + }); + + it('should retry on 5xx errors up to 3 times', async () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: undefined, + setMasqueradeUser(): void { + throw new Error('Function not implemented.'); + }, + }); + const error: any = new Error('Server Error'); + error.response = { status: 500 }; + (api.initializeList as jest.Mock).mockRejectedValue(error); + + const { result } = renderHook(() => useInitializeLearnerHome(), { + wrapper: createWrapper(), + }); + + await waitFor(() => { + expect(result.current.isError).toBe(true); + }); + + // 1 initial + 3 retries = 4 total calls + expect(api.initializeList).toHaveBeenCalledTimes(4); + }); + + it('should have correct query configuration for masquerading', async () => { + const masqueradeUser = 'test-user'; + mockUseMasquerade.mockReturnValue({ + masqueradeUser, + setMasqueradeUser(): void { + throw new Error('Function not implemented.'); + }, + }); + (api.initializeList as jest.Mock).mockResolvedValue(mockQueryData); + + const { result } = renderHook(() => useInitializeLearnerHome(), { + wrapper: createWrapper(), + }); + + // For masquerading, retryOnMount and refetchOnMount should be false + expect(result.current.isRefetchError).toBe(false); + }); + }); +}); diff --git a/src/data/hooks/queryHooks.ts b/src/data/hooks/queryHooks.ts new file mode 100644 index 000000000..ab0ee4e82 --- /dev/null +++ b/src/data/hooks/queryHooks.ts @@ -0,0 +1,58 @@ +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useContext, useEffect } from 'react'; +import { useMasquerade } from '@src/data/context'; +import GlobalDataContext from '@src/data/contexts/GlobalDataContext'; +import { + initializeList, +} from '@src/data/services/lms/api'; +import { getTransformedCourseDataObject } from '@src/utils/dataTransformers'; +import { learnerDashboardQueryKeys } from './queryKeys'; + +const useInitializeLearnerHome = () => { + const { masqueradeUser } = useMasquerade(); + const queryClient = useQueryClient(); + const { setEmailConfirmation, setPlatformSettings } = useContext(GlobalDataContext); + + const query = useQuery({ + queryKey: learnerDashboardQueryKeys.initialize(masqueradeUser), + queryFn: async () => { + const data = await initializeList(masqueradeUser); + return { + ...data, + coursesByCardId: getTransformedCourseDataObject(data?.courses || []), + }; + }, + staleTime: 5 * 60 * 1000, // 5 minutes — dashboard data rarely changes while viewing + retry: (failureCount, error: any) => { + // Don't retry client errors (4xx) — they won't resolve on retry + if (error?.response?.status >= 400 && error?.response?.status < 500) return false; + return failureCount < 3; + }, + retryOnMount: !masqueradeUser, + refetchOnMount: !masqueradeUser, + }); + + // Populate shell-level GlobalDataProvider so header widgets can access this data + useEffect(() => { + if (query.data && !masqueradeUser) { + if (query.data.emailConfirmation && setEmailConfirmation) { + setEmailConfirmation(query.data.emailConfirmation); + } + if (query.data.platformSettings && setPlatformSettings) { + setPlatformSettings(query.data.platformSettings); + } + } + }, [masqueradeUser, query.data, setEmailConfirmation, setPlatformSettings]); + + // When masquerading fails, fall back to the normal user's cached data + let { data } = query; + if (masqueradeUser && query.isError) { + data = queryClient.getQueryData(learnerDashboardQueryKeys.initialize(undefined)); + } + + return { ...query, data }; +}; + +export { + useInitializeLearnerHome, +}; diff --git a/src/data/hooks/queryKeys.ts b/src/data/hooks/queryKeys.ts new file mode 100644 index 000000000..e1ebe62dd --- /dev/null +++ b/src/data/hooks/queryKeys.ts @@ -0,0 +1,16 @@ +const BASE_KEY = ['learner-dashboard'] as const; + +export const learnerDashboardQueryKeys = { + all: BASE_KEY, + initializeBase: () => [...BASE_KEY, 'initialize'] as const, + initialize: (masqueradedUser?: string | null) => [...BASE_KEY, 'initialize', masqueradedUser] as const, +}; + +export const learnerDashboardMutationKeys = { + unenrollFromCourse: () => [...BASE_KEY, 'unenrollFromCourse'] as const, + updateEntitlementEnrollment: () => [...BASE_KEY, 'updateEntitlementEnrollment'] as const, + deleteEntitlementEnrollment: () => [...BASE_KEY, 'deleteEntitlementEnrollment'] as const, + updateEmailSettings: () => [...BASE_KEY, 'updateEmailSettings'] as const, + createCreditRequest: () => [...BASE_KEY, 'createCreditRequest'] as const, + sendConfirmEmail: (sendEmailUrl: string) => [...BASE_KEY, 'sendConfirmEmail', sendEmailUrl] as const, +}; diff --git a/src/data/redux/app/index.js b/src/data/redux/app/index.js deleted file mode 100644 index 8abd5f91d..000000000 --- a/src/data/redux/app/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { actions, reducer } from './reducer'; -export { default as selectors } from './selectors'; diff --git a/src/data/redux/app/reducer.js b/src/data/redux/app/reducer.js deleted file mode 100644 index a3f94e64a..000000000 --- a/src/data/redux/app/reducer.js +++ /dev/null @@ -1,81 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; - -import { StrictDict } from 'utils'; - -const initialState = { - pageNumber: 1, - courseData: {}, - entitlement: [], - emailConfirmation: {}, - enterpriseDashboard: {}, - platformSettings: {}, - suggestedCourses: [], - selectSessionModal: {}, - filters: [], -}; - -export const cardId = (val) => `card-${val}`; - -export const today = Date.now(); - -/** - * Creates a redux slice with actions to load dashboard data and manage visual layout - */ -const app = createSlice({ - name: 'app', - initialState, - reducers: { - loadCourses: (state, { payload: { courses } }) => ({ - ...state, - courseData: courses.reduce( - (obj, curr, index) => { - const out = { ...curr, cardId: cardId(index) }; - if (out.enrollment.lastEnrolled === null) { - out.enrollment.lastEnrolled = today; - } - return { ...obj, [cardId(index)]: out }; - }, - {}, - ), - }), - loadGlobalData: (state, { payload }) => ({ - ...state, - emailConfirmation: payload.emailConfirmation, - enterpriseDashboard: payload.enterpriseDashboard, - platformSettings: payload.platformSettings, - suggestedCourses: payload.suggestedCourses, - socialShareSettings: payload.socialShareSettings, - }), - updateSelectSessionModal: (state, { payload }) => ({ - ...state, - selectSessionModal: { cardId: payload }, - }), - setPageNumber: (state, { payload }) => ({ ...state, pageNumber: payload }), - setFilters: (state, { payload }) => ({ - ...state, - filters: payload, - }), - addFilter: (state, { payload }) => ({ - ...state, - filters: [...state.filters, payload], - }), - removeFilter: (state, { payload }) => ({ - ...state, - filters: state.filters.filter(item => item !== payload), - }), - clearFilters: (state) => ({ - ...state, - filters: [], - }), - }, -}); - -const actions = StrictDict(app.actions); - -const { reducer } = app; - -export { - actions, - initialState, - reducer, -}; diff --git a/src/data/redux/app/reducer.test.js b/src/data/redux/app/reducer.test.js deleted file mode 100644 index 5d542223d..000000000 --- a/src/data/redux/app/reducer.test.js +++ /dev/null @@ -1,124 +0,0 @@ -import { - cardId, - initialState, - reducer, - actions, - today, -} from './reducer'; - -describe('app reducer', () => { - describe('reducers', () => { - beforeEach(() => { - jest.useFakeTimers(); - }); - it('returns initial state', () => { - expect(reducer(undefined, {})).toEqual(initialState); - }); - const initialFilter = 'initial filter'; - const testState = { - ...initialState, - enrollments: [], - courseData: { - }, - entitlement: [], - filters: [initialFilter], - }; - describe('action handlers', () => { - describe('loadCourses', () => { - const courseIds = [ - 'course-1', - 'course-2', - 'course-3', - ]; - const entitlementIds = [ - 'entitlement-course-1', - 'entitlement-course-2', - ]; - const enrollmentData = [ - { - courseRun: { cardId: courseIds[0] }, - course: 1, - some: 'data', - enrollment: { lastEnrolled: 'test-last-enrolled' }, - }, - { - courseRun: { cardId: courseIds[1] }, - course: 2, - some: 'other data', - enrollment: { lastEnrolled: 'test-last-enrolled' }, - }, - { - courseRun: { cardId: courseIds[2] }, - course: 3, - some: 'still different data', - enrollment: { lastEnrolled: 'test-last-enrolled' }, - }, - ]; - const entitlementData = [ - { - courseRun: { cardId: entitlementIds[0] }, - course: 4, - some: 'STILL different data', - enrollment: { lastEnrolled: null }, - }, - { - courseRun: { cardId: entitlementIds[1] }, - course: 5, - some: 'still DIFFERENT data', - enrollment: { lastEnrolled: null }, - }, - ]; - let out; - beforeEach(() => { - out = reducer(testState, actions.loadCourses({ - courses: [...enrollmentData, ...entitlementData], - })); - }); - it('loads object keyed by courseRun ids into courseData field', () => { - expect(out.courseData).toEqual({ - [cardId(0)]: { ...enrollmentData[0], cardId: cardId(0) }, - [cardId(1)]: { ...enrollmentData[1], cardId: cardId(1) }, - [cardId(2)]: { - ...enrollmentData[2], - cardId: cardId(2), - }, - [cardId(3)]: { - ...entitlementData[0], - cardId: cardId(3), - enrollment: { lastEnrolled: today }, - }, - [cardId(4)]: { - ...entitlementData[1], - cardId: cardId(4), - enrollment: { lastEnrolled: today }, - }, - }); - }); - }); - describe('filters', () => { - const newFilter = 'new filter'; - let out; - beforeEach(() => { - out = reducer(testState, {}); - }); - it('overwrites the filters object when using setFilters', () => { - expect(out.filters).toEqual([initialFilter]); - out = reducer(testState, actions.setFilters([newFilter])); - expect(out.filters).toEqual([newFilter]); - }); - it('adds a filter when using addFilter', () => { - out = reducer(testState, actions.addFilter(newFilter)); - expect(out.filters).toEqual([initialFilter, newFilter]); - }); - it('removes a filter when using removeFilter', () => { - out = reducer(testState, actions.removeFilter(initialFilter)); - expect(out.filters).toEqual([]); - }); - it('clears the filters when using clearFilters', () => { - out = reducer(testState, actions.clearFilters()); - expect(out.filters).toEqual([]); - }); - }); - }); - }); -}); diff --git a/src/data/redux/app/selectors/appSelectors.js b/src/data/redux/app/selectors/appSelectors.js deleted file mode 100644 index 255c7976a..000000000 --- a/src/data/redux/app/selectors/appSelectors.js +++ /dev/null @@ -1,23 +0,0 @@ -import { createSelector } from 'reselect'; - -import { StrictDict } from 'utils'; - -import simpleSelectors from './simpleSelectors'; -import * as module from './appSelectors'; - -export const numCourses = createSelector( - [simpleSelectors.courseData], - (courseData) => Object.keys(courseData).length, -); -export const hasCourses = createSelector([module.numCourses], (num) => num > 0); - -export const showSelectSessionModal = createSelector( - [simpleSelectors.selectSessionModal], - (data) => data.cardId != null, -); - -export default StrictDict({ - numCourses, - hasCourses, - showSelectSessionModal, -}); diff --git a/src/data/redux/app/selectors/appSelectors.test.js b/src/data/redux/app/selectors/appSelectors.test.js deleted file mode 100644 index 09b528c08..000000000 --- a/src/data/redux/app/selectors/appSelectors.test.js +++ /dev/null @@ -1,28 +0,0 @@ -import simpleSelectors from './simpleSelectors'; -import * as appSelectors from './appSelectors'; - -describe('basic app selectors', () => { - describe('numCourses', () => { - it('returns the number of courses in the courseData object', () => { - const { preSelectors, cb } = appSelectors.numCourses; - expect(preSelectors).toEqual([simpleSelectors.courseData]); - expect(cb({ course1: 'data', course2: 'data', course3: 'data' })).toEqual(3); - }); - }); - describe('hasCourses', () => { - it('returns true iff numCourses is greater than 0', () => { - const { preSelectors, cb } = appSelectors.hasCourses; - expect(preSelectors).toEqual([appSelectors.numCourses]); - expect(cb(3)).toEqual(true); - expect(cb(0)).toEqual(false); - }); - }); - describe('showSelectSessionModal', () => { - it('returns true if the selectSessionModal cardId is not null', () => { - const { preSelectors, cb } = appSelectors.showSelectSessionModal; - expect(preSelectors).toEqual([simpleSelectors.selectSessionModal]); - expect(cb({ cardId: 'test' })).toEqual(true); - expect(cb({ data: 'test' })).toEqual(false); - }); - }); -}); diff --git a/src/data/redux/app/selectors/courseCard.js b/src/data/redux/app/selectors/courseCard.js deleted file mode 100644 index aba3deb0c..000000000 --- a/src/data/redux/app/selectors/courseCard.js +++ /dev/null @@ -1,155 +0,0 @@ -import { StrictDict } from 'utils'; -import { baseAppUrl } from 'data/services/lms/urls'; -import { EXECUTIVE_EDUCATION_COURSE_MODES } from 'data/constants/course'; - -import * as module from './courseCard'; -import * as simpleSelectors from './simpleSelectors'; - -const { cardSimpleSelectors, mkCardSelector } = simpleSelectors; - -const today = new Date(); -const dateSixMonthsFromNow = new Date(); -dateSixMonthsFromNow.setDate(dateSixMonthsFromNow.getDate() + 180); - -export const loadDateVal = (date) => (date ? new Date(date) : null); - -export const courseCard = StrictDict({ - certificate: mkCardSelector( - cardSimpleSelectors.certificate, - (certificate) => (certificate === null ? {} : ({ - availableDate: new Date(certificate.availableDate), - certPreviewUrl: baseAppUrl(certificate.certPreviewUrl), - isDownloadable: certificate.isDownloadable, - isEarnedButUnavailable: certificate.isEarned && new Date(certificate.availableDate) > new Date(), - isRestricted: certificate.isRestricted, - isEarned: certificate.isEarned, - })), - ), - course: mkCardSelector( - cardSimpleSelectors.course, - (course) => ({ - bannerImgSrc: baseAppUrl(course.bannerImgSrc), - courseNumber: course.courseNumber, - courseName: course.courseName, - socialShareUrl: course.socialShareUrl, - }), - ), - courseProvider: mkCardSelector( - cardSimpleSelectors.courseProvider, - (courseProvider) => ({ name: courseProvider?.name }), - ), - courseRun: mkCardSelector( - cardSimpleSelectors.courseRun, - (courseRun) => (courseRun === null ? {} : { - endDate: module.loadDateVal(courseRun.endDate), - startDate: module.loadDateVal(courseRun.startDate), - advertisedStart: courseRun.advertisedStart, - - courseId: courseRun.courseId, - isArchived: courseRun.isArchived, - isStarted: courseRun.isStarted, - - minPassingGrade: Math.floor(courseRun.minPassingGrade * 100), - - homeUrl: courseRun.homeUrl, - marketingUrl: courseRun.marketingUrl, - - progressUrl: baseAppUrl(courseRun.progressUrl), - resumeUrl: baseAppUrl(courseRun.resumeUrl), // resume will route this to learning mfe. - unenrollUrl: baseAppUrl(courseRun.unenrollUrl), - }), - ), - credit: mkCardSelector( - cardSimpleSelectors.credit, - (credit) => { - if (!credit || Object.keys(credit).length === 0) { - return { isEligible: false }; - } - return { - isEligible: true, - providerStatusUrl: credit.providerStatusUrl, - providerName: credit.providerName, - providerId: credit.providerId, - error: credit.error, - purchased: credit.purchased, - requestStatus: credit.requestStatus, - }; - }, - ), - enrollment: mkCardSelector( - cardSimpleSelectors.enrollment, - (enrollment) => { - if (enrollment == null) { - return { isEnrolled: false }; - } - const { isStaff, hasUnmetPrereqs, isTooEarly } = enrollment.coursewareAccess; - return { - coursewareAccess: enrollment.coursewareAccess, - hasAccess: isStaff || !(hasUnmetPrereqs || isTooEarly), - isEnrolled: enrollment.isEnrolled, - lastEnrolled: enrollment.lastEnrolled, - hasStarted: enrollment.hasStarted, - - accessExpirationDate: module.loadDateVal(enrollment.accessExpirationDate), - canUpgrade: enrollment.canUpgrade, - isAudit: enrollment.isAudit, - isAuditAccessExpired: enrollment.isAuditAccessExpired, - isVerified: enrollment.isVerified, - - isEmailEnabled: enrollment.isEmailEnabled, - hasOptedOutOfEmail: enrollment.hasOptedOutOfEmail, - mode: enrollment.mode, - isExecEd2UCourse: EXECUTIVE_EDUCATION_COURSE_MODES.includes(enrollment.mode), - }; - }, - ), - entitlement: mkCardSelector( - cardSimpleSelectors.entitlement, - (entitlement) => { - if (!entitlement || Object.keys(entitlement).length === 0) { - return { isEntitlement: false }; - } - const deadline = new Date(entitlement.changeDeadline); - const deadlinePassed = deadline < today; - const showExpirationWarning = ( - !entitlement.isFulfilled - && !deadlinePassed - && deadline <= dateSixMonthsFromNow - ); - return { - isEntitlement: true, - - availableSessions: entitlement.availableSessions, - changeDeadline: deadline, - isExpired: entitlement.isExpired, - isFulfilled: entitlement.isFulfilled, - uuid: entitlement.uuid, - - hasSessions: entitlement.availableSessions?.length > 0, - canChange: !deadlinePassed, - showExpirationWarning, - }; - }, - ), - gradeData: mkCardSelector( - cardSimpleSelectors.gradeData, - (gradeData) => ({ isPassing: gradeData.isPassing }), - ), - relatedPrograms: mkCardSelector( - cardSimpleSelectors.relatedPrograms, - (relatedPrograms) => ({ - list: relatedPrograms.map(program => ({ - bannerImgSrc: program.bannerImgSrc, - logoImgSrc: program.logoImgSrc, - numberOfCourses: program.numberOfCourses, - programType: program.programType, - programUrl: program.programUrl, - provider: program.provider, - title: program.title, - })), - length: relatedPrograms.length, - }), - ), -}); - -export default courseCard; diff --git a/src/data/redux/app/selectors/courseCard.test.js b/src/data/redux/app/selectors/courseCard.test.js deleted file mode 100644 index 79edc57e1..000000000 --- a/src/data/redux/app/selectors/courseCard.test.js +++ /dev/null @@ -1,398 +0,0 @@ -import { keyStore } from 'utils'; -import { baseAppUrl } from 'data/services/lms/urls'; -import { EXECUTIVE_EDUCATION_COURSE_MODES } from 'data/constants/course'; - -import simpleSelectors from './simpleSelectors'; -import * as module from './courseCard'; - -jest.mock('data/services/lms/urls', () => ({ - baseAppUrl: url => ({ baseAppUrl: url }), -})); - -jest.mock('./simpleSelectors', () => ({ - mkCardSelector: (simpleSelector, selector) => ({ - mkCardSelector: { selector, simpleSelector }, - }), - cardSimpleSelectors: jest.requireActual('./simpleSelectors').cardSimpleSelectors, -})); - -const { courseCard } = module; -const { cardSimpleSelectors } = simpleSelectors; - -const moduleKeys = keyStore(module); - -let testData; -let selector; -let simpleSelector; -let selected; - -const dates = { - today: new Date(), - tomorrow: new Date(), - nextYear: new Date(), - yesterday: new Date(), -}; -dates.tomorrow.setDate(dates.tomorrow.getDate() + 1); -dates.nextYear.setDate(dates.nextYear.getDate() + 365); -dates.yesterday.setDate(dates.yesterday.getDate() - 1); - -/* - * Takes a selector as input and fetches the referenced selector and simpleSelector for - * the selector to be tested, to be stored in global variables. - * Also sets `selected` global variable is loaded with the passed data, which is stored - * as `testData`. - */ -const loadSelector = (sel, data) => { - ({ simpleSelector, selector } = sel.mkCardSelector); - testData = data; - selected = selector(data); -}; - -describe('courseCard selectors module', () => { - describe('loadDateVal helper function', () => { - it('returns passed date value converted to Date', () => { - const testDate = '2000-10-10'; - expect(module.loadDateVal(testDate)).toEqual(new Date(testDate)); - }); - it('returns null if no value is passed', () => { - expect(module.loadDateVal()).toEqual(null); - }); - }); - describe('courseCard selectors', () => { - let dateSpy; - beforeEach(() => { - dateSpy = jest.spyOn(module, moduleKeys.loadDateVal); - dateSpy.mockImplementation(v => new Date(v)); - }); - afterEach(() => { - dateSpy.mockRestore(); - }); - describe('certificate selector', () => { - beforeEach(() => { - loadSelector(courseCard.certificate, { - certPreviewUrl: 'test-cert-preview-url', - isDownloadable: 'test-is-downloadable', - isRestricted: 'test-is-restricted', - isEarned: false, - availableDate: '2020-10-10', - }); - }); - it('returns a card selector based on certificate cardSimpleSelector', () => { - expect(simpleSelector).toEqual(cardSimpleSelectors.certificate); - }); - it('returns {} object if null certificate received', () => { - expect(selector(null)).toEqual({}); - }); - it('passes availableDate, converted to a date', () => { - expect(selected.availableDate).toMatchObject(new Date(testData.availableDate)); - }); - it('passes [isDownloadable, isRestricted]', () => { - expect(selected.isDownloadable).toEqual(testData.isDownloadable); - expect(selected.isRestricted).toEqual(testData.isRestricted); - }); - it('passes certPreviewUrl as app url', () => { - expect(selected.certPreviewUrl).toEqual(baseAppUrl(testData.certPreviewUrl)); - }); - describe('isEarnedButUnavailable', () => { - it('passes true iff certificate is earned but availableDate is in the future', () => { - const testSelector = (data, expected) => { - expect(selector({ ...testData, ...data }).isEarnedButUnavailable).toEqual(expected); - }; - testSelector({ isEarned: true, availableDate: dates.today }, false); - testSelector({ isEarned: true, availableDate: dates.yesterday }, false); - testSelector({ isEarned: true, availableDate: dates.tomorrow }, true); - testSelector({ isEarned: false, availableDate: dates.tomorrow }, false); - }); - }); - }); - describe('course selector', () => { - beforeEach(() => { - loadSelector(courseCard.course, { - bannerImgSrc: 'test-banner-img-src', - courseNumber: 'test-course-number', - courseName: 'test-course-name', - socialShareUrl: 'test-social-share-url', - }); - }); - it('returns a card selector based on course cardSimpleSelector', () => { - expect(simpleSelector).toEqual(cardSimpleSelectors.course); - }); - it('passes bannerImgSrc, converted to a baseAppUrl', () => { - expect(selected.bannerImgSrc).toEqual(baseAppUrl(testData.bannerImgSrc)); - }); - it('passes [courseNumber, courseName, socialShareUrl]', () => { - expect(selected.courseNumber).toEqual(testData.courseNumber); - expect(selected.courseName).toEqual(testData.courseName); - expect(selected.socialShareUrl).toEqual(testData.socialShareUrl); - }); - }); - describe('courseProvider selector', () => { - beforeEach(() => { - loadSelector(courseCard.courseProvider, { name: 'test-provider-name' }); - }); - it('returns a card selector based on courseProvider cardSimpleSelector', () => { - expect(simpleSelector).toEqual(cardSimpleSelectors.courseProvider); - }); - describe('name', () => { - it('passes the provider name if provider is known', () => { - expect(selected.name).toEqual(testData.name); - }); - it('passes undefined if provider is not known', () => { - expect(selector().name).toEqual(undefined); - }); - }); - }); - describe('courseRun selector', () => { - beforeEach(() => { - loadSelector(courseCard.courseRun, { - endDate: '3000-10-20', - startDate: '2000-10-20', - advertisedStart: 'Mid June', - - courseId: 'test-course-id', - isArchived: 'test-is-archived', - isStarted: 'test-is-started', - - minPassingGrade: 0.9354, - - homeUrl: 'test-home-url', - marketingUrl: 'test-marketing-url', - - progressUrl: 'test-progress-url', - resumeUrl: 'test-resume-url', - unenrollUrl: 'test-unenroll-url', - }); - }); - it('returns a card selector based on courseRun cardSimpleSelector', () => { - expect(simpleSelector).toEqual(cardSimpleSelectors.courseRun); - }); - it('returns {} object if null courseRun received', () => { - expect(selector(null)).toEqual({}); - }); - it('passes [endDate, startDate], converted to dates', () => { - expect(selected.endDate).toEqual(new Date(testData.endDate)); - expect(selected.startDate).toEqual(new Date(testData.startDate)); - }); - it('passes advertised start date', () => { - expect(selected.advertisedStart).toEqual(testData.advertisedStart); - }); - it('passes [courseId, isArchived, isStarted]', () => { - expect(selected.courseId).toEqual(testData.courseId); - expect(selected.isArchived).toEqual(testData.isArchived); - expect(selected.isStarted).toEqual(testData.isStarted); - }); - it('passes minPassingGrade floored from float to a percentage value', () => { - expect(selected.minPassingGrade).toEqual(93); - }); - it('passes [homeUrl, marketingUrl]', () => { - expect(selected.homeUrl).toEqual(testData.homeUrl); - expect(selected.marketingUrl).toEqual(testData.marketingUrl); - }); - it('passes [progressUrl, unenrollUrl, resumeUrl], converted to baseAppUrl', () => { - expect(selected.progressUrl).toEqual(baseAppUrl(testData.progressUrl)); - expect(selected.resumeUrl).toEqual(baseAppUrl(testData.resumeUrl)); - expect(selected.unenrollUrl).toEqual(baseAppUrl(testData.unenrollUrl)); - }); - }); - describe('credit selector', () => { - const credit = { - providerStatusUrl: 'test-provider-status-url', - providerName: 'test-provider-name', - providerId: 'test-provider-id', - error: 'test-provider-id', - purchased: 'test-purchased', - requestStatus: 'test-request-status', - }; - it('returns a card selector based on credit cardSimpleSelector', () => { - loadSelector(courseCard.credit, {}); - expect(simpleSelector).toEqual(cardSimpleSelectors.credit); - }); - it('returns { isEligible: false } if empty object received for credit', () => { - loadSelector(courseCard.credit, {}); - expect(selected).toEqual({ isEligible: false }); - }); - describe('credit fields when credit object is passed', () => { - beforeEach(() => { - loadSelector(courseCard.credit, credit); - }); - it('returns isEligible: true', () => { - expect(selected.isEligible).toEqual(true); - }); - it('returns provider status url, name, and id', () => { - expect(selected.providerStatusUrl).toEqual(credit.providerStatusUrl); - expect(selected.providerName).toEqual(credit.providerName); - expect(selected.providerId).toEqual(credit.providerId); - }); - it('returns error, purchased and requestStatus fields', () => { - expect(selected.error).toEqual(credit.error); - expect(selected.purchased).toEqual(credit.purchased); - expect(selected.requestStatus).toEqual(credit.requestStatus); - }); - }); - }); - describe('enrollment selector', () => { - const defaultData = { - coursewareAccess: { - isStaff: false, - hasUnmetPrereqs: false, - isTooEarly: false, - }, - isEnrolled: 'test-is-enrolled', - lastEnrolled: 'test-last-enrolled', - hasStarted: 'test-has-started', - accessExpirationDate: '3000-10-20', - canUpgrade: 'test-can-upgrade', - isAudit: 'test-is-audit', - isAuditAccessExpired: 'test-is-audit-access-expired', - isVerified: 'test-is-verified', - isEmailEnabled: 'test-is-email-enabled', - mode: 'default', - }; - beforeEach(() => { - loadSelector(courseCard.enrollment, defaultData); - }); - it('returns a card selector based on enrollment cardSimpleSelector', () => { - expect(simpleSelector).toEqual(cardSimpleSelectors.enrollment); - }); - it('returns { isEnrolled: false } object if null enrollment received', () => { - expect(selector(null)).toEqual({ isEnrolled: false }); - }); - it('passes [coursewareAccess, hasStarted, isEnrolled, lastEnrolled]', () => { - expect(selected.coursewareAccess).toEqual(testData.coursewareAccess); - expect(selected.hasStarted).toEqual(testData.hasStarted); - expect(selected.isEnrolled).toEqual(testData.isEnrolled); - expect(selected.lastEnrolled).toEqual(testData.lastEnrolled); - }); - it('passes hasAccess if staff or neither has umet prereqs nor is too early', () => { - const testAccess = (access, expected) => { - expect(selector({ ...testData, coursewareAccess: access }).hasAccess).toEqual(expected); - }; - testAccess({ isStaff: false, hasUnmetPrereqs: false, isTooEarly: false }, true); - testAccess({ isStaff: false, hasUnmetPrereqs: false, isTooEarly: true }, false); - testAccess({ isStaff: false, hasUnmetPrereqs: true, isTooEarly: false }, false); - testAccess({ isStaff: false, hasUnmetPrereqs: true, isTooEarly: true }, false); - testAccess({ isStaff: true, hasUnmetPrereqs: true, isTooEarly: true }, true); - }); - it('passes accessExpirationDate, converted to date', () => { - expect(selected.accessExpirationDate).toEqual(new Date(testData.accessExpirationDate)); - }); - it('passes [canUpgrade, isAudit, isAuditAccessExpired, isVerified]', () => { - expect(selected.canUpgrade).toEqual(testData.canUpgrade); - expect(selected.isAudit).toEqual(testData.isAudit); - expect(selected.isAuditAccessExpired).toEqual(testData.isAuditAccessExpired); - expect(selected.isVerified).toEqual(testData.isVerified); - }); - it('passes isEmailEnabled', () => { - expect(selected.isEmailEnabled).toEqual(testData.isEmailEnabled); - }); - it('returns isExecEd2UCourse: false if mode is not in EXECUTIVE_EDUCATION_COURSE_MODES', () => { - expect(selected.isExecEd2UCourse).toEqual(false); - }); - it('returns isExecEd2UCourse: true if mode is in EXECUTIVE_EDUCATION_COURSE_MODES', () => { - loadSelector(courseCard.enrollment, { ...defaultData, mode: EXECUTIVE_EDUCATION_COURSE_MODES[0] }); - expect(selected.isExecEd2UCourse).toEqual(true); - }); - }); - describe('entitlement selector', () => { - beforeEach(() => { - loadSelector(courseCard.entitlement, { - availableSessions: ['test', 'sessions'], - changeDeadline: '2000-10-20', - isExpired: 'test-is-expired', - isFulfilled: 'test-is-fulfilled', - uuid: 'test-uuid', - }); - }); - it('returns a card selector based on entilement cardSimpleSelector', () => { - expect(simpleSelector).toEqual(cardSimpleSelectors.entitlement); - }); - it('returns { isEntilement: false } if entitlement object is missing or empty', () => { - expect(selector({})).toEqual({ isEntitlement: false }); - expect(selector()).toEqual({ isEntitlement: false }); - }); - it('passes isEntitlement: true for entilement data', () => { - expect(selected.isEntitlement).toEqual(true); - }); - it('passes [availableSessions, isExpired, isFulfilled, uuid]', () => { - expect(selected.availableSessions).toEqual(testData.availableSessions); - expect(selected.isExpired).toEqual(testData.isExpired); - expect(selected.isFulfilled).toEqual(testData.isFulfilled); - expect(selected.uuid).toEqual(testData.uuid); - }); - it('passess changeDeadline as changeDeadline, converted to a date', () => { - expect(selected.changeDeadline).toEqual(new Date(testData.changeDeadline)); - }); - it('passes hasSessions if availableSessions is provided and has a length > 0', () => { - expect(selected.hasSessions).toEqual(true); - expect(selector({ ...testData, availableSessions: [] }).hasSessions).toEqual(false); - }); - it('passes canChange if the deadline is not before current date', () => { - expect(selector({ ...testData, changeDeadline: dates.yesterday }).canChange).toEqual(false); - expect(selector({ ...testData, changeDeadline: dates.tomorrow }).canChange).toEqual(true); - }); - it('passes showExpirationWarning if the deadline is 0-6 months in the future and not fulfilled', () => { - const testSelector = ({ isFulfilled, changeDeadline }, expected) => { - expect( - selector({ ...testData, isFulfilled, changeDeadline }).showExpirationWarning, - ).toEqual(expected); - }; - testSelector({ isFulfilled: false, changeDeadline: dates.yesterday }, false); - testSelector({ isFulfilled: false, changeDeadline: dates.tomorrow }, true); - testSelector({ isFulfilled: false, changeDeadline: dates.nextYear }, false); - testSelector({ isFulfilled: true, changeDeadline: dates.nextYear }, false); - }); - }); - describe('gradeData selector', () => { - beforeEach(() => { - loadSelector(courseCard.gradeData, { isPassing: 'test-is-passing' }); - }); - it('returns a card selector based on gradeData cardSimpleSelector', () => { - expect(simpleSelector).toEqual(cardSimpleSelectors.gradeData); - }); - it('passes isPassing', () => { - expect(selected.isPassing).toEqual(testData.isPassing); - }); - }); - describe('relatedPrograms selector', () => { - beforeEach(() => { - const programData = (index) => ({ - bannerImgSrc: `test-banner-img-src-${index}`, - logoImgSrc: `test-logo-img-src-${index}`, - numberOfCourses: `test-number-of-courses-${index}`, - programType: `test-program-type-${index}`, - programUrl: `test-program-url-${index}`, - provider: `test-provider-${index}`, - title: `test-title-${index}`, - }); - loadSelector(courseCard.relatedPrograms, [ - programData(0), - programData(1), - programData(2), - programData(3), - programData(4), - ]); - }); - it('returns a card selector based on relatedPrograms cardSimpleSelector', () => { - expect(simpleSelector).toEqual(cardSimpleSelectors.relatedPrograms); - }); - it('passes [bannerImgSrc, logoImgSrc] for each program', () => { - selected.list.forEach((row, i) => { - expect(row.bannerImgSrc).toEqual(testData[i].bannerImgSrc); - expect(row.logoImgSrc).toEqual(testData[i].logoImgSrc); - }); - }); - it('passes [numberOfCourses, programType, programUrl, provider, title] for each', () => { - selected.list.forEach((row, i) => { - expect(row.numberOfCourses).toEqual(testData[i].numberOfCourses); - expect(row.programType).toEqual(testData[i].programType); - expect(row.programUrl).toEqual(testData[i].programUrl); - expect(row.provider).toEqual(testData[i].provider); - expect(row.title).toEqual(testData[i].title); - }); - }); - it('passes number of programs a length', () => { - expect(selected.length).toEqual(testData.length); - }); - }); - }); -}); diff --git a/src/data/redux/app/selectors/currentList.js b/src/data/redux/app/selectors/currentList.js deleted file mode 100644 index 9c713eb63..000000000 --- a/src/data/redux/app/selectors/currentList.js +++ /dev/null @@ -1,58 +0,0 @@ -import { StrictDict } from 'utils'; -import { FilterKeys, SortKeys } from 'data/constants/app'; - -import simpleSelectors from './simpleSelectors'; -import * as module from './currentList'; - -export const sortFn = (transform, { reverse }) => (v1, v2) => { - const [a, b] = [v1, v2].map(transform); - if (a === b) { return 0; } - return ((a > b) ? 1 : -1) * (reverse ? -1 : 1); -}; - -export const courseFilters = StrictDict({ - [FilterKeys.notEnrolled]: (course) => !course.enrollment.isEnrolled, - [FilterKeys.done]: (course) => course.courseRun !== null && course.courseRun.isArchived, - [FilterKeys.upgraded]: (course) => course.enrollment.isVerified, - [FilterKeys.inProgress]: (course) => course.enrollment.hasStarted, - [FilterKeys.notStarted]: (course) => !course.enrollment.hasStarted, -}); - -export const transforms = StrictDict({ - [SortKeys.enrolled]: ({ enrollment }) => new Date(enrollment.lastEnrolled), - [SortKeys.title]: ({ course }) => course.courseName.toLowerCase(), -}); - -export const courseFilterFn = filters => (filters.length - ? course => filters.reduce((match, filter) => match && courseFilters[filter](course), true) - : () => true); - -export const currentList = (allCourses, { - sortBy, - filters, -}) => allCourses - .filter(module.courseFilterFn(filters)) - .sort(module.sortFn(transforms[sortBy], { reverse: sortBy === SortKeys.enrolled })); - -export const visibleList = (state, { - sortBy, - filters, - pageSize, -}) => { - const courses = Object.values(simpleSelectors.courseData(state)); - const list = module.currentList(courses, { sortBy, filters }); - const pageNumber = simpleSelectors.pageNumber(state); - - if (pageSize === 0) { - return { - visible: list, - numPages: 1, - }; - } - return { - visibleList: list.slice((pageNumber - 1) * pageSize, pageNumber * pageSize), - numPages: Math.ceil(list.length / pageSize), - }; -}; - -export default visibleList; diff --git a/src/data/redux/app/selectors/currentList.test.js b/src/data/redux/app/selectors/currentList.test.js deleted file mode 100644 index 058d5619c..000000000 --- a/src/data/redux/app/selectors/currentList.test.js +++ /dev/null @@ -1,185 +0,0 @@ -import { keyStore } from 'utils'; -import { FilterKeys, SortKeys } from 'data/constants/app'; -import simpleSelectors from './simpleSelectors'; -import * as module from './currentList'; - -jest.mock('./simpleSelectors', () => ({ - __esModule: true, - default: { - courseData: jest.fn(), - pageNumber: jest.fn(), - }, -})); - -const { - sortFn, - courseFilters, - transforms, - courseFilterFn, - currentList, - visibleList, -} = module; - -const moduleKeys = keyStore(module); - -const testDate = '2000-10-10'; -const testString = 'test-STRING'; - -describe('courseList selector module', () => { - describe('utilities', () => { - describe('sortFn', () => { - it('performs comparison sort after running both values through transform', () => { - const transform = ({ val }) => val; - expect(sortFn(transform, { reverse: false })({ val: 2 }, { val: 1 })).toEqual(1); - expect(sortFn(transform, { reverse: false })({ val: 1 }, { val: 1 })).toEqual(0); - expect(sortFn(transform, { reverse: false })({ val: 1 }, { val: 2 })).toEqual(-1); - expect(sortFn(transform, { reverse: true })({ val: 2 }, { val: 1 })).toEqual(-1); - expect(sortFn(transform, { reverse: true })({ val: 1 }, { val: 1 })).toEqual(0); - expect(sortFn(transform, { reverse: true })({ val: 1 }, { val: 2 })).toEqual(1); - }); - }); - describe('courseFilters', () => { - let filterFn; - test('notEnrolled returns true iff course is not enrolled', () => { - filterFn = courseFilters[FilterKeys.notEnrolled]; - expect(filterFn({ enrollment: { isEnrolled: true } })).toEqual(false); - expect(filterFn({ enrollment: { isEnrolled: false } })).toEqual(true); - }); - test('done returns true iff learner has finished course', () => { - filterFn = courseFilters[FilterKeys.done]; - expect(filterFn({ courseRun: null })).toEqual(false); - expect(filterFn({ courseRun: { isArchived: true } })).toEqual(true); - expect(filterFn({ courseRun: { isArchived: false } })).toEqual(false); - }); - test('upgraded returns true if learner is verified', () => { - filterFn = courseFilters[FilterKeys.upgraded]; - expect(filterFn({ enrollment: { isVerified: true } })).toEqual(true); - expect(filterFn({ enrollment: { isVerified: false } })).toEqual(false); - }); - test('inProgress returns true iff learner has started course', () => { - filterFn = courseFilters[FilterKeys.inProgress]; - expect(filterFn({ enrollment: { hasStarted: true } })).toEqual(true); - expect(filterFn({ enrollment: { hasStarted: false } })).toEqual(false); - }); - test('notStarted returns true iff learner has not started course', () => { - filterFn = courseFilters[FilterKeys.notStarted]; - expect(filterFn({ enrollment: { hasStarted: true } })).toEqual(false); - expect(filterFn({ enrollment: { hasStarted: false } })).toEqual(true); - }); - }); - describe('transforms', () => { - test('enrolled transform returns date transform of enrollment.lastEnrolled', () => { - const data = { enrollment: { lastEnrolled: testDate } }; - expect(transforms[SortKeys.enrolled](data)).toEqual(new Date(testDate)); - }); - test('title transform returns lowercase transform of course.courseName', () => { - const data = { course: { courseName: testString } }; - expect(transforms[SortKeys.title](data)).toEqual(testString.toLowerCase()); - }); - }); - describe('courseFilterFn', () => { - it('returns always-true filter if filter list is empty', () => { - expect(courseFilterFn([])()).toEqual(true); - }); - it('takes filters and then course, and returns true if course matches all filters', () => { - const filters = [FilterKeys.inProgress, FilterKeys.upgraded]; - const inProgressSpy = jest.spyOn(courseFilters, FilterKeys.inProgress); - const upgradedSpy = jest.spyOn(courseFilters, FilterKeys.upgraded); - inProgressSpy.mockImplementation(v => v > 3); - upgradedSpy.mockImplementation(v => v > 5); - - expect(courseFilterFn(filters)(6)).toEqual(true); - expect(courseFilterFn(filters)(5)).toEqual(false); - upgradedSpy.mockImplementation(v => v > 2); - expect(courseFilterFn(filters)(3)).toEqual(false); - - inProgressSpy.mockRestore(); - upgradedSpy.mockRestore(); - }); - }); - describe('currentList selector', () => { - it('returns passed courses filtered and sorted', () => { - const sortSpy = jest.spyOn(module, moduleKeys.sortFn); - const filterSpy = jest.spyOn(module, moduleKeys.courseFilterFn); - filterSpy.mockReturnValue(({ val }) => val > 0); - sortSpy.mockReturnValue((v1, v2) => { - const [a, b] = [v1, v2].map(({ val }) => val); - if (a === b) { return 0; } - return (a > b) ? 1 : -1; - }); - const testCourses = { - empty: { val: 0 }, - v1: { val: 1 }, - v2: { val: 2 }, - v3: { val: 3 }, - }; - const { - empty, - v1, - v2, - v3, - } = testCourses; - let sortBy = SortKeys.enrolled; - const testFilters = [1, 2, 3]; - expect(currentList( - [empty, v2, v1, empty, empty, v3, empty], - { sortBy, filters: testFilters }, - )).toEqual([v1, v2, v3]); - expect(sortSpy).toHaveBeenCalledWith(transforms[sortBy], { reverse: true }); - expect(filterSpy).toHaveBeenCalledWith(testFilters); - - sortSpy.mockClear(); - sortBy = SortKeys.title; - expect(currentList( - [empty, v2, v1, empty, empty, v3, empty], - { sortBy, filters: testFilters }, - )).toEqual([v1, v2, v3]); - expect(sortSpy).toHaveBeenCalledWith(transforms[sortBy], { reverse: false }); - expect(filterSpy).toHaveBeenCalledWith(testFilters); - sortSpy.mockRestore(); - filterSpy.mockRestore(); - }); - }); - }); - describe('visibleList selector', () => { - let listSpy; - let out; - const pageSize = 2; - const pageNumber = 3; - const testState = { some: 'state' }; - const sortBy = SortKeys.enrolled; - const testFilters = [1, 2, 3]; - const testCourseData = { test: 'course-data' }; - const testList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; - beforeEach(() => { - listSpy = jest.spyOn(module, moduleKeys.currentList); - listSpy.mockReturnValue(testList); - simpleSelectors.courseData.mockReturnValue(testCourseData); - simpleSelectors.pageNumber.mockReturnValue(pageNumber); - out = visibleList(testState, { sortBy, filters: testFilters, pageSize }); - }); - afterEach(() => { - listSpy.mockRestore(); - }); - it('loads currentList from courseData values and passed sortBy and filters values', () => { - expect(simpleSelectors.courseData).toHaveBeenCalledWith(testState); - expect(simpleSelectors.pageNumber).toHaveBeenCalledWith(testState); - expect(listSpy).toHaveBeenCalledWith( - Object.values(testCourseData), - { sortBy, filters: testFilters }, - ); - }); - it('returns visible page based on passed page size and stored pageNumber', () => { - // page 3, 2 per page. [0 1] [2 3] [4 5] ... - expect(out.visibleList).toEqual([testList[4], testList[5]]); - }); - it('returns number of pages based on page size and list length', () => { - expect(out.numPages).toEqual(6); - }); - it('disable pagination if page size is 0', () => { - out = visibleList(testState, { sortBy, filters: testFilters, pageSize: 0 }); - expect(out.visible).toEqual(testList); - expect(out.numPages).toEqual(1); - }); - }); -}); diff --git a/src/data/redux/app/selectors/index.js b/src/data/redux/app/selectors/index.js deleted file mode 100644 index a2cc58175..000000000 --- a/src/data/redux/app/selectors/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import { StrictDict } from 'utils'; - -import simpleSelectors from './simpleSelectors'; -import appSelectors from './appSelectors'; -import courseCard from './courseCard'; -import currentList from './currentList'; - -export default StrictDict({ - ...simpleSelectors, - ...appSelectors, - courseCard, - currentList, -}); diff --git a/src/data/redux/app/selectors/simpleSelectors.js b/src/data/redux/app/selectors/simpleSelectors.js deleted file mode 100644 index 0c576fc28..000000000 --- a/src/data/redux/app/selectors/simpleSelectors.js +++ /dev/null @@ -1,38 +0,0 @@ -import { createSelector } from 'reselect'; -import { StrictDict } from 'utils'; - -import * as module from './simpleSelectors'; - -export const appSelector = (state) => state.app; -const mkSimpleSelector = (cb) => createSelector([module.appSelector], cb); - -// top-level app data selectors -export const simpleSelectors = StrictDict({ - courseData: mkSimpleSelector(app => app.courseData), - platformSettings: mkSimpleSelector(app => app.platformSettings), - suggestedCourses: mkSimpleSelector(app => app.suggestedCourses), - emailConfirmation: mkSimpleSelector(app => app.emailConfirmation), - enterpriseDashboard: mkSimpleSelector(app => app.enterpriseDashboard || {}), - selectSessionModal: mkSimpleSelector(app => app.selectSessionModal), - pageNumber: mkSimpleSelector(app => app.pageNumber), - filters: mkSimpleSelector(app => app.filters), - socialShareSettings: mkSimpleSelector(app => app.socialShareSettings), -}); - -export const cardSimpleSelectors = StrictDict({ - certificate: ({ certificate }) => certificate, - course: ({ course }) => course, - courseProvider: ({ courseProvider }) => courseProvider, - courseRun: ({ courseRun }) => courseRun, - credit: ({ credit }) => credit, - enrollment: ({ enrollment }) => enrollment, - entitlement: ({ entitlement }) => entitlement, - gradeData: ({ gradeData }) => gradeData, - relatedPrograms: ({ programs: { relatedPrograms } }) => relatedPrograms, -}); - -export const mkCardSelector = (simpleSelector, selector) => (state, cardId) => ( - selector(simpleSelector(module.simpleSelectors.courseData(state)[cardId])) -); - -export default simpleSelectors; diff --git a/src/data/redux/app/selectors/simpleSelectors.test.js b/src/data/redux/app/selectors/simpleSelectors.test.js deleted file mode 100644 index 7d9a8dbed..000000000 --- a/src/data/redux/app/selectors/simpleSelectors.test.js +++ /dev/null @@ -1,75 +0,0 @@ -import { keyStore } from 'utils'; -import * as module from './simpleSelectors'; - -const { - appSelector, - simpleSelectors, - cardSimpleSelectors, - mkCardSelector, -} = module; - -let keys; -let testData; - -let testState; -const testString = 'test-STRING'; -const testCardId = 'testCARD-id'; - -describe('app simple selectors', () => { - describe('base app selector', () => { - }); - describe('simple selectors', () => { - keys = keyStore(simpleSelectors); - test.each([ - keys.courseData, - keys.platformSettings, - keys.suggestedCourses, - keys.emailConfirmation, - keys.enterpriseDashboard, - keys.selectSessionModal, - keys.pageNumber, - keys.socialShareSettings, - keys.filters, - ])('%s app simple selector forwards corresponding data from app store', (key) => { - testState = { app: { [key]: testString, otherField: 'fake string' } }; - const { preSelectors, cb } = simpleSelectors[key]; - expect(preSelectors).toEqual([appSelector]); - expect(cb(testState.app)).toEqual(testString); - }); - describe('cardSimpleSelectors', () => { - keys = keyStore(cardSimpleSelectors); - test.each([ - keys.certificate, - keys.course, - keys.courseProvider, - keys.courseRun, - keys.credit, - keys.enrollment, - keys.entitlement, - keys.gradeData, - ])('%s card simple selector forwards corresponding data from passed object', (key) => { - testState = { [key]: testString }; - expect(cardSimpleSelectors[key](testState)).toEqual(testString); - }); - test('relatedPrograms simple selector forwards relatedPrograms from programs obj', () => { - expect( - cardSimpleSelectors.relatedPrograms({ programs: { relatedPrograms: testString } }), - ).toEqual(testString); - }); - }); - describe('mkCardSelector util', () => { - it('takes [card simpleSelector, selector] and creates card selector from cardData', () => { - const selector = (data) => ({ selector: data }); - const simpleSelector = (data) => ({ simpleSelector: data }); - testData = { some: 'test data' }; - const oldCourseData = simpleSelectors.courseData; - simpleSelectors.courseData = jest.fn().mockReturnValueOnce({ [testCardId]: testData }); - expect(mkCardSelector(simpleSelector, selector)(testState, testCardId)).toEqual( - selector(simpleSelector(testData)), - ); - expect(simpleSelectors.courseData).toHaveBeenCalledWith(testState); - simpleSelectors.courseData = oldCourseData; - }); - }); - }); -}); diff --git a/src/data/redux/hooks/app.js b/src/data/redux/hooks/app.js deleted file mode 100644 index ec7a75166..000000000 --- a/src/data/redux/hooks/app.js +++ /dev/null @@ -1,107 +0,0 @@ -/* eslint-disable react-hooks/rules-of-hooks */ -import { useSelector, useDispatch } from 'react-redux'; - -import * as redux from 'data/redux'; -import * as module from './app'; - -const selectors = redux.selectors.app; -const actions = redux.actions.app; - -/** Simple Selectors **/ -export const usePageNumber = () => useSelector(selectors.pageNumber); -export const useFilters = () => useSelector(selectors.filters); -export const useEmailConfirmationData = () => useSelector(selectors.emailConfirmation); -export const useEnterpriseDashboardData = () => useSelector(selectors.enterpriseDashboard); -export const usePlatformSettingsData = () => useSelector(selectors.platformSettings); -export const useSelectSessionModalData = () => useSelector(selectors.selectSessionModal); -export const useSocialShareSettings = () => useSelector(selectors.socialShareSettings); - -/** global-level meta-selectors **/ -export const useHasCourses = () => useSelector(selectors.hasCourses); -export const useCurrentCourseList = (opts) => useSelector( - state => selectors.currentList(state, opts), -); -export const useShowSelectSessionModal = () => useSelector(selectors.showSelectSessionModal); - -// eslint-disable-next-line -export const useCourseCardData = (selector) => (cardId) => useSelector( - (state) => selector(state, cardId), -); -/** Course Card selectors **/ -const { courseCard } = selectors; -export const useCardCertificateData = useCourseCardData(courseCard.certificate); -export const useCardCourseData = useCourseCardData(courseCard.course); -export const useCardCourseRunData = useCourseCardData(courseCard.courseRun); -export const useCardCreditData = useCourseCardData(courseCard.credit); -export const useCardEnrollmentData = useCourseCardData(courseCard.enrollment); -export const useCardEntitlementData = useCourseCardData(courseCard.entitlement); -export const useCardGradeData = useCourseCardData(courseCard.gradeData); -export const useCardProviderData = useCourseCardData(courseCard.courseProvider); -export const useCardRelatedProgramsData = useCourseCardData(courseCard.relatedPrograms); - -export const useCardSocialSettingsData = (cardId) => { - const socialShareSettings = module.useSocialShareSettings(); - const { socialShareUrl } = module.useCardCourseData(cardId); - const defaultSettings = { isEnabled: false, shareUrl: '' }; - - if (!socialShareSettings) { - return { facebook: defaultSettings, twitter: defaultSettings }; - } - const { facebook, twitter } = socialShareSettings; - const loadSettings = (target) => ({ - isEnabled: target.isEnabled, - shareUrl: `${socialShareUrl}?${target.utmParams}`, - }); - return { facebook: loadSettings(facebook), twitter: loadSettings(twitter) }; -}; - -export const useCardExecEdTrackingParam = (cardId) => { - const { isExecEd2UCourse } = module.useCardEnrollmentData(cardId); - const { authOrgId } = module.useEnterpriseDashboardData(cardId); - return isExecEd2UCourse ? `?org_id=${authOrgId}` : ''; -}; - -/** Events **/ -export const useUpdateSelectSessionModalCallback = (cardId) => { - const dispatch = useDispatch(); - return () => dispatch(actions.updateSelectSessionModal(cardId)); -}; - -export const useTrackCourseEvent = (tracker, cardId, ...args) => { - const { courseId } = module.useCardCourseRunData(cardId); - return (e) => tracker(courseId, ...args)(e); -}; - -export const useSetPageNumber = () => { - const dispatch = useDispatch(); - return (value) => dispatch(actions.setPageNumber(value)); -}; - -export const useSetFilters = () => { - const dispatch = useDispatch(); - return (value) => dispatch(actions.setFilters(value)); -}; - -export const useAddFilter = () => { - const dispatch = useDispatch(); - return (value) => dispatch(actions.addFilter(value)); -}; - -export const useRemoveFilter = () => { - const dispatch = useDispatch(); - return (value) => dispatch(actions.removeFilter(value)); -}; - -export const useClearFilters = () => { - const dispatch = useDispatch(); - return (value) => dispatch(actions.clearFilters(value)); -}; - -export const useLoadData = () => { - const dispatch = useDispatch(); - return ({ courses, ...globalData }) => { - dispatch(actions.setPageNumber(1)); - dispatch(actions.loadGlobalData(globalData)); - dispatch(actions.loadCourses({ courses })); - }; -}; diff --git a/src/data/redux/hooks/index.js b/src/data/redux/hooks/index.js deleted file mode 100644 index ba6f57432..000000000 --- a/src/data/redux/hooks/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from './app'; -export * from './requests'; diff --git a/src/data/redux/hooks/requests.js b/src/data/redux/hooks/requests.js deleted file mode 100644 index 79537b824..000000000 --- a/src/data/redux/hooks/requests.js +++ /dev/null @@ -1,45 +0,0 @@ -import { useSelector, useDispatch } from 'react-redux'; - -import * as redux from 'data/redux'; -import * as module from './requests'; - -const selectors = redux.selectors.requests; -const actions = redux.actions.requests; - -export const useMasqueradeData = () => useSelector(selectors.masquerade); - -export const statusSelector = selector => (requestName) => useSelector(selector(requestName)); -export const useRequestIsPending = module.statusSelector(selectors.isPending); -export const useRequestIsFailed = module.statusSelector(selectors.isFailed); -export const useRequestIsCompleted = module.statusSelector(selectors.isCompleted); -export const useRequestIsInactive = module.statusSelector(selectors.isInactive); -export const useRequestError = module.statusSelector(selectors.error); -export const useRequestErrorCode = module.statusSelector(selectors.errorCode); -export const useRequestErrorStatus = module.statusSelector(selectors.errorStatus); -export const useRequestData = module.statusSelector(selectors.data); - -export const useMakeNetworkRequest = () => { - const dispatch = useDispatch(); - return ({ - requestKey, - promise, - onSuccess, - onFailure, - }) => { - dispatch(actions.startRequest({ requestKey })); - return promise.then((response) => { - if (onSuccess) { onSuccess(response); } - dispatch(actions.completeRequest({ requestKey, response })); - }).catch((error) => { - if (onFailure) { onFailure(error); } - dispatch(actions.failRequest({ requestKey, error })); - }); - }; -}; - -export const useClearRequest = () => { - const dispatch = useDispatch(); - return (requestKey) => { - dispatch(actions.clearRequest({ requestKey })); - }; -}; diff --git a/src/data/redux/index.js b/src/data/redux/index.js deleted file mode 100644 index 24e6e10b3..000000000 --- a/src/data/redux/index.js +++ /dev/null @@ -1,37 +0,0 @@ -import { combineReducers } from 'redux'; - -import { StrictDict } from 'utils'; - -import * as app from './app'; -import * as requests from './requests'; - -const modules = { - app, - requests, -}; - -/** - * Extracts keys from the modules object and the provided propName parameter to locate the - * corresponding object for that propName. - * Example: moduleProps('reducer') will return an aggregated object containing the reducer for each module - * - * @param {string} propName Used to locate the prop in each module - * @returns {object} Aggregated values for the provided propName - */ -const moduleProps = (propName) => Object.keys(modules).reduce( - (obj, moduleKey) => { - const value = modules[moduleKey][propName]; - return value ? { ...obj, [moduleKey]: value } : obj; - }, - {}, -); - -const rootReducer = combineReducers(moduleProps('reducer')); - -const actions = StrictDict(moduleProps('actions')); - -const selectors = StrictDict(moduleProps('selectors')); - -export { actions, selectors }; - -export default rootReducer; diff --git a/src/data/redux/requests/index.js b/src/data/redux/requests/index.js deleted file mode 100644 index 8abd5f91d..000000000 --- a/src/data/redux/requests/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { actions, reducer } from './reducer'; -export { default as selectors } from './selectors'; diff --git a/src/data/redux/requests/reducer.js b/src/data/redux/requests/reducer.js deleted file mode 100644 index e29bbf1ae..000000000 --- a/src/data/redux/requests/reducer.js +++ /dev/null @@ -1,54 +0,0 @@ -import { createSlice } from '@reduxjs/toolkit'; - -import { StrictDict } from 'utils'; - -import { RequestStates, RequestKeys } from 'data/constants/requests'; - -const initialState = { - [RequestKeys.initialize]: { status: RequestStates.inactive }, - [RequestKeys.refreshList]: { status: RequestStates.inactive }, - [RequestKeys.enrollEntitlementSession]: { status: RequestStates.inactive }, - [RequestKeys.leaveEntitlementSession]: { status: RequestStates.inactive }, - [RequestKeys.masquerade]: { status: RequestStates.inactive }, -}; - -// eslint-disable-next-line no-unused-vars -const requests = createSlice({ - name: 'requests', - initialState, - reducers: { - startRequest: (state, { payload }) => ({ - ...state, - [payload.requestKey]: { - status: RequestStates.pending, - }, - }), - completeRequest: (state, { payload }) => ({ - ...state, - [payload.requestKey]: { - status: RequestStates.completed, - response: payload.response, - }, - }), - failRequest: (state, { payload }) => ({ - ...state, - [payload.requestKey]: { - status: RequestStates.failed, - error: payload.error, - }, - }), - clearRequest: (state, { payload }) => ({ - ...state, - [payload.requestKey]: {}, - }), - }, -}); - -const actions = StrictDict(requests.actions); -const { reducer } = requests; - -export { - actions, - reducer, - initialState, -}; diff --git a/src/data/redux/requests/reducer.test.js b/src/data/redux/requests/reducer.test.js deleted file mode 100644 index 8eb39ea65..000000000 --- a/src/data/redux/requests/reducer.test.js +++ /dev/null @@ -1,62 +0,0 @@ -import { RequestStates } from 'data/constants/requests'; -import { initialState, actions, reducer } from './reducer'; - -const testingState = { - ...initialState, - arbitraryField: 'arbitrary', -}; - -describe('requests reducer', () => { - it('has initial state', () => { - expect(reducer(undefined, {})).toEqual(initialState); - }); - - const testValue = 'roll for initiative'; - const testKey = 'test-key'; - describe('handling actions', () => { - describe('startRequest', () => { - it('adds a pending status for the given key', () => { - expect(reducer( - testingState, - actions.startRequest({ requestKey: testKey }), - )).toEqual({ - ...testingState, - [testKey]: { status: RequestStates.pending }, - }); - }); - }); - describe('completeRequest', () => { - it('adds a completed status with passed response', () => { - expect(reducer( - testingState, - actions.completeRequest({ requestKey: testKey, response: testValue }), - )).toEqual({ - ...testingState, - [testKey]: { status: RequestStates.completed, response: testValue }, - }); - }); - }); - describe('failRequest', () => { - it('adds a failed status with passed error', () => { - expect(reducer( - testingState, - actions.failRequest({ requestKey: testKey, error: testValue }), - )).toEqual({ - ...testingState, - [testKey]: { status: RequestStates.failed, error: testValue }, - }); - }); - }); - describe('clearRequest', () => { - it('cleanup status and error', () => { - expect(reducer( - testingState, - actions.clearRequest({ requestKey: testKey, error: testValue }), - )).toEqual({ - ...testingState, - [testKey]: {}, - }); - }); - }); - }); -}); diff --git a/src/data/redux/requests/selectors.js b/src/data/redux/requests/selectors.js deleted file mode 100644 index ae2bd143b..000000000 --- a/src/data/redux/requests/selectors.js +++ /dev/null @@ -1,40 +0,0 @@ -import { StrictDict } from 'utils'; -import { RequestStates, RequestKeys } from 'data/constants/requests'; -// import * as module from './selectors'; - -export const requestStatus = (state, { requestKey }) => state.requests[requestKey]; - -const statusSelector = (fn) => (requestKey) => (state) => fn(state.requests[requestKey]); - -export const isInactive = ({ status }) => status === RequestStates.inactive; -export const isPending = ({ status }) => status === RequestStates.pending; -export const isCompleted = ({ status }) => status === RequestStates.completed; -export const isFailed = ({ status }) => status === RequestStates.failed; -export const error = (request) => request.error; -export const errorStatus = (request) => request.error?.response?.status; -export const errorCode = (request) => request.error?.response?.data; - -export const data = (request) => request.data; - -export const masquerade = (state) => { - const request = requestStatus(state, { requestKey: RequestKeys.masquerade }); - return { - isMasquerading: isCompleted(request), - isMasqueradingFailed: isFailed(request), - isMasqueradingPending: isPending(request), - masqueradeErrorStatus: errorStatus(request), - }; -}; - -export default StrictDict({ - requestStatus, - isInactive: statusSelector(isInactive), - isPending: statusSelector(isPending), - isCompleted: statusSelector(isCompleted), - isFailed: statusSelector(isFailed), - error: statusSelector(error), - errorCode: statusSelector(errorCode), - errorStatus: statusSelector(errorStatus), - data: statusSelector(data), - masquerade, -}); diff --git a/src/data/redux/requests/selectors.test.js b/src/data/redux/requests/selectors.test.js deleted file mode 100644 index f80936411..000000000 --- a/src/data/redux/requests/selectors.test.js +++ /dev/null @@ -1,110 +0,0 @@ -import { RequestStates, RequestKeys } from 'data/constants/requests'; - -import selectors from './selectors'; - -const requestKey = 'my-test-request-key'; -const requestData = { some: 'request-data' }; -const inactiveRequest = { status: RequestStates.inactive, some: 'request-data' }; -const pendingRequest = { status: RequestStates.pending, some: 'request-data' }; -const completedRequest = { status: RequestStates.completed, some: 'request-data' }; -const failedRequest = { status: RequestStates.failed, some: 'request-data' }; - -const testValue = 'my-test-value'; - -const testErrorValue = { - response: { - status: 500, - data: 'my-test-error', - }, -}; - -const testState = { - requests: { - [requestKey]: requestData, - }, -}; -const mockUseSelector = (selector, state) => selector(state); -const genRequests = (request) => ({ - requests: { [requestKey]: request }, -}); -const select = (selector, request) => ( - mockUseSelector(selector(requestKey), genRequests(request)) -); -describe('requests selectors unit tests', () => { - test('requestStatus returns data associated with given key', () => { - expect(selectors.requestStatus(testState, { requestKey })).toEqual(requestData); - }); - const testStatusSelector = (selector, matchingRequest) => { - expect(mockUseSelector(selector(requestKey), testState)).toEqual(false); - expect(mockUseSelector( - selector(requestKey), - { requests: { [requestKey]: matchingRequest } }, - )).toEqual(true); - }; - test('isInactive returns true iff the given request is inactive', () => { - testStatusSelector(selectors.isInactive, inactiveRequest); - }); - test('isPending returns true iff the given request is pending', () => { - testStatusSelector(selectors.isPending, pendingRequest); - }); - test('isCompleted returns true iff the given request is completed', () => { - testStatusSelector(selectors.isCompleted, completedRequest); - }); - test('isFailed returns true iff the given request is failed', () => { - testStatusSelector(selectors.isFailed, failedRequest); - }); - test('error returns the error from the request', () => { - expect(select(selectors.error, { error: testValue })).toEqual(testValue); - }); - test('errorStatus returns the error response status', () => { - expect(select(selectors.errorStatus, {})).toEqual(undefined); - expect(select(selectors.errorStatus, { error: {} })).toEqual(undefined); - expect(select(selectors.errorStatus, { error: { response: {} } })).toEqual(undefined); - expect(select(selectors.errorStatus, { error: { response: { status: testValue } } })) - .toEqual(testValue); - expect(select(selectors.errorStatus, { error: testErrorValue })).toEqual( - testErrorValue.response.status, - ); - }); - test('errorCode returns the error response data', () => { - expect(select(selectors.errorCode, {})).toEqual(undefined); - expect(select(selectors.errorCode, { error: {} })).toEqual(undefined); - expect(select(selectors.errorCode, { error: { response: {} } })).toEqual(undefined); - expect(select(selectors.errorCode, { error: { response: { data: testValue } } })) - .toEqual(testValue); - expect(select(selectors.errorCode, { error: testErrorValue })).toEqual( - testErrorValue.response.data, - ); - }); - test('data reurns the request data', () => { - expect(select(selectors.data, { data: testValue })).toEqual(testValue); - }); - test('masquerade returns the masquerade data', () => { - const mockResponse = (response) => ({ - requests: { - [RequestKeys.masquerade]: response, - }, - }); - expect(selectors.masquerade(mockResponse(completedRequest))).toEqual({ - isMasquerading: true, - isMasqueradingFailed: false, - isMasqueradingPending: false, - masqueradeErrorStatus: undefined, - }); - expect(selectors.masquerade(mockResponse(pendingRequest))).toEqual({ - isMasquerading: false, - isMasqueradingFailed: false, - isMasqueradingPending: true, - masqueradeErrorStatus: undefined, - }); - expect(selectors.masquerade(mockResponse({ - ...failedRequest, - error: testErrorValue, - }))).toEqual({ - isMasquerading: false, - isMasqueradingFailed: true, - isMasqueradingPending: false, - masqueradeErrorStatus: testErrorValue.response.status, - }); - }); -}); diff --git a/src/data/services/lms/api.js b/src/data/services/lms/api.js deleted file mode 100644 index e2d477181..000000000 --- a/src/data/services/lms/api.js +++ /dev/null @@ -1,77 +0,0 @@ -import eventNames from 'tracking/constants'; -import { - client, - get, - post, - stringifyUrl, -} from './utils'; -import { - apiKeys, - unenrollmentAction, - enableEmailsAction, -} from './constants'; -import urls from './urls'; -import * as module from './api'; - -/********************************************************************************* - * GET Actions - *********************************************************************************/ -export const initializeList = ({ user } = {}) => get( - stringifyUrl(urls.getInitApiUrl(), { [apiKeys.user]: user }), -); - -export const updateEntitlementEnrollment = ({ uuid, courseId }) => post( - urls.entitlementEnrollment(uuid), - { [apiKeys.courseRunId]: courseId }, -); - -export const deleteEntitlementEnrollment = ({ uuid, isRefundable }) => client() - .delete( - stringifyUrl( - urls.entitlementEnrollment(uuid), - { [apiKeys.isRefund]: isRefundable }, - ), - ); - -export const updateEmailSettings = ({ courseId, enable }) => post( - urls.updateEmailSettings(), - { [apiKeys.courseId]: courseId, ...(enable && enableEmailsAction) }, -); - -export const unenrollFromCourse = ({ courseId }) => post( - urls.courseUnenroll(), - { [apiKeys.courseId]: courseId, ...unenrollmentAction }, -); - -export const logEvent = ({ eventName, data, courseId }) => post(urls.event(), { - courserun_key: courseId, - event_type: eventName, - page: window.location.href, - event: JSON.stringify(data), -}); - -export const logShare = ({ courseId, site }) => module.logEvent({ - eventName: eventNames.shareClicked, - courseId, - data: { - course_id: courseId, - social_media_site: site, - location: 'dashboard', - }, -}); - -export const createCreditRequest = ({ providerId, courseId, username }) => post( - urls.creditRequestUrl(providerId), - { course_key: courseId, username }, -); - -export default { - initializeList, - unenrollFromCourse, - updateEmailSettings, - updateEntitlementEnrollment, - deleteEntitlementEnrollment, - logEvent, - logShare, - createCreditRequest, -}; diff --git a/src/data/services/lms/api.test.js b/src/data/services/lms/api.test.js deleted file mode 100644 index af24aad9f..000000000 --- a/src/data/services/lms/api.test.js +++ /dev/null @@ -1,156 +0,0 @@ -import { mockLocation } from 'testUtils'; -import { keyStore } from 'utils'; -import eventNames from 'tracking/constants'; -import * as api from './api'; -import * as utils from './utils'; -import urls from './urls'; -import { - apiKeys, - unenrollmentAction, - enableEmailsAction, -} from './constants'; - -jest.mock('./utils', () => { - const deleteFn = (...args) => ({ delete: args }); - return { - client: () => ({ delete: deleteFn }), - delete: deleteFn, - get: (...args) => ({ get: args }), - post: jest.fn((...args) => ({ post: args })), - stringifyUrl: (...args) => ({ stringifyUrl: args }), - }; -}); - -const testUser = 'test-user'; -const testUuid = 'test-UUID'; -const courseId = 'TEST-course-ID'; -const isRefundable = 'test-is-refundable'; - -const moduleKeys = keyStore(api); - -describe('lms api methods', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - test('keys identical to module', () => { - /* eslint-disable-next-line global-require */ - const { default: defaultApi, ...rest } = require('./api'); - expect(Object.keys(rest).sort()).toMatchObject(Object.keys(defaultApi).sort()); - }); - describe('initializeList', () => { - test('calls get with the correct url and user', () => { - const userArg = { - [apiKeys.user]: testUser, - }; - expect(api.initializeList(userArg)).toEqual( - utils.get(utils.stringifyUrl(urls.getInitApiUrl(), userArg)), - ); - }); - }); - describe('updateEntitlementEnrollment', () => { - it('calls post on entitlementEnrollment url with uuid and course run ID', () => { - expect( - api.updateEntitlementEnrollment({ uuid: testUuid, courseId }), - ).toEqual( - utils.post( - urls.entitlementEnrollment(testUuid), - { [apiKeys.courseRunId]: courseId }, - ), - ); - }); - }); - describe('deleteEntitlementEnrollment', () => { - it('calls delete on entitlementEnrollment url with uuid and null course run ID', () => { - expect( - api.deleteEntitlementEnrollment({ uuid: testUuid, isRefundable }), - ).toEqual( - utils.client().delete(utils.stringifyUrl( - urls.entitlementEnrollment(testUuid), - { [apiKeys.isRefund]: isRefundable }, - )), - ); - }); - }); - describe('updateEmailSettings', () => { - describe('disable', () => { - it('calls post on updateEmailSettings url with course ID', () => { - expect( - api.updateEmailSettings({ courseId, enable: false }), - ).toEqual( - utils.post(urls.updateEmailSettings(), { [apiKeys.courseId]: courseId }), - ); - }); - }); - describe('enable', () => { - it('calls post on updateEmailSettings url with course ID and enableEmailsAction', () => { - expect( - api.updateEmailSettings({ courseId, enable: true }), - ).toEqual( - utils.post( - urls.updateEmailSettings(), - { [apiKeys.courseId]: courseId, ...enableEmailsAction }, - ), - ); - }); - }); - }); - describe('unenrollFromCourse', () => { - it('calls post on unenrollFromCourse url with courseId and unenrollment action', () => { - expect( - api.unenrollFromCourse({ courseId }), - ).toEqual( - utils.post( - urls.courseUnenroll(), - { [apiKeys.courseId]: courseId, ...unenrollmentAction }, - ), - ); - }); - }); - describe('logging events', () => { - describe('logEvent', () => { - it('posts to event url with event data', () => { - const href = 'test-href'; - const eventName = 'test-event-key'; - const data = { some: 'data' }; - mockLocation(href); - expect( - api.logEvent({ courseId, eventName, data }), - ).toEqual( - utils.post(urls.event(), { - courserun_key: courseId, - event_type: eventName, - page: href, - event: JSON.stringify(data), - }), - ); - }); - }); - describe('logged events', () => { - const logEvent = (args) => ({ logEvent: args }); - beforeEach(() => { - jest.spyOn(api, moduleKeys.logEvent).mockImplementation(logEvent); - }); - test('logShare sends share clicke vent with course id, side and location', () => { - const site = 'test-site'; - expect(api.logShare({ courseId, site })).toEqual(logEvent({ - eventName: eventNames.shareClicked, - courseId, - data: { course_id: courseId, social_media_site: site, location: 'dashboard' }, - })); - }); - }); - }); - describe('credit requests', () => { - describe('createCreditRequest', () => { - const providerId = 'test-provider-id'; - const username = 'test-username'; - it('posts course ID and username to credit request url', () => { - api.createCreditRequest({ providerId, courseId, username }); - expect(utils.post).toHaveBeenCalledWith( - urls.creditRequestUrl(providerId), - { course_key: courseId, username }, - ); - }); - }); - }); -}); diff --git a/src/data/services/lms/api.test.tsx b/src/data/services/lms/api.test.tsx new file mode 100644 index 000000000..b68fe540b --- /dev/null +++ b/src/data/services/lms/api.test.tsx @@ -0,0 +1,285 @@ +import { getAuthenticatedHttpClient } from '@openedx/frontend-base'; +import urls from '@src/data/services/lms/urls'; +import { stringifyUrl } from '@src/data/services/lms/utils'; +import { + initializeList, + unenrollFromCourse, + updateEntitlementEnrollment, + deleteEntitlementEnrollment, + updateEmailSettings, + logEvent, + logShare, + createCreditRequest, + sendConfirmEmail, +} from './api'; + +// Mock dependencies +jest.mock('@openedx/frontend-base', () => ({ + ...jest.requireActual('@openedx/frontend-base'), + getAuthenticatedHttpClient: jest.fn(), +})); +jest.mock('@src/data/services/lms/constants', () => ({ + apiKeys: { + user: 'user', + courseId: 'course_id', + courseRunId: 'course_run_id', + isRefund: 'is_refund', + }, + enableEmailsAction: { enable: true }, + unenrollmentAction: { action: 'unenroll' }, +})); +jest.mock('@src/data/services/lms/urls'); +jest.mock('@src/data/services/lms/utils'); +jest.mock('@src/tracking/constants', () => ({ + __esModule: true, + default: { shareClicked: 'share_clicked' }, +})); + +const mockHttpClient = { + get: jest.fn(), + post: jest.fn(), + delete: jest.fn(), +}; + +const mockedGetAuthenticatedHttpClient = getAuthenticatedHttpClient as jest.MockedFunction< + typeof getAuthenticatedHttpClient>; +const mockedStringifyUrl = stringifyUrl as jest.MockedFunction; + +describe('API functions', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockedGetAuthenticatedHttpClient.mockReturnValue(mockHttpClient as any); + + // Mock urls + (urls as any).getInitApiUrl = jest.fn(() => '/api/init'); + (urls as any).courseUnenroll = jest.fn(() => '/api/unenroll'); + (urls as any).entitlementEnrollment = jest.fn((uuid) => `/api/entitlement/${uuid}`); + (urls as any).updateEmailSettings = jest.fn(() => '/api/email-settings'); + (urls as any).event = jest.fn(() => '/api/event'); + (urls as any).creditRequestUrl = jest.fn((providerId) => `/api/credit/${providerId}`); + + mockedStringifyUrl.mockImplementation((url, params) => { + const paramString = Object.entries(params || {}).map(([key, value]) => `${key}=${value}`).join('&'); + return paramString ? `${url}?${paramString}` : url; + }); + + // Mock window.location + delete (window as any).location; + (window as any).location = { href: 'https://example.com/dashboard' }; + }); + + describe('initializeList', () => { + it('should make GET request to init API with user parameter', async () => { + const mockData = { courses: [], entitlements: [] }; + mockHttpClient.get.mockResolvedValue({ data: mockData }); + + const result = await initializeList('test-user'); + + expect(urls.getInitApiUrl).toHaveBeenCalled(); + expect(stringifyUrl).toHaveBeenCalledWith('/api/init', { user: 'test-user' }); + expect(mockHttpClient.get).toHaveBeenCalledWith('/api/init?user=test-user'); + expect(result).toEqual(mockData); + }); + + it('should handle null user parameter', async () => { + const mockData = { courses: [] }; + mockHttpClient.get.mockResolvedValue({ data: mockData }); + + await initializeList(null); + + expect(stringifyUrl).toHaveBeenCalledWith('/api/init', { user: null }); + }); + }); + + describe('unenrollFromCourse', () => { + it('should make POST request to unenroll from course with FormData', async () => { + const mockResponse = { status: 200 }; + mockHttpClient.post.mockResolvedValue(mockResponse); + const result = await unenrollFromCourse({ courseId: 'course-123' }); + expect(urls.courseUnenroll).toHaveBeenCalled(); + expect(mockHttpClient.post).toHaveBeenCalledWith('/api/unenroll', expect.any(FormData)); + expect(result).toEqual(mockResponse); + }); + }); + + describe('updateEntitlementEnrollment', () => { + it('should make POST request to update entitlement enrollment', async () => { + const mockResponse = { status: 200 }; + mockHttpClient.post.mockResolvedValue(mockResponse); + + const result = await updateEntitlementEnrollment({ + uuid: 'entitlement-uuid', + courseId: 'course-123', + }); + + expect(urls.entitlementEnrollment).toHaveBeenCalledWith('entitlement-uuid'); + expect(mockHttpClient.post).toHaveBeenCalledWith('/api/entitlement/entitlement-uuid', { + course_run_id: 'course-123', + }); + expect(result).toEqual(mockResponse); + }); + }); + + describe('deleteEntitlementEnrollment', () => { + it('should make DELETE request with refundable flag', async () => { + const mockResponse = { status: 204 }; + mockHttpClient.delete.mockResolvedValue(mockResponse); + + const result = await deleteEntitlementEnrollment({ + uuid: 'entitlement-uuid', + isRefundable: true, + }); + + expect(urls.entitlementEnrollment).toHaveBeenCalledWith('entitlement-uuid'); + expect(stringifyUrl).toHaveBeenCalledWith('/api/entitlement/entitlement-uuid', { + is_refund: true, + }); + expect(mockHttpClient.delete).toHaveBeenCalledWith('/api/entitlement/entitlement-uuid?is_refund=true'); + expect(result).toEqual(mockResponse); + }); + + it('should handle non-refundable deletion', async () => { + const mockResponse = { status: 204 }; + mockHttpClient.delete.mockResolvedValue(mockResponse); + + await deleteEntitlementEnrollment({ + uuid: 'entitlement-uuid', + isRefundable: false, + }); + + expect(stringifyUrl).toHaveBeenCalledWith('/api/entitlement/entitlement-uuid', { + is_refund: false, + }); + }); + }); + + describe('updateEmailSettings', () => { + it('should make POST request to enable email settings', async () => { + const mockResponse = { status: 200 }; + mockHttpClient.post.mockResolvedValue(mockResponse); + + const result = await updateEmailSettings({ + courseId: 'course-123', + enable: true, + }); + + expect(urls.updateEmailSettings).toHaveBeenCalled(); + expect(mockHttpClient.post).toHaveBeenCalledWith('/api/email-settings', { + course_id: 'course-123', + enable: true, + }); + expect(result).toEqual(mockResponse); + }); + + it('should make POST request to disable email settings', async () => { + const mockResponse = { status: 200 }; + mockHttpClient.post.mockResolvedValue(mockResponse); + + const result = await updateEmailSettings({ + courseId: 'course-123', + enable: false, + }); + + expect(mockHttpClient.post).toHaveBeenCalledWith('/api/email-settings', { + course_id: 'course-123', + }); + expect(result).toEqual(mockResponse); + }); + }); + + describe('logEvent', () => { + it('should make POST request to log event', async () => { + const mockResponse = { status: 200 }; + mockHttpClient.post.mockResolvedValue(mockResponse); + + const eventData = { + eventName: 'test-event', + data: { key: 'value' }, + courseId: 'course-123', + }; + + const result = await logEvent(eventData); + + expect(urls.event).toHaveBeenCalled(); + expect(mockHttpClient.post).toHaveBeenCalledWith('/api/event', { + courserun_key: 'course-123', + event_type: 'test-event', + page: 'https://example.com/dashboard', + event: JSON.stringify({ key: 'value' }), + }); + expect(result).toEqual(mockResponse); + }); + }); + + describe('logShare', () => { + it('should call logEvent with share event data', async () => { + const mockResponse = { status: 200 }; + mockHttpClient.post.mockResolvedValue(mockResponse); + + const result = await logShare({ + courseId: 'course-123', + site: 'facebook', + }); + + expect(mockHttpClient.post).toHaveBeenCalledWith('/api/event', { + courserun_key: 'course-123', + event_type: 'share_clicked', + page: 'https://example.com/dashboard', + event: JSON.stringify({ + course_id: 'course-123', + social_media_site: 'facebook', + location: 'dashboard', + }), + }); + expect(result).toEqual(mockResponse); + }); + }); + + describe('createCreditRequest', () => { + it('should make POST request to create credit request', async () => { + const mockResponse = { status: 201 }; + mockHttpClient.post.mockResolvedValue(mockResponse); + + const result = await createCreditRequest({ + providerId: 'provider-123', + courseId: 'course-123', + username: 'test-user', + }); + + expect(urls.creditRequestUrl).toHaveBeenCalledWith('provider-123'); + expect(mockHttpClient.post).toHaveBeenCalledWith('/api/credit/provider-123', { + course_key: 'course-123', + username: 'test-user', + }); + expect(result).toEqual(mockResponse); + }); + }); + + describe('sendConfirmEmail', () => { + it('should make POST request to send confirmation email', async () => { + const mockResponse = { status: 200 }; + mockHttpClient.post.mockResolvedValue(mockResponse); + + const result = await sendConfirmEmail('https://example.com/send-email'); + + expect(mockHttpClient.post).toHaveBeenCalledWith('https://example.com/send-email'); + expect(result).toEqual(mockResponse); + }); + }); + + describe('Error handling', () => { + it('should propagate network errors', async () => { + const networkError = new Error('Network error'); + mockHttpClient.get.mockRejectedValue(networkError); + + await expect(initializeList('test-user')).rejects.toThrow('Network error'); + }); + + it('should propagate API errors', async () => { + const apiError = { response: { status: 404, data: { error: 'Not found' } } }; + mockHttpClient.post.mockRejectedValue(apiError); + + await expect(unenrollFromCourse({ courseId: 'invalid-course' })).rejects.toEqual(apiError); + }); + }); +}); diff --git a/src/data/services/lms/api.ts b/src/data/services/lms/api.ts new file mode 100644 index 000000000..a04093f09 --- /dev/null +++ b/src/data/services/lms/api.ts @@ -0,0 +1,95 @@ +import { getAuthenticatedHttpClient } from '@openedx/frontend-base'; +import { apiKeys, enableEmailsAction, unenrollmentAction } from '@src/data/services/lms/constants'; +import urls from '@src/data/services/lms/urls'; +import { stringifyUrl } from '@src/data/services/lms/utils'; +import eventNames from '@src/tracking/constants'; + +const initializeList = async (user) => { + const { data } = await getAuthenticatedHttpClient().get( + stringifyUrl(urls.getInitApiUrl(), { [apiKeys.user]: user }), + ); + return data; +}; + +const unenrollFromCourse = async ({ courseId }) => { + const url = urls.courseUnenroll(); + const formData = new FormData(); + formData.append(apiKeys.courseId, courseId); + Object.entries(unenrollmentAction).forEach(([key, value]) => { + formData.append(key, value); + }); + const response = await getAuthenticatedHttpClient().post(url, formData); + return response; +}; + +const updateEntitlementEnrollment = async ({ uuid, courseId }) => { + const url = urls.entitlementEnrollment(uuid); + const content = { [apiKeys.courseRunId]: courseId }; + const response = await getAuthenticatedHttpClient().post(url, content); + return response; +}; + +const deleteEntitlementEnrollment = async ({ uuid, isRefundable }) => { + const url = stringifyUrl( + urls.entitlementEnrollment(uuid), + { [apiKeys.isRefund]: isRefundable }, + ); + const response = await getAuthenticatedHttpClient().delete(url); + return response; +}; + +const updateEmailSettings = async ({ courseId, enable }) => { + const url = urls.updateEmailSettings(); + const content = { [apiKeys.courseId]: courseId, ...(enable && enableEmailsAction) }; + const response = await getAuthenticatedHttpClient().post(url, content); + return response; +}; + +const logEvent = async ({ eventName, data, courseId }) => { + const url = urls.event(); + const content = { + courserun_key: courseId, + event_type: eventName, + page: window.location.href, + event: JSON.stringify(data), + }; + const response = await getAuthenticatedHttpClient().post(url, content); + return response; +}; + +const logShare = ({ courseId, site }) => { + const eventData = { + eventName: eventNames.shareClicked, + courseId, + data: { + course_id: courseId, + social_media_site: site, + location: 'dashboard', + }, + }; + return logEvent(eventData); +}; + +const createCreditRequest = async ({ providerId, courseId, username }) => { + const url = urls.creditRequestUrl(providerId); + const content = { course_key: courseId, username }; + const response = await getAuthenticatedHttpClient().post(url, content); + return response; +}; + +const sendConfirmEmail = async (sendEmailUrl: string) => { + const response = await getAuthenticatedHttpClient().post(sendEmailUrl); + return response; +}; + +export { + initializeList, + unenrollFromCourse, + updateEntitlementEnrollment, + deleteEntitlementEnrollment, + updateEmailSettings, + logEvent, + logShare, + createCreditRequest, + sendConfirmEmail, +}; diff --git a/src/data/services/lms/constants.js b/src/data/services/lms/constants.js index e02e97aec..d725e2c0c 100644 --- a/src/data/services/lms/constants.js +++ b/src/data/services/lms/constants.js @@ -1,4 +1,4 @@ -import { StrictDict } from 'utils'; +import { StrictDict } from '../../../utils'; export const apiKeys = StrictDict({ receiveEmails: 'receive_emails', diff --git a/src/data/services/lms/fakeData/courses.js b/src/data/services/lms/fakeData/courses.js deleted file mode 100644 index 31911a969..000000000 --- a/src/data/services/lms/fakeData/courses.js +++ /dev/null @@ -1,828 +0,0 @@ -import { StrictDict } from 'utils'; -import creditVals from 'data/constants/credit'; - -export const providers = StrictDict({ - edx: { name: 'edX Course Provider' }, - mit: { name: 'MIT' }, -}); - -export const relatedPrograms = [ - { - provider: 'HarvardX', - bannerImgSrc: 'https://prod-discovery.edx-cdn.org/media/course/image/327c8e4f-315a-417b-9857-046dfc90c243-677b97464958.small.jpg', - logoImgSrc: 'https://prod-discovery.edx-cdn.org/organization/certificate_logos/44022f13-20df-4666-9111-cede3e5dc5b6-770e00385e7e.png', - title: 'Relativity in Modern Mechanics', - programUrl: 'www.edx/my-program', - programType: 'MicroBachelors Program', - numberOfCourses: 3, - }, - { - provider: 'University of Maryland', - bannerImgSrc: 'https://prod-discovery.edx-cdn.org/media/programs/banner_images/9a310b98-8f27-439e-be85-12d6460245c9-f2efca129273.small.jpg', - logoImgSrc: 'https://prod-discovery.edx-cdn.org/organization/certificate_logos/b9dc96da-b3fc-45a6-b6b7-b8e12eb79335-ac60112330e3.png', - title: 'Pandering for Modern Professionals', - programUrl: 'www.edx/my-program-2', - programType: 'MicroBachelors Program', - programTypeUrl: 'www.edx/my-program-type', - numberOfCourses: 3, - }, - { - provider: 'HarvardX', - bannerImgSrc: 'https://prod-discovery.edx-cdn.org/media/course/image/327c8e4f-315a-417b-9857-046dfc90c243-677b97464958.small.jpg', - logoImgSrc: 'https://prod-discovery.edx-cdn.org/organization/certificate_logos/44022f13-20df-4666-9111-cede3e5dc5b6-770e00385e7e.png', - title: 'Relativity in Modern Mechanics', - programUrl: 'www.edx/my-program-3', - programType: 'MicroBachelors Program', - numberOfCourses: 3, - }, - { - provider: 'University of Maryland', - bannerImgSrc: 'https://prod-discovery.edx-cdn.org/media/programs/banner_images/9a310b98-8f27-439e-be85-12d6460245c9-f2efca129273.small.jpg', - logoImgSrc: 'https://prod-discovery.edx-cdn.org/organization/certificate_logos/b9dc96da-b3fc-45a6-b6b7-b8e12eb79335-ac60112330e3.png', - title: 'Pandering for Modern Professionals', - programUrl: 'www.edx/my-program-4', - programType: 'MicroBachelors Program', - programTypeUrl: 'www.edx/my-program-type', - numberOfCourses: 3, - }, -]; - -export const genCardId = (index) => `card-id${index}`; -export const genCourseId = (index) => `course-number${index}-course-id${index}`; -export const genCourseNumber = (index) => `course-number${index}`; -export const genCourseShareUrl = (index) => `home.edx.org?social-share-url/${index}`; -export const genEntitlementUUID = (index) => `entitlement-course-uuid-${index}`; - -const bannerImgSrc = '/asset-v1:edX+DemoX+Demo_Course+type@asset+block@images_course_image.jpg'; - -export const farPastDate = '1900-11-11T00:00:00Z'; -export const pastDate = '2000-11-11T00:00:00Z'; -export const futureDate = '3030-11-11T00:00:00Z'; -export const farFutureDate = '4040-11-11T00:00:00Z'; -export const soonDate = new Date(); -soonDate.setDate(soonDate.getDate() + 60); -export const soonDateStr = soonDate.toDateString(); - -export const globalData = { - emailConfirmation: { - isNeeded: true, - sendEmailUrl: 'sendConfirmation@edx.org', - }, - enterpriseDashboard: { label: 'edX, Inc.', url: '/edx-dashboard' }, - platformSettings: { - supportEmail: 'support@example.com', - billingEmail: 'billing@email.com', - courseSearchUrl: 'edx.com/course-search', - }, - suggestedCourses: [ - { - bannerImgSrc: 'https://prod-discovery.edx-cdn.org/media/programs/banner_images/9a310b98-8f27-439e-be85-12d6460245c9-f2efca129273.small.jpg', - courseName: 'Suggested course 1', - courseUrl: 'www.edx/suggested-course', - }, - { - bannerImgSrc: 'https://prod-discovery.edx-cdn.org/media/programs/banner_images/9a310b98-8f27-439e-be85-12d6460245c9-f2efca129273.small.jpg', - courseName: 'Suggested course 2 with a really really really long name for some reason', - courseUrl: 'www.edx/suggested-course', - }, - { - bannerImgSrc: 'https://prod-discovery.edx-cdn.org/media/programs/banner_images/9a310b98-8f27-439e-be85-12d6460245c9-f2efca129273.small.jpg', - courseName: 'Suggested course 3', - courseUrl: 'www.edx/suggested-course', - }, - { - bannerImgSrc: 'https://prod-discovery.edx-cdn.org/media/programs/banner_images/9a310b98-8f27-439e-be85-12d6460245c9-f2efca129273.small.jpg', - courseName: 'Suggested course 4', - courseUrl: 'www.edx/suggested-course', - }, - ], - socialShareSettings: { - facebook: { - isEnabled: true, - socialBrand: 'edx.org', - utmParams: 'utm_campaign=social-sharing-db&utm_medium=social&utm_source=facebook', - }, - twitter: { - isEnabled: true, - socialBrand: 'edx.org', - utmParams: 'utm_campaign=social-sharing-db&utm_medium=social&utm_source=twitter', - }, - }, -}; - -export const genCourseRunData = (data = {}) => ({ - isStarted: false, - isArchived: false, - startDate: data.isStarted ? farPastDate : futureDate, - endDate: farFutureDate, - minPassingGrade: '0.70', - homeUrl: 'edx.com/courses/my-course-url/home', - marketingUrl: 'edx.com/courses/my-course-url/marketing', - progressUrl: 'edx.com/courses/my-course-url/progress', - unenrollUrl: 'edx.com/courses/my-course-url/unenroll', - resumeUrl: 'edx.com/courses/my-course-url/resume', - ...data, -}); - -export const creditData = { - providerStatusUrl: 'test-provider-status-url', - providerName: 'Credit Provider Name', - providerId: 'credit-provider-id', - error: false, - purchased: false, - requestStatus: null, -}; - -export const genEnrollmentData = (data = {}) => ({ - coursewareAccess: { - isTooEarly: false, - hasUnmetPrerequisites: false, - isStaff: false, - }, - accessExpirationDate: ((data.isEnrolled === false) ? null : futureDate), - canUpgrade: (data.isVerified ? null : true), - hasStarted: false, - isAudit: !data.isVerified && (data.isEnrolled !== false), - isAuditAccessExpired: data.isVerified ? null : false, - isEmailEnabled: false, - hasOptedOutOfEmail: false, - isEnrolled: true, - isVerified: false, - ...data, -}); - -export const genCertificateData = (data = {}) => ({ - availableDate: null, - isRestricted: false, - isEarned: false, - isDownloadable: false, - certPreviewUrl: 'edx.com/courses/my-course-url/cert-preview', - ...data, -}); - -export const availableSessions = [ - { - startDate: '2000-01-02T00:00:00Z', - endDate: '2020-01-02T00:00:00Z', - courseId: genCourseId(1000), - }, - { - startDate: '2000-02-03T00:00:00Z', - endDate: '2020-02-03T00:00:00Z', - courseId: genCourseId(1001), - }, - { - startDate: '2000-03-04T00:00:00Z', - endDate: '2020-03-04T00:00:00Z', - courseId: genCourseId(1002), - }, - { - startDate: '2000-04-05T00:00:00Z', - endDate: '2020-04-05T00:00:00Z', - courseId: genCourseId(1003), - }, - { - startDate: '2000-05-06T00:00:00Z', - endDate: '2020-05-06T00:00:00Z', - courseId: genCourseId(1004), - }, -]; - -const auditCourses = [ - // audit, course run not started - { - courseName: 'Audit Course, Course run not started', - }, - // audit, course run not started, too early to view - { - courseName: 'Audit Course, Course run not started, Too early to view', - enrollment: { - coursewareAccess: { - isTooEarly: true, - hasUnmetPrerequisites: false, - isStaff: false, - }, - }, - }, - // audit, course run not started, too early to view and unmet prereqs - { - courseName: 'Audit Course, Course run not started, Too early to view, Has unmet prereqs.', - enrollment: { - coursewareAccess: { - isTooEarly: true, - hasUnmetPrerequisites: true, - isStaff: false, - }, - }, - }, - // audit, course run started - { - courseName: 'Audit Course, Course run not started', - courseRun: { isStarted: true }, - }, - // audit, course run started, unmet prereqs - { - courseName: 'Audit Course, Course run not started, Has unmet prereqs', - enrollment: { - coursewareAccess: { - isTooEarly: true, - hasUnmetPrerequisites: true, - isStaff: false, - }, - }, - courseRun: { isStarted: true }, - }, - // audit, course run started, access expired, learner not started - { - courseName: 'Audit Course, Course run started, Audit ccess expired, Learner not started', - courseRun: { isStarted: true }, - enrollment: { - accessExpirationDate: pastDate, - isAuditAccessExpired: true, - }, - }, - // audit, course run started, access expired, cannot upgrade, learner not started - { - courseName: 'Audit course, Course run not started, Audit access expired, Cannot upgrade, Learner not started', - courseRun: { isStarted: true }, - enrollment: { - accessExpirationDate: pastDate, - canUpgrade: false, - isAuditAccessExpired: true, - }, - }, - // audit, course run ended, access expired, cannot upgrade, learner not started - { - courseName: 'Audit Course, Course run ended, Audit access expired, Cannot upgrade, Learner not started', - courseRun: { - endDate: pastDate, - isStarted: true, - }, - enrollment: { - accessExpirationDate: pastDate, - isAuditAccessExpired: true, - }, - }, - // audit, course run archived, access expired, cannot upgrade, learner not started - { - courseName: 'Audit Course, Course run archived, Audit access expired, Cannot upgrade, Learner not started', - courseRun: { - endDate: pastDate, - isArchived: true, - isStarted: true, - }, - enrollment: { - accessExpirationDate: pastDate, - isAuditAccessExpired: true, - }, - }, - // audit, course run and learner started, passing - { - courseName: 'Audit Course, Course run and learner started, Passing', - courseRun: { isStarted: true }, - enrollment: { hasStarted: true }, - }, - // audit, course run and learner started, access expired - { - courseName: 'Audit Course, Course run and learner started, Audit access expired', - courseRun: { isStarted: true }, - enrollment: { - accessExpirationDate: pastDate, - isAuditAccessExpired: true, - hasStarted: true, - }, - }, - // audit, course run and learner started, access expired, cannot upgrade - { - courseName: 'Audit Course, Course run and learner started, Audit access expired, Cannot upgrade', - courseRun: { isStarted: true }, - enrollment: { - accessExpirationDate: pastDate, - canUpgrade: false, - isAuditAccessExpired: true, - hasStarted: true, - }, - }, - // audit, course run ended, learner started, expired, cannot upgraded, not passing - { - courseName: 'Audit Course, Course run ended, Learner started, Access expired, Cannot upgrade, Not passing', - courseRun: { - isStarted: true, - endDate: pastDate, - }, - enrollment: { - accessExpirationDate: pastDate, - canUpgrade: false, - isAuditAccessExpired: true, - hasStarted: true, - }, - grade: { isPassing: false }, - }, - // audit, course run archived, learner started, expired, cannot upgrade, not passing - { - courseName: 'Audit Course, Course run archived, Learner started, Access expired, Cannot upgrade, Not passing', - courseRun: { - isStarted: true, - isArchived: true, - endDate: pastDate, - }, - enrollment: { - accessExpirationDate: pastDate, - canUpgrade: false, - isAuditAccessExpired: true, - hasStarted: true, - }, - grade: { isPassing: false }, - }, -]; -const verifiedCourses = [ - // verified, course not started, learner not started - { - courseName: 'Verified Course, Course and learner not started', - enrollment: { isVerified: true }, - }, - // verified, course started, learner not started - { - courseName: 'Verified Course, Course started, Learner not started', - courseRun: { isStarted: true }, - enrollment: { isVerified: true }, - }, - // verified, course started, learner started, passing - { - courseName: 'Verified Course, Course and learner started, Passing', - courseRun: { isStarted: true }, - enrollment: { hasStarted: true, isVerified: true }, - }, - // verified, course started, learner started, not passing - { - courseName: 'Verified Course, Course and learner started, not passing', - courseRun: { isStarted: true }, - gradeData: { isPassing: false }, - enrollment: { hasStarted: true, isVerified: true }, - }, - // verified, learner finished, not passing, cert not earned - { - courseName: 'Verified Course, Learner finished, cert not earned', - enrollment: { - hasStarted: true, - isVerified: true, - }, - courseRun: { isStarted: true, isArchived: true }, - gradeData: { isPassing: false }, - certificate: { - isEarned: false, - }, - }, - // verified, learner finished, passing, cert earned but not available - { - courseName: 'Verified Course, Learner finished, Cert earned but not available', - enrollment: { - hasStarted: true, - isVerified: true, - }, - courseRun: { isStarted: true, isArchived: true }, - certificate: { - isEarned: true, - availableDate: futureDate, - }, - }, - // verified, learner finished, passing, restricted - { - courseName: 'Verified Course, Learner finished, Passing, Certificate restricted', - enrollment: { - hasStarted: true, - isVerified: true, - }, - courseRun: { isStarted: true, isArchived: true }, - certificate: { isRestricted: true }, - }, - // verified, learner finished, cert earned, downloadable (web + link) - { - courseName: 'Verified Course, Learner finished, Passing, Certificate downloadable and viewable', - enrollment: { - hasStarted: true, - isVerified: true, - }, - courseRun: { isStarted: true, isArchived: true }, - certificate: { - isEarned: true, - isDownloadable: true, - availableDate: pastDate, - certPreviewUrl: bannerImgSrc, - }, - }, - // verified, course ended, learner finished, cert earned, downloadable (link only), - { - courseName: 'Verified Course, Course ended, Learner finished, Passing, Certificate downloadable', - enrollment: { - hasStarted: true, - isVerified: true, - }, - courseRun: { - isStarted: true, - isArchived: true, - endDate: pastDate, - }, - certificate: { - isEarned: true, - isDownloadable: true, - availableDate: pastDate, - }, - }, - // verified, course ended, learner finished, cert earned, downloadable (web + link) - { - courseName: 'Verified Course, Course ended, Learner finished, Passing, Certificate downloadable and viewable', - enrollment: { - hasStarted: true, - isVerified: true, - }, - courseRun: { - isStarted: true, - isArchived: true, - endDate: pastDate, - }, - certificate: { - isEarned: true, - isDownloadable: true, - availableDate: pastDate, - certPreviewUrl: bannerImgSrc, - }, - }, -]; -const fulfilledEntitlementCourses = [ - // Entitlement - not started - { - courseName: 'Entitlement Course, not started', - enrollment: { - isVerified: true, - coursewareAccess: { - isTooEarly: true, - hasUnmetPrerequisites: false, - isStaff: false, - }, - }, - courseRun: { isStarted: false }, - entitlement: { - uuid: genEntitlementUUID(0), - availableSessions, - changeDeadline: futureDate, - enrollmentUrl: '/entitlement-enrollment', - isExpired: false, - isFulfilled: true, - isRefundable: true, - }, - }, - // Entitlement - Course run started, learner not started, unmet prereqs - { - courseName: 'Entitlement Course, Course run started, Learner not started, Has unmet prereqs', - enrollment: { - isVerified: true, - coursewareAccess: { - }, - }, - courseRun: { isStarted: true }, - entitlement: { - uuid: genEntitlementUUID(1), - availableSessions, - changeDeadline: futureDate, - enrollmentUrl: '/entitlement-enrollment', - isExpired: false, - isFulfilled: true, - isRefundable: true, - }, - }, - // Entitlement - Course run started, learner started, not passing - { - courseName: 'Entitlement Course, Course run started, Learner started, Not passing', - enrollment: { - isVerified: true, - hasStarted: true, - }, - courseRun: { isStarted: true }, - entitlement: { - uuid: genEntitlementUUID(2), - availableSessions, - changeDeadline: futureDate, - enrollmentUrl: '/entitlement-enrollment', - isExpired: false, - isFulfilled: true, - isRefundable: true, - }, - gradeData: { isPassing: false }, - }, - // Entitlement - Course run started, learner started, passing, cannot change - { - courseName: 'Entitlement Course, Course run and learner started, Passing, Cannot change sessions', - enrollment: { - isVerified: true, - hasStarted: true, - }, - courseRun: { isStarted: true }, - entitlement: { - uuid: genEntitlementUUID(3), - availableSessions, - changeDeadline: pastDate, - enrollmentUrl: '/entitlement-enrollment', - isExpired: false, - isFulfilled: true, - isRefundable: true, - }, - }, - // Entitlement - Learner finished, but did not pass - { - courseName: 'Entitlement Course, Learner finished but did not pass', - enrollment: { - isVerified: true, - }, - courseRun: { isStarted: true, isArchived: false }, - entitlement: { - uuid: genEntitlementUUID(4), - availableSessions: null, - changeDeadline: pastDate, - enrollmentUrl: '/entitlement-enrollment', - isExpired: false, - isFulfilled: true, - isRefundable: false, - }, - gradeData: { isPassing: false }, - }, - // Entitlement - Learner finished, and passed. cannot refund. previewable cert. - { - courseName: 'Entitlement course, Learner finished and passed, Cannot refund, Previewable Cert', - enrollment: { - isVerified: true, - }, - courseRun: { isStarted: true, isArchived: false }, - entitlement: { - uuid: genEntitlementUUID(5), - availableSessions: null, - changeDeadline: pastDate, - enrollmentUrl: '/entitlement-enrollment', - isExpired: false, - isFulfilled: true, - isRefundable: false, - }, - certificate: { - isEarned: true, - isDownloadable: true, - availableDate: pastDate, - certPreviewUrl: bannerImgSrc, - }, - }, - // Entitlement - Learner finished and failed. cannot refund. course ended. - { - courseName: 'Entitlement Course, Learner finished and failed, Cannot refund, Course ended', - enrollment: { - isVerified: true, - }, - courseRun: { - isStarted: true, - isArchived: false, - endDate: pastDate, - }, - entitlement: { - uuid: genEntitlementUUID(6), - availableSessions: null, - enrollmentUrl: '/entitlement-enrollment', - isFulfilled: true, - isRefundable: false, - changeDeadline: pastDate, - isExpired: false, - }, - gradeData: { isPassing: false }, - }, - // Entitlement - Learner finished and passed. cannot refund. cert downloadable - { - courseName: 'Entitlement Course, Learner finished and passed, Cannot refund, Cert downloadable', - enrollment: { - isVerified: true, - }, - courseRun: { - isArchived: true, - isStarted: true, - endDate: pastDate, - }, - entitlement: { - uuid: genEntitlementUUID(7), - availableSessions: null, - changeDeadline: pastDate, - enrollmentUrl: '/entitlement-enrollment', - isExpired: false, - isRefundable: false, - isFulfilled: true, - }, - certificate: { - isEarned: true, - isDownloadable: true, - availableDate: pastDate, - certPreviewUrl: bannerImgSrc, - }, - }, -]; -const creditCourses = [ - { - courseName: 'Credit - Eligible for credit from unknown provider', - credit: { - ...creditData, - providerName: null, - providerId: null, - }, - enrollment: { isEnrolled: true }, - }, - { - courseName: 'Credit - Eligible for credit from known provider', - credit: creditData, - enrollment: { isEnrolled: true }, - }, - { - courseName: 'Credit - Purchased but must request', - credit: { ...creditData, purchased: true }, - enrollment: { isEnrolled: true }, - }, - { - courseName: 'Credit - Credit Request Pending', - credit: { - ...creditData, - purchased: true, - requestStatus: creditVals.requestStatuses.pending, - }, - enrollment: { isEnrolled: true }, - }, - { - courseName: 'Credit - Credit Request Approved', - credit: { - ...creditData, - purchased: true, - requestStatus: creditVals.requestStatuses.approved, - }, - enrollment: { isEnrolled: true }, - }, - { - courseName: 'Credit - Credit Request Rejected, Error thrown', - credit: { - ...creditData, - purchased: true, - requestStatus: creditVals.requestStatuses.rejected, - error: true, - }, - enrollment: { isEnrolled: true }, - }, -]; - -export const courseRuns = [ - ...auditCourses, - ...verifiedCourses, - ...fulfilledEntitlementCourses, - ...creditCourses, -]; - -// unfulfilled entitlement select session -// unfulfilled entitlement select session with deadline -// unfulfilled entitlement select session pass deadline with available session {banner different from 4th} -// unfulfilled entitlement select session pass deadline without available session -export const entitlementCourses = [ - { - courseName: 'Unfulfilled Entitlement select session', - entitlement: { - uuid: genEntitlementUUID(10), - availableSessions, - changeDeadline: futureDate, - enrollmentUrl: '/entitlement-enrollment', - isExpired: false, - isFulfilled: false, - isRefundable: true, - }, - }, { - courseName: 'Unfulfilled Entitlement select session with upcoming deadline', - entitlement: { - uuid: genEntitlementUUID(11), - availableSessions, - changeDeadline: soonDateStr, - enrollmentUrl: '/entitlement-enrollment', - isExpired: false, - isFulfilled: false, - isRefundable: true, - }, - }, { - courseName: 'Unfulfilled Entitlement select session past deadline, With available session', - entitlement: { - uuid: genEntitlementUUID(12), - availableSessions, - changeDeadline: pastDate, - enrollmentUrl: '/entitlement-enrollment', - isExpired: false, - isFulfilled: false, - isRefundable: true, - }, - }, { - courseName: 'Unfulfilled Entitlement select session past deadline, With available no session', - entitlement: { - uuid: genEntitlementUUID(13), - availableSessions: [], - changeDeadline: pastDate, - enrollmentUrl: '/entitlement-enrollment', - isExpired: true, - isFulfilled: false, - isRefundable: true, - }, - }, -]; - -const providerOptions = [ - providers.edx, - providers.mit, - null, -]; - -const emailOptions = [ - { isEmailEnabled: false, hasOptedOutOfEmail: false }, - { isEmailEnabled: true, hasOptedOutOfEmail: false }, - { isEmailEnabled: true, hasOptedOutOfEmail: true }, -]; - -const programsOptions = [ - { relatedPrograms }, - { relatedPrograms: [relatedPrograms[0]] }, - { relatedPrograms: [] }, -]; - -const getOption = (options, index) => options[index % options.length]; - -export const compileCourseRunData = ({ courseName, ...data }, index) => { - const courseId = genCourseId(index); - const courseNumber = genCourseNumber(index); - const socialShareUrl = genCourseShareUrl(index); - const lastEnrolledDate = new Date(); - lastEnrolledDate.setDate(lastEnrolledDate.getDate() - index - 1); - const lastEnrolled = lastEnrolledDate.toISOString(); - const out = { - gradeData: { isPassing: true }, - entitlement: null, - credit: {}, - ...data, - certificate: genCertificateData(data.certificate), - enrollment: genEnrollmentData({ - lastEnrolled, - ...getOption(emailOptions, index), - ...data.enrollment, - }), - courseRun: genCourseRunData({ - ...data.courseRun, - courseId, - }), - course: { - courseName, - bannerImgSrc, - courseNumber, - socialShareUrl, - }, - courseProvider: getOption(providerOptions, index), - programs: getOption(programsOptions, index), - }; - return out; -}; - -export const compileEntitlementData = ({ courseName, ...data }, index) => { - const courseNumber = genCourseNumber(100 + index); - const socialShareUrl = genCourseShareUrl(100 + index); - return { - enrollment: genEnrollmentData({ - isEnrolled: false, - lastEnrolled: null, - accessExpirationDate: null, - canUpgrade: false, - hasStarted: false, - isAudit: false, - isAuditAccessExpired: false, - isEmailEnabled: false, - isVerified: false, - }), - gradeData: null, - certificate: null, - courseRun: null, - ...data, - course: { - courseName, - courseNumber, - bannerImgSrc, - socialShareUrl, - }, - courseProvider: getOption(providerOptions, index), - programs: getOption(programsOptions, index), - }; -}; - -// Entitlement Course - refundable -// Entitlement Course - cannot view yet -// Entitlement Course - can view and change -// Entitlement Course - expired -export const courseRunData = courseRuns.map(compileCourseRunData); -export const entitlementData = entitlementCourses.map(compileEntitlementData); - -export default { - compileEntitlementData, - compileCourseRunData, - courseRunData, - entitlementData, - globalData, -}; diff --git a/src/data/services/lms/fakeData/testUtils.js b/src/data/services/lms/fakeData/testUtils.js deleted file mode 100644 index c471ef6c6..000000000 --- a/src/data/services/lms/fakeData/testUtils.js +++ /dev/null @@ -1,40 +0,0 @@ -// import { StrictDict } from 'utils'; -import { - ErrorStatuses, - // RequestKeys, -} from 'data/constants/requests'; -import { actions } from 'data/redux'; - -export const errorData = (status, data = '') => ({ - response: { - status, - data, - }, -}); - -export const networkErrorData = errorData(ErrorStatuses.badRequest); - -export const genTestUtils = ({ - dispatch, -}) => { - /* - const mockStart = (requestKey) => () => { - dispatch(actions.requests.startRequest(requestKey)); - }; - */ - - const mockError = (requestKey, status, data) => () => { - dispatch(actions.requests.failRequest({ - requestKey, - error: errorData(status, data), - })); - }; - const mockNetworkError = (requestKey) => ( - mockError(requestKey, ErrorStatuses.badRequest) - ); - return { - mockNetworkError, - }; -}; - -export default genTestUtils; diff --git a/src/data/services/lms/index.js b/src/data/services/lms/index.js index c79aa5f06..fc90b81ed 100644 --- a/src/data/services/lms/index.js +++ b/src/data/services/lms/index.js @@ -1,4 +1,4 @@ -import { StrictDict } from 'utils'; +import { StrictDict } from '@src/utils'; import api from './api'; import urls from './urls'; diff --git a/src/data/services/lms/urls.js b/src/data/services/lms/urls.js index 30c2bbf1b..055d3e759 100644 --- a/src/data/services/lms/urls.js +++ b/src/data/services/lms/urls.js @@ -1,12 +1,11 @@ -import { StrictDict } from 'utils'; +import { appId } from '../../../constants'; +import { StrictDict } from '../../../utils'; -import { getConfig } from '@edx/frontend-platform'; +import { getAppConfig, getSiteConfig } from '@openedx/frontend-base'; -export const getEcommerceUrl = () => getConfig().ECOMMERCE_BASE_URL; +const getBaseUrl = () => getSiteConfig().lmsBaseUrl; -const getBaseUrl = () => getConfig().LMS_BASE_URL; - -export const getApiUrl = () => (`${getConfig().LMS_BASE_URL}/api`); +export const getApiUrl = () => (`${getSiteConfig().lmsBaseUrl}/api`); const getInitApiUrl = () => (`${getApiUrl()}/learner_home/init`); @@ -19,12 +18,17 @@ const entitlementEnrollment = (uuid) => `${getApiUrl()}/entitlements/v1/entitlem export const updateUrl = (base, url) => ((url == null || url.startsWith('http://') || url.startsWith('https://')) ? url : `${base}${url}`); export const baseAppUrl = (url) => updateUrl(getBaseUrl(), url); -export const learningMfeUrl = (url) => updateUrl(getConfig().LEARNING_BASE_URL, url); +export const learningMfeUrl = (url) => updateUrl(getAppConfig(appId).LEARNING_BASE_URL, url); // static view url const programsUrl = () => baseAppUrl('/dashboard/programs'); -export const creditPurchaseUrl = (courseId) => `${getEcommerceUrl()}/credit/checkout/${courseId}/`; +export const creditPurchaseUrl = (courseId) => { + const config = getAppConfig(appId); + return config.CREDIT_PURCHASE_URL + ? `${config.CREDIT_PURCHASE_URL}/${courseId}/` + : `${config.ECOMMERCE_BASE_URL}/credit/checkout/${courseId}/`; +}; export const creditRequestUrl = (providerId) => `${getApiUrl()}/credit/v1/providers/${providerId}/request/`; export default StrictDict({ diff --git a/src/data/services/lms/urls.test.js b/src/data/services/lms/urls.test.js index 8b4ae8b1a..60aa00b53 100644 --- a/src/data/services/lms/urls.test.js +++ b/src/data/services/lms/urls.test.js @@ -1,4 +1,5 @@ -import { getConfig } from '@edx/frontend-platform'; +import { getAppConfig, getSiteConfig } from '@openedx/frontend-base'; +import { appId } from '@src/constants'; import * as urls from './urls'; describe('urls', () => { @@ -10,7 +11,7 @@ describe('urls', () => { it('returns the url if it is relative', () => { const url = '/edx.org'; expect(urls.baseAppUrl(url)).toEqual( - `${getConfig().LMS_BASE_URL}${url}`, + `${getSiteConfig().lmsBaseUrl}${url}`, ); }); it('return null if url is null', () => { @@ -25,7 +26,7 @@ describe('urls', () => { it('returns the url if it is relative', () => { const url = '/edx.org'; expect(urls.learningMfeUrl(url)).toEqual( - `${getConfig().LEARNING_BASE_URL}${url}`, + `${getAppConfig(appId).LEARNING_BASE_URL}${url}`, ); }); it('return null if url is null', () => { @@ -38,6 +39,13 @@ describe('urls', () => { const url = urls.creditPurchaseUrl(courseId); expect(url).toEqual(expect.stringContaining(courseId)); }); + it('returns CREDIT_PURCHASE_URL if set, with courseId', () => { + const courseId = 'test-course-id'; + const config = getAppConfig(appId); + config.CREDIT_PURCHASE_URL = 'http://credit-purchase.example.com'; + const url = urls.creditPurchaseUrl(courseId); + expect(url).toBe(`http://credit-purchase.example.com/${courseId}/`); + }); }); describe('creditRequestUrl', () => { it('builds from api url and loads providerId', () => { diff --git a/src/data/services/lms/utils.js b/src/data/services/lms/utils.js index e5cbbc0f6..7a6130a50 100644 --- a/src/data/services/lms/utils.js +++ b/src/data/services/lms/utils.js @@ -1,4 +1,4 @@ -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { getAuthenticatedHttpClient } from '@openedx/frontend-base'; /** * stringify(query, existingQuery) diff --git a/src/data/services/lms/utils.test.js b/src/data/services/lms/utils.test.js index 7d78a0734..5697d6afd 100644 --- a/src/data/services/lms/utils.test.js +++ b/src/data/services/lms/utils.test.js @@ -1,7 +1,8 @@ -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { getAuthenticatedHttpClient } from '@openedx/frontend-base'; import * as utils from './utils'; -jest.mock('@edx/frontend-platform/auth', () => ({ +jest.mock('@openedx/frontend-base', () => ({ + ...jest.requireActual('@openedx/frontend-base'), getAuthenticatedHttpClient: jest.fn(), })); diff --git a/src/data/services/segment/utils.js b/src/data/services/segment/utils.js index 65f306423..e87393ac5 100755 --- a/src/data/services/segment/utils.js +++ b/src/data/services/segment/utils.js @@ -1,6 +1,5 @@ -/* eslint-disable import/prefer-default-export */ -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; -import { appName } from 'tracking/constants'; +import { sendTrackEvent } from '@openedx/frontend-base'; +import { appName } from '../../../tracking/constants'; export const LINK_TIMEOUT = 300; @@ -12,5 +11,7 @@ export const createEventTracker = (name, options = {}) => () => sendTrackEvent( export const createLinkTracker = (tracker, href) => (e) => { e.preventDefault(); tracker(); - return setTimeout(() => { global.location.href = href; }, LINK_TIMEOUT); + return setTimeout(() => { + global.location.href = href; + }, LINK_TIMEOUT); }; diff --git a/src/data/services/segment/utils.test.js b/src/data/services/segment/utils.test.js index 1a5f32b1c..965887e77 100644 --- a/src/data/services/segment/utils.test.js +++ b/src/data/services/segment/utils.test.js @@ -1,13 +1,14 @@ -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; +import { sendTrackEvent } from '@openedx/frontend-base'; -import { appName } from 'tracking/constants'; +import { appName } from '@src/tracking/constants'; import { createEventTracker, createLinkTracker, LINK_TIMEOUT } from './utils'; jest.useFakeTimers(); jest.spyOn(global, 'setTimeout'); -jest.mock('@edx/frontend-platform/analytics', () => ({ +jest.mock('@openedx/frontend-base', () => ({ + ...jest.requireActual('@openedx/frontend-base'), sendTrackEvent: jest.fn(), })); diff --git a/src/data/store.js b/src/data/store.js deleted file mode 100755 index 8df4af52e..000000000 --- a/src/data/store.js +++ /dev/null @@ -1,37 +0,0 @@ -import * as redux from 'redux'; -import thunkMiddleware from 'redux-thunk'; -import { - composeWithDevToolsLogOnlyInProduction, -} from '@redux-devtools/extension'; -import { createLogger } from 'redux-logger'; - -import apiTestUtils from 'data/services/lms/fakeData/testUtils'; - -import reducer, { actions, selectors } from './redux'; - -export const createStore = () => { - const loggerMiddleware = createLogger(); - - const middleware = [thunkMiddleware, loggerMiddleware]; - - const store = redux.createStore( - reducer, - composeWithDevToolsLogOnlyInProduction(redux.applyMiddleware(...middleware)), - ); - - /** - * Dev tools for redux work - */ - if (process.env.NODE_ENV === 'development') { - window.store = store; - window.actions = actions; - window.selectors = selectors; - window.apiTestUtils = apiTestUtils(store); - } - - return store; -}; - -const store = createStore(); - -export default store; diff --git a/src/data/store.test.js b/src/data/store.test.js deleted file mode 100644 index cd789bc4b..000000000 --- a/src/data/store.test.js +++ /dev/null @@ -1,68 +0,0 @@ -import { applyMiddleware } from 'redux'; -import thunkMiddleware from 'redux-thunk'; -import { - composeWithDevToolsLogOnlyInProduction, -} from '@redux-devtools/extension'; -import { createLogger } from 'redux-logger'; - -import rootReducer, { actions, selectors } from 'data/redux'; - -import exportedStore, { createStore } from './store'; - -jest.mock('data/redux', () => ({ - __esModule: true, - default: 'REDUCER', - actions: 'ACTIONS', - selectors: 'SELECTORS', -})); - -jest.mock('redux-logger', () => ({ - createLogger: () => 'logger', -})); -jest.mock('redux-thunk', () => 'thunkMiddleware'); -jest.mock('redux', () => ({ - applyMiddleware: (...middleware) => ({ applied: middleware }), - createStore: (reducer, middleware) => ({ reducer, middleware }), -})); -jest.mock('@redux-devtools/extension', () => ({ - composeWithDevToolsLogOnlyInProduction: (middleware) => ({ withDevTools: middleware }), -})); - -describe('store aggregator module', () => { - describe('exported store', () => { - it('is generated by createStore', () => { - expect(exportedStore).toEqual(createStore()); - }); - it('creates store with connected reducers', () => { - expect(createStore().reducer).toEqual(rootReducer); - }); - describe('middleware', () => { - it('exports thunk and logger middleware, composed and applied with dev tools', () => { - expect(createStore().middleware).toEqual( - composeWithDevToolsLogOnlyInProduction(applyMiddleware(thunkMiddleware, createLogger())), - ); - }); - }); - }); - describe('dev exposed tools', () => { - beforeEach(() => { - window.store = undefined; - window.actions = undefined; - window.selectors = undefined; - }); - it('exposes redux tools if in development env', () => { - process.env.NODE_ENV = 'development'; - const store = createStore(); - expect(window.store).toEqual(store); - expect(window.actions).toEqual(actions); - expect(window.selectors).toEqual(selectors); - }); - it('does not expose redux tools if in production env', () => { - process.env.NODE_ENV = 'production'; - createStore(); - expect(window.store).toEqual(undefined); - expect(window.actions).toEqual(undefined); - expect(window.selectors).toEqual(undefined); - }); - }); -}); diff --git a/src/data/utils.js b/src/data/utils.js deleted file mode 100644 index 40bcfc4b1..000000000 --- a/src/data/utils.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Simple selector factory. - * Takes a list of string keys, and returns a simple slector for each. - * - * @function - * @param {Object|string[]} keys - If passed as object, Object.keys(keys) is used. - * @return {Object} - object of `{[key]: ({key}) => key}` - */ -const simpleSelectorFactory = (transformer, keys) => { - const selKeys = Array.isArray(keys) ? keys : Object.keys(keys); - return selKeys.reduce( - (obj, key) => ({ - ...obj, [key]: (state) => transformer(state)[key], - }), - { root: (state) => transformer(state) }, - ); -}; - -export default simpleSelectorFactory; diff --git a/src/data/utils.test.js b/src/data/utils.test.js deleted file mode 100644 index fd870d259..000000000 --- a/src/data/utils.test.js +++ /dev/null @@ -1,29 +0,0 @@ -import simpleSelectorFactory from './utils'; - -describe('Redux utilities - creators', () => { - describe('simpleSelectors', () => { - const data = { a: 1, b: 2, c: 3 }; - const state = { - testGroup: data, - other: 'stuff', - }; - const transformer = ({ testGroup }) => testGroup; - - test('given a list of strings, returns a dict w/ a simple selector per string', () => { - const keys = ['a', 'b']; - const selectors = simpleSelectorFactory(transformer, keys); - expect(Object.keys(selectors)).toEqual(['root', ...keys]); - expect(selectors.root(state)).toEqual(data); - expect(selectors.a(state)).toEqual(data.a); - expect(selectors.b(state)).toEqual(data.b); - }); - test('given an object for keys, returns a dict w/ simple selector per key', () => { - const selectors = simpleSelectorFactory(transformer, data); - expect(Object.keys(selectors)).toEqual(['root', ...Object.keys(data)]); - expect(selectors.root(state)).toEqual(data); - expect(selectors.a(state)).toEqual(data.a); - expect(selectors.b(state)).toEqual(data.b); - expect(selectors.c(state)).toEqual(data.c); - }); - }); -}); diff --git a/src/hooks/api.js b/src/hooks/api.js deleted file mode 100644 index d64a11850..000000000 --- a/src/hooks/api.js +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; - -import { AppContext } from '@edx/frontend-platform/react'; - -import { RequestKeys } from 'data/constants/requests'; -import { post } from 'data/services/lms/utils'; -import api from 'data/services/lms/api'; - -import * as reduxHooks from 'data/redux/hooks'; -import * as module from './api'; - -const { useMakeNetworkRequest } = reduxHooks; - -export const useNetworkRequest = (action, args) => { - const makeNetworkRequest = useMakeNetworkRequest(); - return (...actionsArgs) => makeNetworkRequest({ - promise: action(...actionsArgs), - ...args, - }); -}; - -/** - * initialize the app, loading ora and course metadata from the api, and loading the initial - * submission list data. - */ -export const useInitializeApp = () => { - const loadData = reduxHooks.useLoadData(); - return module.useNetworkRequest(api.initializeList, { - requestKey: RequestKeys.initialize, - onSuccess: ({ data }) => loadData(data), - }); -}; - -export const useNewEntitlementEnrollment = (cardId) => { - const { uuid } = reduxHooks.useCardEntitlementData(cardId); - const onSuccess = module.useInitializeApp(); - return module.useNetworkRequest( - (selection) => api.updateEntitlementEnrollment({ uuid, courseId: selection }), - { onSuccess, requestKey: RequestKeys.newEntitlementEnrollment }, - ); -}; - -export const useSwitchEntitlementEnrollment = (cardId) => { - const { uuid } = reduxHooks.useCardEntitlementData(cardId); - const onSuccess = module.useInitializeApp(); - const action = (selection) => api.updateEntitlementEnrollment({ uuid, courseId: selection }); - return module.useNetworkRequest( - action, - { onSuccess, requestKey: RequestKeys.switchEntitlementSession }, - ); -}; - -export const useLeaveEntitlementSession = (cardId) => { - const { uuid, isRefundable } = reduxHooks.useCardEntitlementData(cardId); - const onSuccess = module.useInitializeApp(); - return module.useNetworkRequest( - () => api.deleteEntitlementEnrollment({ uuid, isRefundable }), - { onSuccess, requestKey: RequestKeys.leaveEntitlementSession }, - ); -}; - -export const useUnenrollFromCourse = (cardId) => { - const { courseId } = reduxHooks.useCardCourseRunData(cardId); - return module.useNetworkRequest( - () => api.unenrollFromCourse({ courseId }), - { requestKey: RequestKeys.unenrollFromCourse }, - ); -}; - -export const useMasqueradeAs = () => { - const loadData = reduxHooks.useLoadData(); - return module.useNetworkRequest( - (user) => api.initializeList({ user }), - { onSuccess: ({ data }) => loadData(data), requestKey: RequestKeys.masquerade }, - ); -}; - -export const useClearMasquerade = () => { - const clearRequest = reduxHooks.useClearRequest(); - const initializeApp = module.useInitializeApp(); - return () => { - clearRequest(RequestKeys.masquerade); - initializeApp(); - }; -}; - -export const useUpdateEmailSettings = (cardId) => { - const { courseId } = reduxHooks.useCardCourseRunData(cardId); - return module.useNetworkRequest( - (enable) => api.updateEmailSettings({ courseId, enable }), - { requestKey: RequestKeys.updateEmailSettings }, - ); -}; - -export const useSendConfirmEmail = () => { - const { sendEmailUrl } = reduxHooks.useEmailConfirmationData(); - return () => post(sendEmailUrl); -}; - -export const useCreateCreditRequest = (cardId) => { - const { providerId } = reduxHooks.useCardCreditData(cardId); - const { authenticatedUser: { username } } = React.useContext(AppContext); - const { courseId } = reduxHooks.useCardCourseRunData(cardId); - return () => api.createCreditRequest({ providerId, courseId, username }); -}; diff --git a/src/hooks/api.test.js b/src/hooks/api.test.js deleted file mode 100644 index cd1a5b6b7..000000000 --- a/src/hooks/api.test.js +++ /dev/null @@ -1,271 +0,0 @@ -import React from 'react'; -import { AppContext } from '@edx/frontend-platform/react'; -import { keyStore } from 'utils'; -import { RequestKeys } from 'data/constants/requests'; -import { post } from 'data/services/lms/utils'; -import api from 'data/services/lms/api'; - -import * as reduxHooks from 'data/redux/hooks'; -import * as apiHooks from './api'; - -const reduxKeys = keyStore(reduxHooks); - -jest.mock('data/services/lms/utils', () => ({ - post: jest.fn((...args) => ({ post: args })), -})); -jest.mock('data/services/lms/api', () => ({ - initializeList: jest.fn(), - updateEntitlementEnrollment: jest.fn(), - unenrollFromCourse: jest.fn(), - deleteEntitlementEnrollment: jest.fn(), - updateEmailSettings: jest.fn(), - createCreditRequest: jest.fn(), -})); -jest.mock('data/redux/hooks', () => ({ - useCardCourseRunData: jest.fn(), - useCardCreditData: jest.fn(), - useCardEntitlementData: jest.fn(), - useLoadData: jest.fn(), - useMakeNetworkRequest: jest.fn(), - useClearRequest: jest.fn(), - useEmailConfirmationData: jest.fn(), -})); - -const moduleKeys = keyStore(apiHooks); -const testString = 'TEST-string'; -const uuid = 'test-UUID'; -const cardId = 'test-card-id'; -const selection = 'test-selection'; -const courseId = 'test-COURSE-id'; -const isRefundable = 'test-is-refundable'; -const user = 'test-user'; - -const loadData = jest.fn(); -reduxHooks.useLoadData.mockReturnValue(loadData); -const clearRequest = jest.fn(); -reduxHooks.useClearRequest.mockReturnValue(clearRequest); - -reduxHooks.useCardCourseRunData.mockReturnValue({ courseId }); -reduxHooks.useCardEntitlementData.mockReturnValue({ uuid, isRefundable }); - -let hook; -let out; - -const testInitCardHook = (hookKey) => { - test(`initializes ${hookKey} with cardId`, () => { - expect(reduxHooks[hookKey]).toHaveBeenCalledWith(cardId); - }); -}; - -const initializeApp = jest.fn(); - -describe('api hooks', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('useNetworkRequest', () => { - const makeNetworkRequest = jest.fn(args => ({ networkRequest: args })); - const action = jest.fn((...actionArgs) => ({ action: actionArgs })); - const args = { some: 'test', args: 'for you' }; - it('returns network request based on incoming action', () => { - reduxHooks.useMakeNetworkRequest.mockReturnValue(makeNetworkRequest); - hook = apiHooks.useNetworkRequest(action, args); - expect(hook()).toEqual(makeNetworkRequest({ promise: action(), ...args })); - }); - it('forwards action arguments', () => { - reduxHooks.useMakeNetworkRequest.mockReturnValue(makeNetworkRequest); - hook = apiHooks.useNetworkRequest(action, args); - const actionArgs = [1, 2, 3]; - expect(hook(...actionArgs)).toEqual( - makeNetworkRequest({ promise: action(...actionArgs), ...args }), - ); - }); - }); - - describe('network requests', () => { - const mockUseNetworkRequest = jest.fn((action, args) => ({ action, args })); - const testRequestKey = (requestKey) => { - test('requestKey', () => { expect(hook.args.requestKey).toEqual(requestKey); }); - }; - - beforeEach(() => { - jest.spyOn(apiHooks, moduleKeys.useNetworkRequest).mockImplementation(mockUseNetworkRequest); - }); - - describe('useInitializeApp', () => { - beforeEach(() => { - hook = apiHooks.useInitializeApp(); - }); - it('calls initialize api method', () => { - expect(hook.action).toEqual(api.initializeList); - }); - testRequestKey(RequestKeys.initialize); - it('initializes load data hook', () => { - expect(reduxHooks.useLoadData).toHaveBeenCalledWith(); - }); - it('calls loadData with data on success', () => { - hook.args.onSuccess({ data: testString }); - expect(loadData).toHaveBeenCalledWith(testString); - }); - }); - - describe('entitlement enrollment hooks', () => { - beforeEach(() => { - jest.spyOn(apiHooks, moduleKeys.useInitializeApp).mockReturnValue(initializeApp); - }); - - const testInitialization = () => { - it('initializes useInitializeApp', () => { - expect(apiHooks.useInitializeApp).toHaveBeenCalledWith(); - }); - testInitCardHook(reduxKeys.useCardEntitlementData); - }; - - const testArgs = (requestKey) => { - testRequestKey(requestKey); - it('initializes app on success', () => { - expect(hook.args.onSuccess).toEqual(initializeApp); - }); - }; - - describe('useNewEntitlementEnrollment', () => { - beforeEach(() => { - hook = apiHooks.useNewEntitlementEnrollment(cardId); - }); - testInitialization(); - testArgs(RequestKeys.newEntitlementEnrollment); - it('calls updateEntitlementEnrollment api method', () => { - hook.action(selection); - expect(api.updateEntitlementEnrollment) - .toHaveBeenCalledWith({ uuid, courseId: selection }); - }); - }); - - describe('useSwitchEntitlementEnrollment', () => { - beforeEach(() => { - hook = apiHooks.useSwitchEntitlementEnrollment(cardId); - }); - testInitialization(); - testArgs(RequestKeys.switchEntitlementSession); - it('calls updateEntitlementEnrollment api method', () => { - hook.action(selection); - expect(api.updateEntitlementEnrollment) - .toHaveBeenCalledWith({ uuid, courseId: selection }); - }); - }); - - describe('useLeaveEntitlementSession', () => { - beforeEach(() => { - hook = apiHooks.useLeaveEntitlementSession(cardId); - }); - testInitialization(); - testArgs(RequestKeys.leaveEntitlementSession); - it('calls updateEntitlementEnrollment api method', () => { - hook.action(); - expect(api.deleteEntitlementEnrollment) - .toHaveBeenCalledWith({ uuid, isRefundable }); - }); - }); - }); - - describe('useUnenrollFromCourse', () => { - beforeEach(() => { - hook = apiHooks.useUnenrollFromCourse(cardId); - }); - testInitCardHook(reduxKeys.useCardCourseRunData); - testRequestKey(RequestKeys.unenrollFromCourse); - it('calls unenrollFromCourse api method with courseId', () => { - hook.action(); - expect(api.unenrollFromCourse).toHaveBeenCalledWith({ courseId }); - }); - }); - - describe('useMasqueradeAs', () => { - beforeEach(() => { - hook = apiHooks.useMasqueradeAs(cardId); - }); - it('initializes load data hook', () => { - expect(reduxHooks.useLoadData).toHaveBeenCalledWith(); - }); - testRequestKey(RequestKeys.masquerade); - it('calls initializeList api method', () => { - hook.action(user); - expect(api.initializeList).toHaveBeenCalledWith({ user }); - }); - it('loads data on success', () => { - hook.args.onSuccess({ data: testString }); - expect(loadData).toHaveBeenCalledWith(testString); - }); - }); - - describe('useClearMasquerade', () => { - beforeEach(() => { - jest.spyOn(apiHooks, moduleKeys.useInitializeApp).mockReturnValue(initializeApp); - hook = apiHooks.useClearMasquerade(cardId); - }); - it('initializes clear request redux hook', () => { - expect(reduxHooks.useClearRequest).toHaveBeenCalledWith(); - }); - it('initializes useInitializeApp hook', () => { - expect(apiHooks.useInitializeApp).toHaveBeenCalledWith(); - }); - it('clears masquerade state and initializes app on call', () => { - hook(); - expect(clearRequest).toHaveBeenCalledWith(RequestKeys.masquerade); - expect(initializeApp).toHaveBeenCalledWith(); - }); - }); - - describe('useUpdateEmailSettings', () => { - const enable = 'test-enable'; - beforeEach(() => { - hook = apiHooks.useUpdateEmailSettings(cardId); - }); - testInitCardHook(reduxKeys.useCardCourseRunData); - testRequestKey(RequestKeys.updateEmailSettings); - it('calls updateEmailSettings api method on call', () => { - hook.action(enable); - expect(api.updateEmailSettings).toHaveBeenCalledWith({ courseId, enable }); - }); - }); - - describe('useSendConfirmEmail', () => { - const sendEmailUrl = 'test-send-email-url'; - beforeEach(() => { - reduxHooks.useEmailConfirmationData.mockReturnValue({ sendEmailUrl }); - hook = apiHooks.useSendConfirmEmail(cardId); - out = hook(); - }); - it('initializes useEmailConfirmationData hook', () => { - expect(reduxHooks.useEmailConfirmationData).toHaveBeenCalledWith(); - }); - it('posts to email url on call', () => { - expect(out).toEqual(post(sendEmailUrl)); - }); - }); - - describe('useCreateCreditRequest', () => { - const username = 'test-username'; - const providerId = 'test-provider-id'; - beforeEach(() => { - React.useContext.mockReturnValue({ authenticatedUser: { username } }); - reduxHooks.useCardCreditData.mockReturnValue({ providerId }); - hook = apiHooks.useCreateCreditRequest(cardId); - }); - testInitCardHook(reduxKeys.useCardCreditData); - testInitCardHook(reduxKeys.useCardCourseRunData); - it('initializes username from app context', () => { - expect(React.useContext).toHaveBeenCalledWith(AppContext); - }); - it('calls createCreditRequest api method on call', () => { - out = hook(); - expect(api.createCreditRequest).toHaveBeenCalledWith({ - providerId, - courseId, - username, - }); - }); - }); - }); -}); diff --git a/src/hooks/index.js b/src/hooks/index.js index 402616026..243b6120c 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -1,7 +1,11 @@ -import * as redux from 'data/redux/hooks'; -import * as api from './api'; import * as utils from './utils'; +import useCourseData from './useCourseData'; +import useCourseTrackingEvent from './useCourseTrackingEvent'; +import useEntitlementInfo from './useEntitlementInfo'; +import useIsMasquerading from './useIsMasquerading'; -export const reduxHooks = redux; -export const apiHooks = api; export const utilHooks = utils; +export { useCourseData }; +export { useCourseTrackingEvent }; +export { useEntitlementInfo }; +export { useIsMasquerading }; diff --git a/src/hooks/useCourseData.test.tsx b/src/hooks/useCourseData.test.tsx new file mode 100644 index 000000000..fd3982cc4 --- /dev/null +++ b/src/hooks/useCourseData.test.tsx @@ -0,0 +1,150 @@ +import { renderHook } from '@testing-library/react'; +import { useInitializeLearnerHome } from '@src/data/hooks'; +import useCourseData from './useCourseData'; + +jest.mock('@src/data/hooks'); + +const mockUseInitializeLearnerHome = useInitializeLearnerHome as jest.MockedFunction; + +describe('useCourseData', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const mockCoursesByCardId = { + 'course-1': { + cardId: 'course-1', + courseName: 'Introduction to React', + courseNumber: 'CS101', + enrollment: { isEnrolled: true }, + }, + 'course-2': { + cardId: 'course-2', + courseName: 'Advanced JavaScript', + courseNumber: 'CS201', + enrollment: { isEnrolled: true }, + }, + 'course-3': { + cardId: 'course-3', + courseName: 'Data Structures', + courseNumber: 'CS301', + enrollment: { isEnrolled: false }, + }, + }; + + describe('successful data retrieval', () => { + beforeEach(() => { + mockUseInitializeLearnerHome.mockReturnValue({ + data: { courses: [], coursesByCardId: mockCoursesByCardId }, + isLoading: false, + isError: false, + error: null, + } as any); + }); + + it('should return correct course data for existing cardId', () => { + const { result } = renderHook(() => useCourseData('course-1')); + expect(result.current).toEqual(mockCoursesByCardId['course-1']); + }); + + it('should return correct course data for different cardId', () => { + const { result } = renderHook(() => useCourseData('course-2')); + expect(result.current).toEqual(mockCoursesByCardId['course-2']); + }); + + it('should return undefined for non-existing cardId', () => { + const { result } = renderHook(() => useCourseData('non-existing-course')); + expect(result.current).toBeUndefined(); + }); + }); + + describe('no data scenarios', () => { + it('should handle undefined data gracefully', () => { + mockUseInitializeLearnerHome.mockReturnValue({ + data: undefined, + isLoading: false, + isError: false, + error: null, + } as any); + + const { result } = renderHook(() => useCourseData('course-1')); + expect(result.current).toBeUndefined(); + }); + + it('should handle null data gracefully', () => { + mockUseInitializeLearnerHome.mockReturnValue({ + data: null, + isLoading: false, + isError: false, + error: null, + } as any); + + const { result } = renderHook(() => useCourseData('course-1')); + expect(result.current).toBeUndefined(); + }); + + it('should handle missing coursesByCardId property', () => { + mockUseInitializeLearnerHome.mockReturnValue({ + data: { courses: [] }, + isLoading: false, + isError: false, + error: null, + } as any); + + const { result } = renderHook(() => useCourseData('course-1')); + expect(result.current).toBeUndefined(); + }); + }); + + describe('loading and error states', () => { + it('should handle loading state', () => { + mockUseInitializeLearnerHome.mockReturnValue({ + data: undefined, + isLoading: true, + isError: false, + error: null, + } as any); + + const { result } = renderHook(() => useCourseData('course-1')); + expect(result.current).toBeUndefined(); + }); + + it('should handle error state', () => { + mockUseInitializeLearnerHome.mockReturnValue({ + data: undefined, + isLoading: false, + isError: true, + error: new Error('API Error'), + } as any); + + const { result } = renderHook(() => useCourseData('course-1')); + expect(result.current).toBeUndefined(); + }); + }); + + describe('edge cases', () => { + beforeEach(() => { + mockUseInitializeLearnerHome.mockReturnValue({ + data: { courses: [], coursesByCardId: mockCoursesByCardId }, + isLoading: false, + isError: false, + error: null, + } as any); + }); + + it('should handle empty string cardId', () => { + const { result } = renderHook(() => useCourseData('')); + expect(result.current).toBeUndefined(); + }); + + it('should handle null cardId', () => { + const { result } = renderHook(() => useCourseData(null as any)); + expect(result.current).toBeUndefined(); + }); + + it('should handle undefined cardId', () => { + const { result } = renderHook(() => useCourseData(undefined as any)); + expect(result.current).toBeUndefined(); + }); + }); +}); diff --git a/src/hooks/useCourseData.ts b/src/hooks/useCourseData.ts new file mode 100644 index 000000000..0679ae497 --- /dev/null +++ b/src/hooks/useCourseData.ts @@ -0,0 +1,8 @@ +import { useInitializeLearnerHome } from '@src/data/hooks'; + +const useCourseData = (cardId: string) => { + const { data } = useInitializeLearnerHome(); + return data?.coursesByCardId?.[cardId]; +}; + +export default useCourseData; diff --git a/src/hooks/useCourseTrackingEvent.test.tsx b/src/hooks/useCourseTrackingEvent.test.tsx new file mode 100644 index 000000000..f8adb15b7 --- /dev/null +++ b/src/hooks/useCourseTrackingEvent.test.tsx @@ -0,0 +1,389 @@ +import { renderHook } from '@testing-library/react'; +import useCourseData from './useCourseData'; +import useCourseTrackingEvent from './useCourseTrackingEvent'; + +jest.mock('./useCourseData'); + +const mockUseCourseData = useCourseData as jest.MockedFunction; + +describe('useCourseTrackingEvent', () => { + const mockTracker = jest.fn(); + const mockEvent = new Event('click'); + const testCardId = 'test-card-id'; + + beforeEach(() => { + jest.clearAllMocks(); + mockTracker.mockImplementation(() => jest.fn()); + }); + + describe('successful tracking scenarios', () => { + it('should call tracker with courseId when course data exists', () => { + const mockCourseData = { + courseRun: { + courseId: 'course-v1:TestX+CS101+2024', + }, + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, testCardId)); + + const trackingHandler = result.current; + trackingHandler(mockEvent); + + expect(mockUseCourseData).toHaveBeenCalledWith(testCardId); + expect(mockTracker).toHaveBeenCalledWith('course-v1:TestX+CS101+2024'); + }); + + it('should call tracker with courseId and additional arguments', () => { + const mockCourseData = { + courseRun: { + courseId: 'course-v1:TestX+CS201+2024', + }, + }; + + const additionalArgs = ['arg1', 'arg2', { option: 'value' }]; + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, testCardId, ...additionalArgs)); + + const trackingHandler = result.current; + trackingHandler(mockEvent); + + expect(mockTracker).toHaveBeenCalledWith( + 'course-v1:TestX+CS201+2024', + 'arg1', + 'arg2', + { option: 'value' }, + ); + }); + + it('should call the returned tracking function with the event', () => { + const mockTrackingFunction = jest.fn(); + mockTracker.mockReturnValue(mockTrackingFunction); + + const mockCourseData = { + courseRun: { + courseId: 'course-v1:TestX+CS301+2024', + }, + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, testCardId)); + + const trackingHandler = result.current; + trackingHandler(mockEvent); + + expect(mockTrackingFunction).toHaveBeenCalledWith(mockEvent); + }); + + it('should work with different event types', () => { + const mockTrackingFunction = jest.fn(); + mockTracker.mockReturnValue(mockTrackingFunction); + + const mockCourseData = { + courseRun: { + courseId: 'course-v1:TestX+CS401+2024', + }, + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, testCardId)); + + const trackingHandler = result.current; + + const clickEvent = new Event('click'); + const mouseEvent = new MouseEvent('mouseover'); + const customEvent = new CustomEvent('custom'); + + trackingHandler(clickEvent); + trackingHandler(mouseEvent); + trackingHandler(customEvent); + + expect(mockTrackingFunction).toHaveBeenCalledTimes(3); + expect(mockTrackingFunction).toHaveBeenNthCalledWith(1, clickEvent); + expect(mockTrackingFunction).toHaveBeenNthCalledWith(2, mouseEvent); + expect(mockTrackingFunction).toHaveBeenNthCalledWith(3, customEvent); + }); + }); + + describe('no courseId scenarios', () => { + it('should not call tracker when courseData is null', () => { + mockUseCourseData.mockReturnValue(null); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, testCardId)); + + const trackingHandler = result.current; + trackingHandler(mockEvent); + + expect(mockUseCourseData).toHaveBeenCalledWith(testCardId); + expect(mockTracker).not.toHaveBeenCalled(); + }); + + it('should not call tracker when courseData is undefined', () => { + mockUseCourseData.mockReturnValue(undefined); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, testCardId)); + + const trackingHandler = result.current; + trackingHandler(mockEvent); + + expect(mockUseCourseData).toHaveBeenCalledWith(testCardId); + expect(mockTracker).not.toHaveBeenCalled(); + }); + + it('should not call tracker when courseRun is missing', () => { + const mockCourseData = { + courseName: 'Test Course', + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, testCardId)); + + const trackingHandler = result.current; + trackingHandler(mockEvent); + + expect(mockTracker).not.toHaveBeenCalled(); + }); + + it('should not call tracker when courseId is missing from courseRun', () => { + const mockCourseData = { + courseRun: { + courseName: 'Test Course', + }, + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, testCardId)); + + const trackingHandler = result.current; + trackingHandler(mockEvent); + + expect(mockTracker).not.toHaveBeenCalled(); + }); + + it('should not call tracker when courseId is empty string', () => { + const mockCourseData = { + courseRun: { + courseId: '', + }, + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, testCardId)); + + const trackingHandler = result.current; + trackingHandler(mockEvent); + + expect(mockTracker).not.toHaveBeenCalled(); + }); + + it('should not call tracker when courseId is null', () => { + const mockCourseData = { + courseRun: { + courseId: null, + }, + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, testCardId)); + + const trackingHandler = result.current; + trackingHandler(mockEvent); + + expect(mockTracker).not.toHaveBeenCalled(); + }); + }); + + describe('cardId variations', () => { + it('should work with different cardId values', () => { + const mockCourseData = { + courseRun: { + courseId: 'course-v1:TestX+CS501+2024', + }, + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const cardIds = ['card-1', 'card-2', 'another-card-id']; + + cardIds.forEach(cardId => { + mockUseCourseData.mockClear(); + mockTracker.mockClear(); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, cardId)); + + const trackingHandler = result.current; + trackingHandler(mockEvent); + + expect(mockUseCourseData).toHaveBeenCalledWith(cardId); + expect(mockTracker).toHaveBeenCalledWith('course-v1:TestX+CS501+2024'); + }); + }); + + it('should handle empty cardId', () => { + mockUseCourseData.mockReturnValue(null); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, '')); + + const trackingHandler = result.current; + trackingHandler(mockEvent); + + expect(mockUseCourseData).toHaveBeenCalledWith(''); + expect(mockTracker).not.toHaveBeenCalled(); + }); + }); + + describe('hook behavior and memoization', () => { + it('should return a function', () => { + const mockCourseData = { + courseRun: { + courseId: 'course-v1:TestX+CS601+2024', + }, + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, testCardId)); + + expect(typeof result.current).toBe('function'); + }); + + it('should create new function when dependencies change', () => { + const mockCourseData = { + courseRun: { + courseId: 'course-v1:TestX+CS701+2024', + }, + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result, rerender } = renderHook( + ({ cardId, tracker }) => useCourseTrackingEvent(tracker, cardId), + { + initialProps: { + cardId: testCardId, + tracker: mockTracker, + }, + }, + ); + + const firstHandler = result.current; + + rerender({ cardId: 'different-card-id', tracker: mockTracker }); + + expect(result.current).not.toBe(firstHandler); + }); + }); + + describe('error handling', () => { + it('should handle when tracker throws an error', () => { + const errorTracker = jest.fn().mockImplementation(() => { + throw new Error('Tracker error'); + }); + + const mockCourseData = { + courseRun: { + courseId: 'course-v1:TestX+CS801+2024', + }, + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result } = renderHook(() => useCourseTrackingEvent(errorTracker, testCardId)); + + const trackingHandler = result.current; + + expect(() => trackingHandler(mockEvent)).toThrow('Tracker error'); + }); + + it('should handle when tracker returns null', () => { + const nullTracker = jest.fn().mockReturnValue(null); + + const mockCourseData = { + courseRun: { + courseId: 'course-v1:TestX+CS901+2024', + }, + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result } = renderHook(() => useCourseTrackingEvent(nullTracker, testCardId)); + + const trackingHandler = result.current; + + expect(() => trackingHandler(mockEvent)).toThrow(); + }); + + it('should handle when useCourseData throws an error', () => { + mockUseCourseData.mockImplementation(() => { + throw new Error('Course data error'); + }); + + expect(() => { + renderHook(() => useCourseTrackingEvent(mockTracker, testCardId)); + }).toThrow('Course data error'); + }); + }); + + describe('integration scenarios', () => { + it('should work with realistic tracking scenario', () => { + const realTracker = jest.fn((courseId, action, category) => jest.fn(() => { + console.log(`Tracking ${action} for ${courseId} in ${category}`); + })); + + const mockCourseData = { + courseRun: { + courseId: 'course-v1:MITx+6.00.1x+2024', + courseName: 'Introduction to Computer Science', + }, + enrollment: { + isActive: true, + }, + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result } = renderHook(() => useCourseTrackingEvent(realTracker, 'mit-cs-card', 'click', 'course-card')); + + const trackingHandler = result.current; + trackingHandler(mockEvent); + + expect(realTracker).toHaveBeenCalledWith( + 'course-v1:MITx+6.00.1x+2024', + 'click', + 'course-card', + ); + }); + + it('should work with multiple tracking calls', () => { + const mockTrackingFunction = jest.fn(); + mockTracker.mockReturnValue(mockTrackingFunction); + + const mockCourseData = { + courseRun: { + courseId: 'course-v1:TestX+CS1001+2024', + }, + }; + + mockUseCourseData.mockReturnValue(mockCourseData); + + const { result } = renderHook(() => useCourseTrackingEvent(mockTracker, testCardId)); + + const trackingHandler = result.current; + + trackingHandler(mockEvent); + trackingHandler(mockEvent); + trackingHandler(mockEvent); + + expect(mockTracker).toHaveBeenCalledTimes(3); + expect(mockTrackingFunction).toHaveBeenCalledTimes(3); + }); + }); +}); diff --git a/src/hooks/useCourseTrackingEvent.ts b/src/hooks/useCourseTrackingEvent.ts new file mode 100644 index 000000000..32363456e --- /dev/null +++ b/src/hooks/useCourseTrackingEvent.ts @@ -0,0 +1,14 @@ +import useCourseData from './useCourseData'; + +const useCourseTrackingEvent = (tracker: (...a: any[]) => any, cardId: string, ...args: any[]) => { + const courseData = useCourseData(cardId); + const courseId = (courseData as any)?.courseRun?.courseId; + + return (e: Event) => { + if (courseId) { + tracker(courseId, ...args)(e); + } + }; +}; + +export default useCourseTrackingEvent; diff --git a/src/hooks/useEntitlementInfo.test.tsx b/src/hooks/useEntitlementInfo.test.tsx new file mode 100644 index 000000000..02587ac51 --- /dev/null +++ b/src/hooks/useEntitlementInfo.test.tsx @@ -0,0 +1,534 @@ +import { renderHook } from '@testing-library/react'; +import useEntitlementInfo from './useEntitlementInfo'; + +describe('useEntitlementInfo', () => { + const today = new Date(); + const pastDate = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000); // 30 days ago + const futureDate = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000); // 30 days from now + const farFutureDate = new Date(today.getTime() + 200 * 24 * 60 * 60 * 1000); // 200 days from now + + const mockAvailableSessions = [ + { + sessionId: 'session-1', + courseName: 'Session 1', + startDate: '2024-03-01', + }, + { + sessionId: 'session-2', + courseName: 'Session 2', + startDate: '2024-06-01', + }, + ]; + + describe('non-entitlement scenarios', () => { + it('should return isEntitlement: false when entitlement is null', () => { + const courseData = { + entitlement: null, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current).toEqual({ + isEntitlement: false, + }); + }); + + it('should return isEntitlement: false when entitlement is undefined', () => { + const courseData = { + entitlement: undefined, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current).toEqual({ + isEntitlement: false, + }); + }); + + it('should return isEntitlement: false when entitlement is empty object', () => { + const courseData = { + entitlement: {}, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current).toEqual({ + isEntitlement: false, + }); + }); + + it('should return isEntitlement: false when entitlement.isEntitlement is false', () => { + const courseData = { + entitlement: { + isEntitlement: false, + uuid: 'test-uuid', + changeDeadline: futureDate.toISOString(), + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current).toEqual({ + isEntitlement: false, + }); + }); + + it('should return isEntitlement: false when courseData is null', () => { + const { result } = renderHook(() => useEntitlementInfo(null)); + + expect(result.current).toEqual({ + isEntitlement: false, + }); + }); + + it('should return isEntitlement: false when courseData is undefined', () => { + const { result } = renderHook(() => useEntitlementInfo(undefined)); + + expect(result.current).toEqual({ + isEntitlement: false, + }); + }); + }); + + describe('valid entitlement scenarios', () => { + const baseEntitlementData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid-123', + changeDeadline: futureDate.toISOString(), + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + it('should return complete entitlement info for valid entitlement', () => { + const { result } = renderHook(() => useEntitlementInfo(baseEntitlementData)); + + expect(result.current).toMatchObject({ + isEntitlement: true, + availableSessions: mockAvailableSessions, + changeDeadline: expect.any(Date), + isExpired: false, + isFulfilled: false, + uuid: 'test-uuid-123', + hasSessions: true, + canChange: true, + showExpirationWarning: true, + }); + }); + + it('should correctly parse changeDeadline as Date object', () => { + const { result } = renderHook(() => useEntitlementInfo(baseEntitlementData)); + + expect(result.current.changeDeadline).toBeInstanceOf(Date); + expect(result.current.changeDeadline?.getTime()).toBe(futureDate.getTime()); + }); + + it('should return hasSessions: true when availableSessions has items', () => { + const { result } = renderHook(() => useEntitlementInfo(baseEntitlementData)); + + expect(result.current.hasSessions).toBe(true); + }); + + it('should return hasSessions: false when availableSessions is empty', () => { + const courseData = { + entitlement: { + ...baseEntitlementData.entitlement, + availableSessions: [], + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.hasSessions).toBe(false); + }); + + it('should return hasSessions: false when availableSessions is null', () => { + const courseData = { + entitlement: { + ...baseEntitlementData.entitlement, + availableSessions: null, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.hasSessions).toBe(false); + }); + + it('should return hasSessions: false when availableSessions is undefined', () => { + const courseData = { + entitlement: { + ...baseEntitlementData.entitlement, + availableSessions: undefined, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.hasSessions).toBe(false); + }); + }); + + describe('deadline and expiration logic', () => { + it('should return canChange: false when deadline has passed', () => { + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: pastDate.toISOString(), + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.canChange).toBe(false); + }); + + it('should return canChange: true when deadline has not passed', () => { + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: futureDate.toISOString(), + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.canChange).toBe(true); + }); + + it('should return showExpirationWarning: false when already fulfilled', () => { + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: futureDate.toISOString(), + isExpired: false, + isFulfilled: true, + availableSessions: mockAvailableSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.showExpirationWarning).toBe(false); + }); + + it('should return showExpirationWarning: false when deadline has passed', () => { + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: pastDate.toISOString(), + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.showExpirationWarning).toBe(false); + }); + + it('should return showExpirationWarning: false when deadline is more than 6 months away', () => { + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: farFutureDate.toISOString(), + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.showExpirationWarning).toBe(false); + }); + + it('should return showExpirationWarning: true when conditions are met', () => { + const withinSixMonths = new Date(today.getTime() + 90 * 24 * 60 * 60 * 1000); // 90 days from now + + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: withinSixMonths.toISOString(), + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.showExpirationWarning).toBe(true); + }); + }); + + describe('entitlement properties passthrough', () => { + it('should pass through isExpired property', () => { + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: futureDate.toISOString(), + isExpired: true, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.isExpired).toBe(true); + }); + + it('should pass through isFulfilled property', () => { + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: futureDate.toISOString(), + isExpired: false, + isFulfilled: true, + availableSessions: mockAvailableSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.isFulfilled).toBe(true); + }); + + it('should pass through uuid property', () => { + const testUuid = 'unique-entitlement-uuid-456'; + const courseData = { + entitlement: { + isEntitlement: true, + uuid: testUuid, + changeDeadline: futureDate.toISOString(), + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.uuid).toBe(testUuid); + }); + + it('should pass through availableSessions property', () => { + const customSessions = [ + { sessionId: 'custom-1', courseName: 'Custom Session' }, + ]; + + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: futureDate.toISOString(), + isExpired: false, + isFulfilled: false, + availableSessions: customSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.availableSessions).toEqual(customSessions); + }); + }); + + describe('memoization behavior', () => { + it('should memoize result when courseData does not change', () => { + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: futureDate.toISOString(), + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + const { result, rerender } = renderHook( + ({ data }) => useEntitlementInfo(data), + { initialProps: { data: courseData } }, + ); + + const firstResult = result.current; + + rerender({ data: courseData }); + + expect(result.current).toBe(firstResult); + }); + + it('should recalculate when courseData changes', () => { + const courseData1 = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid-1', + changeDeadline: futureDate.toISOString(), + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + const courseData2 = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid-2', + changeDeadline: futureDate.toISOString(), + isExpired: false, + isFulfilled: true, + availableSessions: mockAvailableSessions, + }, + }; + + const { result, rerender } = renderHook( + ({ data }) => useEntitlementInfo(data), + { initialProps: { data: courseData1 } }, + ); + + const firstResult = result.current; + + rerender({ data: courseData2 }); + + expect(result.current).not.toBe(firstResult); + expect(result.current.uuid).toBe('test-uuid-2'); + expect(result.current.isFulfilled).toBe(true); + }); + }); + + describe('edge cases and error handling', () => { + it('should handle invalid date strings gracefully', () => { + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: 'invalid-date-string', + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.changeDeadline).toBeInstanceOf(Date); + }); + + it('should handle missing changeDeadline property', () => { + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.changeDeadline).toBeInstanceOf(Date); + }); + + it('should handle entitlement with missing optional properties', () => { + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: futureDate.toISOString(), + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.isEntitlement).toBe(true); + expect(result.current.uuid).toBe('test-uuid'); + expect(result.current.isExpired).toBeUndefined(); + expect(result.current.isFulfilled).toBeUndefined(); + expect(result.current.availableSessions).toBeUndefined(); + expect(result.current.hasSessions).toBe(false); + }); + + it('should handle courseData with extra properties', () => { + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: futureDate.toISOString(), + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + course: { + courseName: 'Extra Course Info', + }, + enrollment: { + isActive: true, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.isEntitlement).toBe(true); + expect(result.current.uuid).toBe('test-uuid'); + }); + }); + + describe('boundary date calculations', () => { + it('should correctly calculate 6-month boundary for showExpirationWarning', () => { + const exactly180Days = new Date(); + exactly180Days.setDate(exactly180Days.getDate() + 180); + + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: exactly180Days.toISOString(), + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + + expect(result.current.showExpirationWarning).toBe(true); + }); + + it('should handle date exactly at deadline boundary', () => { + const courseData = { + entitlement: { + isEntitlement: true, + uuid: 'test-uuid', + changeDeadline: new Date().toISOString(), + isExpired: false, + isFulfilled: false, + availableSessions: mockAvailableSessions, + }, + }; + + const { result } = renderHook(() => useEntitlementInfo(courseData)); + expect(typeof result.current.canChange).toBe('boolean'); + expect(typeof result.current.showExpirationWarning).toBe('boolean'); + }); + }); +}); diff --git a/src/hooks/useEntitlementInfo.ts b/src/hooks/useEntitlementInfo.ts new file mode 100644 index 000000000..7dbe9d8a0 --- /dev/null +++ b/src/hooks/useEntitlementInfo.ts @@ -0,0 +1,33 @@ +import { useMemo } from 'react'; + +const useEntitlementInfo = (courseData) => useMemo(() => { + const { entitlement } = courseData || {}; + if (!entitlement || Object.keys(entitlement).length === 0 || entitlement.isEntitlement === false) { + return { isEntitlement: false }; + } + const today = new Date(); + const dateSixMonthsFromNow = new Date(); + dateSixMonthsFromNow.setDate(dateSixMonthsFromNow.getDate() + 180); + const deadline = new Date(entitlement.changeDeadline); + const deadlinePassed = deadline < today; + const showExpirationWarning = ( + !entitlement.isFulfilled + && !deadlinePassed + && deadline <= dateSixMonthsFromNow + ); + return { + isEntitlement: true, + + availableSessions: entitlement.availableSessions, + changeDeadline: deadline, + isExpired: entitlement.isExpired, + isFulfilled: entitlement.isFulfilled, + uuid: entitlement.uuid, + + hasSessions: entitlement.availableSessions?.length > 0, + canChange: !deadlinePassed, + showExpirationWarning, + }; +}, [courseData]); + +export default useEntitlementInfo; diff --git a/src/hooks/useIsMasquerading.test.tsx b/src/hooks/useIsMasquerading.test.tsx new file mode 100644 index 000000000..63799c9a2 --- /dev/null +++ b/src/hooks/useIsMasquerading.test.tsx @@ -0,0 +1,450 @@ +import { renderHook } from '@testing-library/react'; +import { useMasquerade } from '@src/data/context'; +import { useInitializeLearnerHome } from '@src/data/hooks'; +import { useIsMasquerading } from '@src/hooks'; + +jest.mock('@src/data/context'); +jest.mock('@src/data/hooks'); + +const mockUseMasquerade = useMasquerade as jest.MockedFunction; +const mockUseInitializeLearnerHome = useInitializeLearnerHome as jest.MockedFunction; + +describe('useIsMasquerading', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('returns true when masquerading successfully', () => { + it('should return true when masqueradeUser exists and no error', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: 'test-user', + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: {}, + isError: false, + isLoading: false, + error: null, + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(true); + }); + + it('should return true when masqueradeUser is a non-empty string and no error', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: 'admin@example.com', + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: {}, + isError: false, + isLoading: false, + error: null, + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(true); + }); + + it('should return true when masqueradeUser is valid and query is loading', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: 'test-user', + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: undefined, + isError: false, + isLoading: true, + error: null, + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(true); + }); + + it('should return true when masqueradeUser is valid and query is successful', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: 'student123', + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: { courses: [] }, + isError: false, + isLoading: false, + error: null, + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(true); + }); + }); + + describe('returns false when no masqueradeUser', () => { + it('should return false when masqueradeUser is null', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: undefined, + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: {}, + isError: false, + isLoading: false, + error: null, + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(false); + }); + + it('should return false when masqueradeUser is undefined', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: undefined, + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: {}, + isError: false, + isLoading: false, + error: null, + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(false); + }); + + it('should return false when masqueradeUser is empty string', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: '', + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: {}, + isError: false, + isLoading: false, + error: null, + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(false); + }); + + it('should return false when masqueradeUser is whitespace only', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: ' ', + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: {}, + isError: false, + isLoading: false, + error: null, + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(true); + }); + }); + + describe('returns false when there is an error', () => { + it('should return false when masqueradeUser exists but query has error', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: 'test-user', + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: undefined, + isError: true, + isLoading: false, + error: new Error('API Error'), + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(false); + }); + + it('should return false when masqueradeUser is valid but query fails with network error', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: 'admin@example.com', + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: undefined, + isError: true, + isLoading: false, + error: new Error('Network Error'), + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(false); + }); + + it('should return false when masqueradeUser is valid but query fails with 404 error', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: 'nonexistent-user', + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: undefined, + isError: true, + isLoading: false, + error: { response: { status: 404 } }, + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(false); + }); + }); + + describe('returns false for edge cases', () => { + it('should return false when both masqueradeUser is undefined and there is an error', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: undefined, + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: undefined, + isError: true, + isLoading: false, + error: new Error('API Error'), + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(false); + }); + + it('should return false when masqueradeUser is empty string and there is an error', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: '', + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: undefined, + isError: true, + isLoading: false, + error: new Error('API Error'), + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(false); + }); + + it('should return false when masqueradeUser is undefined and there is an error', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: undefined, + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: undefined, + isError: true, + isLoading: false, + error: new Error('API Error'), + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(result.current).toBe(false); + }); + }); + + describe('hook behavior and reactivity', () => { + it('should update when masqueradeUser changes', () => { + const { result, rerender } = renderHook(() => useIsMasquerading()); + mockUseMasquerade.mockReturnValue({ + masqueradeUser: undefined, + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: {}, + isError: false, + isLoading: false, + error: null, + } as any); + + rerender(); + expect(result.current).toBe(false); + + mockUseMasquerade.mockReturnValue({ + masqueradeUser: 'test-user', + setMasqueradeUser: jest.fn(), + }); + + rerender(); + expect(result.current).toBe(true); + }); + + it('should update when error state changes', () => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: 'test-user', + setMasqueradeUser: jest.fn(), + }); + + const { result, rerender } = renderHook(() => useIsMasquerading()); + mockUseInitializeLearnerHome.mockReturnValue({ + data: {}, + isError: false, + isLoading: false, + error: null, + } as any); + + rerender(); + expect(result.current).toBe(true); + mockUseInitializeLearnerHome.mockReturnValue({ + data: undefined, + isError: true, + isLoading: false, + error: new Error('API Error'), + } as any); + + rerender(); + expect(result.current).toBe(false); + }); + + it('should return boolean type consistently', () => { + const testCases = [ + { masqueradeUser: 'user', isError: false, expected: true }, + { masqueradeUser: undefined, isError: false, expected: false }, + { masqueradeUser: 'user', isError: true, expected: false }, + { masqueradeUser: '', isError: false, expected: false }, + ]; + + testCases.forEach(({ masqueradeUser, isError, expected }) => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser, + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: isError ? undefined : {}, + isError, + isLoading: false, + error: isError ? new Error('Test error') : null, + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + + expect(typeof result.current).toBe('boolean'); + expect(result.current).toBe(expected); + }); + }); + }); + + describe('integration scenarios', () => { + it('should work correctly in typical masquerading flow', () => { + const { result, rerender } = renderHook(() => useIsMasquerading()); + mockUseMasquerade.mockReturnValue({ + masqueradeUser: undefined, + setMasqueradeUser: jest.fn(), + }); + mockUseInitializeLearnerHome.mockReturnValue({ + data: {}, + isError: false, + isLoading: false, + error: null, + } as any); + + rerender(); + expect(result.current).toBe(false); + mockUseMasquerade.mockReturnValue({ + masqueradeUser: 'student123', + setMasqueradeUser: jest.fn(), + }); + mockUseInitializeLearnerHome.mockReturnValue({ + data: undefined, + isError: false, + isLoading: true, + error: null, + } as any); + + rerender(); + expect(result.current).toBe(true); + mockUseInitializeLearnerHome.mockReturnValue({ + data: { courses: ['course1', 'course2'] }, + isError: false, + isLoading: false, + error: null, + } as any); + + rerender(); + expect(result.current).toBe(true); + mockUseInitializeLearnerHome.mockReturnValue({ + data: undefined, + isError: true, + isLoading: false, + error: new Error('User not found'), + } as any); + + rerender(); + expect(result.current).toBe(false); + mockUseMasquerade.mockReturnValue({ + masqueradeUser: undefined, + setMasqueradeUser: jest.fn(), + }); + mockUseInitializeLearnerHome.mockReturnValue({ + data: {}, + isError: false, + isLoading: false, + error: null, + } as any); + + rerender(); + expect(result.current).toBe(false); + }); + + it('should handle various masqueradeUser formats', () => { + const userFormats = [ + 'username', + 'user@example.com', + 'user.name@domain.co.uk', + 'user_123', + 'User Name', + '12345', + ]; + + userFormats.forEach(user => { + mockUseMasquerade.mockReturnValue({ + masqueradeUser: user, + setMasqueradeUser: jest.fn(), + }); + + mockUseInitializeLearnerHome.mockReturnValue({ + data: {}, + isError: false, + isLoading: false, + error: null, + } as any); + + const { result } = renderHook(() => useIsMasquerading()); + expect(result.current).toBe(true); + }); + }); + }); +}); diff --git a/src/hooks/useIsMasquerading.ts b/src/hooks/useIsMasquerading.ts new file mode 100644 index 000000000..f57f89676 --- /dev/null +++ b/src/hooks/useIsMasquerading.ts @@ -0,0 +1,10 @@ +import { useMasquerade } from '@src/data/context'; +import { useInitializeLearnerHome } from '@src/data/hooks'; + +const useIsMasquerading = () => { + const { masqueradeUser } = useMasquerade(); + const { isError } = useInitializeLearnerHome(); + return !!masqueradeUser && !isError; +}; + +export default useIsMasquerading; diff --git a/src/hooks/utils.js b/src/hooks/utils.js index 2f0d76cd2..0787246ef 100644 --- a/src/hooks/utils.js +++ b/src/hooks/utils.js @@ -1,6 +1,6 @@ import React from 'react'; -import { useIntl } from '@edx/frontend-platform/i18n'; -import dateFormatter from 'utils/dateFormatter'; +import { useIntl } from '@openedx/frontend-base'; +import dateFormatter from '../utils/dateFormatter'; export const useValueCallback = (cb, prereqs = []) => ( React.useCallback(e => cb(e.target.value), prereqs) // eslint-disable-line diff --git a/src/hooks/utils.test.js b/src/hooks/utils.test.js index e5420adf6..e8d92273c 100644 --- a/src/hooks/utils.test.js +++ b/src/hooks/utils.test.js @@ -1,6 +1,10 @@ import * as utils from './utils'; jest.unmock('./utils'); +jest.mock('react', () => ({ + ...jest.requireActual('react'), + useCallback: jest.fn((cb, prereqs) => ({ useCallback: { cb, prereqs } })), +})); describe('app-level hooks', () => { describe('useValuecallback', () => { diff --git a/src/i18n/index.js b/src/i18n/index.js deleted file mode 100644 index d6d1738de..000000000 --- a/src/i18n/index.js +++ /dev/null @@ -1 +0,0 @@ -export default []; diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 000000000..01b4dfe77 --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1 @@ +export { default } from './messages'; diff --git a/src/i18n/messages.d.ts b/src/i18n/messages.d.ts new file mode 100644 index 000000000..ed986d8b3 --- /dev/null +++ b/src/i18n/messages.d.ts @@ -0,0 +1,4 @@ +import type { SiteMessages } from '@openedx/frontend-base'; + +declare const messages: SiteMessages; +export default messages; diff --git a/src/index.jsx b/src/index.jsx deleted file mode 100755 index 418d81587..000000000 --- a/src/index.jsx +++ /dev/null @@ -1,69 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -import 'core-js/stable'; -import 'regenerator-runtime/runtime'; - -import React, { StrictMode } from 'react'; -import { createRoot } from 'react-dom/client'; -import { - Route, Navigate, Routes, -} from 'react-router-dom'; - -import { - AppProvider, - ErrorPage, - PageWrap, -} from '@edx/frontend-platform/react'; -import store from 'data/store'; -import { - APP_READY, - APP_INIT_ERROR, - initialize, - subscribe, - mergeConfig, -} from '@edx/frontend-platform'; - -import { configuration } from './config'; - -import messages from './i18n'; - -import App from './App'; -import NoticesWrapper from './components/NoticesWrapper'; - -subscribe(APP_READY, () => { - const root = createRoot(document.getElementById('root')); - - root.render( - - - - - } /> - } /> - - - - , - ); -}); - -subscribe(APP_INIT_ERROR, (error) => { - const root = createRoot(document.getElementById('root')); - - root.render( - - - , - ); -}); - -export const appName = 'LearnerHomeAppConfig'; - -initialize({ - handlers: { - config: () => { - mergeConfig(configuration, appName); - }, - }, - messages, - requireAuthenticatedUser: true, -}); diff --git a/src/index.test.jsx b/src/index.test.jsx deleted file mode 100644 index 5baed7483..000000000 --- a/src/index.test.jsx +++ /dev/null @@ -1,79 +0,0 @@ -import { - APP_INIT_ERROR, - APP_READY, - initialize, - mergeConfig, - subscribe, -} from '@edx/frontend-platform'; - -import { configuration } from './config'; -import * as app from '.'; - -// These need to be var not let so they get hoisted -// and can be used by jest.mock (which is also hoisted) -var mockRender; // eslint-disable-line no-var -var mockCreateRoot; // eslint-disable-line no-var -jest.mock('react-dom/client', () => { - mockRender = jest.fn(); - mockCreateRoot = jest.fn(() => ({ - render: mockRender, - })); - - return ({ - createRoot: mockCreateRoot, - }); -}); - -jest.mock('@edx/frontend-platform', () => ({ - mergeConfig: jest.fn(), - ensureConfig: jest.fn(), - APP_READY: 'app-is-ready-key', - APP_INIT_ERROR: 'app-init-error', - initialize: jest.fn(), - subscribe: jest.fn(), -})); - -jest.mock('data/store', () => ({ redux: 'store' })); -jest.mock('./App', () => 'App'); -jest.mock('components/NoticesWrapper', () => 'NoticesWrapper'); - -describe('app registry', () => { - let getElement; - - beforeEach(() => { - mockCreateRoot.mockClear(); - mockRender.mockClear(); - - getElement = window.document.getElementById; - window.document.getElementById = jest.fn(id => ({ id })); - }); - afterAll(() => { - window.document.getElementById = getElement; - }); - - test('subscribe: APP_READY. links App to root element', () => { - const callArgs = subscribe.mock.calls[0]; - expect(callArgs[0]).toEqual(APP_READY); - callArgs[1](); - const [rendered] = mockRender.mock.calls[0]; - expect(rendered).toMatchSnapshot(); - }); - test('subscribe: APP_INIT_ERROR. snapshot: displays an ErrorPage to root element', () => { - const callArgs = subscribe.mock.calls[1]; - expect(callArgs[0]).toEqual(APP_INIT_ERROR); - const error = { message: 'test-error-message' }; - callArgs[1](error); - const [rendered] = mockRender.mock.calls[0]; - expect(rendered).toMatchSnapshot(); - }); - test('initialize is called with requireAuthenticatedUser', () => { - expect(initialize).toHaveBeenCalledTimes(1); - const initializeArg = initialize.mock.calls[0][0]; - expect(initializeArg.requireAuthenticatedUser).toEqual(true); - }); - test('initialize config', () => { - const initializeArg = initialize.mock.calls[0][0]; - initializeArg.handlers.config(); - expect(mergeConfig).toHaveBeenCalledWith(configuration, app.appName); - }); -}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..432758869 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export { default as learnerDashboardApp } from './app'; +export { default as learnerDashboardRoutes } from './routes'; diff --git a/src/messages.js b/src/messages.js index 3d60482c9..b9c256961 100644 --- a/src/messages.js +++ b/src/messages.js @@ -1,21 +1,21 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ - loadingSR: { + 'learner-dash.loadingSR': { id: 'learner-dash.loadingSR', description: 'Page loading screen-reader text', defaultMessage: 'Loading...', }, - errorMessage: { - id: 'learner-dash.error-page-message', - defaultMessage: 'If you experience repeated failures, please email support at {supportEmail}', - description: 'Error page message', - }, - pageTitle: { + 'learner-dash.title': { id: 'learner-dash.title', description: 'Page title: Learner Home', defaultMessage: 'Learner Home', }, + 'learner-dash.page.title': { + id: 'learner-dash.page.title', + description: 'Document title for the learner dashboard page', + defaultMessage: 'Learner Home | {siteName}', + }, }); export default messages; diff --git a/src/plugin-slots/CourseBannerSlot/README.md b/src/plugin-slots/CourseBannerSlot/README.md deleted file mode 100644 index 847ee9fb4..000000000 --- a/src/plugin-slots/CourseBannerSlot/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Course Card Action Slot - -### Slot ID: `org.openedx.frontend.learner_dashboard.course_card_banner.v1` -### Props: -* `cardId` - -## Description - -This slot is used for replacing or adding content for the `CourseBanner` component. This banner is rendered as a child of the `CourseCard`. - -The default CourseBanner looks like this when audit access has expired for the course: -![Screenshot of the default CourseBanner when audit access has expired](./images/course_banner_slot_default.png) - -## Example - -The following `env.config.jsx` will render a custom implemenation of a CourseBanner under every `CourseCard`. - -![Screenshot of custom banner added under CourseCard](./images/course_banner_slot_default.png) - -```js -import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; -import { Alert } from '@openedx/paragon'; - -const config = { - pluginSlots: { - 'org.openedx.frontend.learner_dashboard.course_card_banner.v1': { - keepDefault: false, - plugins: [ - { - op: PLUGIN_OPERATIONS.Insert, - widget: { - id: 'org.openedx.frontend.learner_dashboard.course_card_banner.v1', - type: DIRECT_PLUGIN, - priority: 60, - RenderWidget: ({ cardId }) => ( - - Course banner for course with {cardId} - - ), - }, - }, - ], - }, - }, -} - -export default config; -``` diff --git a/src/plugin-slots/CourseBannerSlot/index.jsx b/src/plugin-slots/CourseBannerSlot/index.jsx deleted file mode 100644 index 958ee7067..000000000 --- a/src/plugin-slots/CourseBannerSlot/index.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { PluginSlot } from '@openedx/frontend-plugin-framework'; -import CourseBanner from 'containers/CourseCard/components/CourseCardBanners/CourseBanner'; - -const CourseBannerSlot = ({ cardId }) => ( - - - -); - -CourseBannerSlot.propTypes = { - cardId: PropTypes.string.isRequired, -}; - -export default CourseBannerSlot; diff --git a/src/plugin-slots/CourseCardActionSlot/README.md b/src/plugin-slots/CourseCardActionSlot/README.md deleted file mode 100644 index 7a6c35340..000000000 --- a/src/plugin-slots/CourseCardActionSlot/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# Course Card Action Slot - -### Slot ID: `org.openedx.frontend.learner_dashboard.course_card_action.v1` - -### Slot ID Aliases -* `course_card_action_slot` - -### Props: -* `cardId` - -## Description - -This slot is used for adding content in the Action buttons section of each Course Card. - -## Example - -The following `env.config.jsx` will render the `cardId` of the course as `

` elements in a `

`. - -![Screenshot of Content added after the Sequence Container](./images/post_course_card_action.png) - -```js -import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; -import ActionButton from 'containers/CourseCard/components/CourseCardActions/ActionButton'; - -const config = { - pluginSlots: { - 'org.openedx.frontend.learner_dashboard.course_card_action.v1': { - keepDefault: false, - plugins: [ - { - // Insert Custom Button in Course Card - op: PLUGIN_OPERATIONS.Insert, - widget: { - id: 'custom_course_card_action', - priority: 60, - type: DIRECT_PLUGIN, - RenderWidget: ({cardId}) => ( - - Custom Button - - ), - }, - }, - { - // Insert Another Button in Course Card - op: PLUGIN_OPERATIONS.Insert, - widget: { - id: 'another_custom_course_card_action', - priority: 70, - type: DIRECT_PLUGIN, - RenderWidget: ({cardId}) => ( - - 📚: {cardId} - - ), - }, - }, - ] - } - }, -} - -export default config; -``` diff --git a/src/plugin-slots/CourseCardActionSlot/index.jsx b/src/plugin-slots/CourseCardActionSlot/index.jsx deleted file mode 100644 index 0ddc2b8d0..000000000 --- a/src/plugin-slots/CourseCardActionSlot/index.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { PluginSlot } from '@openedx/frontend-plugin-framework'; - -const CourseCardActionSlot = ({ cardId }) => ( - -); - -CourseCardActionSlot.propTypes = { - cardId: PropTypes.string.isRequired, -}; - -export default CourseCardActionSlot; diff --git a/src/plugin-slots/CourseListSlot/README.md b/src/plugin-slots/CourseListSlot/README.md deleted file mode 100644 index d9a7aadde..000000000 --- a/src/plugin-slots/CourseListSlot/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Course List Slot - -### Slot ID: `org.openedx.frontend.learner_dashboard.course_list.v1` - -### Slot ID Aliases -* `course_list_slot` - -## Plugin Props - -* courseListData - -## Description - -This slot is used for replacing or adding content around the `CourseList` component. The `CourseListSlot` is only rendered if the learner has enrolled in at least one course. - -## Example - -The space will show the `CourseList` component by default. This can be disabled in the configuration with the `keepDefault` boolean. - -![Screenshot of the CourseListSlot](./images/course_list_slot.png) - -Setting the MFE's `env.config.jsx` to the following will replace the default experience with a list of course titles. - -![Screenshot of a custom course list](./images/readme_custom_course_list.png) - -```js -import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; - -const config = { - pluginSlots: { - 'org.openedx.frontend.learner_dashboard.course_list.v1': { - // Hide the default CourseList component - keepDefault: false, - plugins: [ - { - op: PLUGIN_OPERATIONS.Insert, - widget: { - id: 'custom_course_list', - type: DIRECT_PLUGIN, - priority: 60, - RenderWidget: ({ courseListData }) => { - // Extract the "visibleList" - const courses = courseListData.visibleList; - // Render a list of course names - return ( -
- {courses.map(courseData => ( -

- {courseData.course.courseName} -

- ))} -
- ) - }, - }, - }, - ], - }, - }, -} - -export default config; -``` \ No newline at end of file diff --git a/src/plugin-slots/CourseListSlot/index.jsx b/src/plugin-slots/CourseListSlot/index.jsx deleted file mode 100644 index 573593cb5..000000000 --- a/src/plugin-slots/CourseListSlot/index.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; - -import { PluginSlot } from '@openedx/frontend-plugin-framework'; -import { CourseList, courseListDataShape } from 'containers/CoursesPanel/CourseList'; - -export const CourseListSlot = ({ courseListData }) => ( - - - -); - -CourseListSlot.propTypes = { - courseListData: courseListDataShape, -}; - -export default CourseListSlot; diff --git a/src/plugin-slots/DashboardModalSlot/README.md b/src/plugin-slots/DashboardModalSlot/README.md deleted file mode 100644 index b6c6cfb58..000000000 --- a/src/plugin-slots/DashboardModalSlot/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Dashboard Modal Slot - -### Slot ID: `org.openedx.frontend.learner_dashboard.dashboard_modal.v1` - -### https://github.com/openedx/frontend-plugin-framework/blob/master/docs/decisions/0003-slot-naming-and-life-cycle.rst#1-naming-format - -## Description - -This slot is used for the modal on a dashboard. -The following `env.config.jsx` will render the modal. - -## Example - -Learner dashboard will show modal -![Screenshot of the dashboard modal](./images/dashboard_modal_slot.png) - -```js -import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; -import { ModalDialog } from '@openedx/paragon'; - -const config = { - pluginSlots: { - org.openedx.frontend.learner_dashboard.dashboard_modal.v1: { - plugins: [ - { - op: PLUGIN_OPERATIONS.Insert, - widget: { - id: 'dashboard_modal', - type: DIRECT_PLUGIN, - priority: 60, - RenderWidget: - , - }, - }, - ], - } - }, -} - -export default config; -``` diff --git a/src/plugin-slots/DashboardModalSlot/index.jsx b/src/plugin-slots/DashboardModalSlot/index.jsx deleted file mode 100644 index 9fe5b8daf..000000000 --- a/src/plugin-slots/DashboardModalSlot/index.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import { PluginSlot } from '@openedx/frontend-plugin-framework'; - -const DashboardModalSlot = () => ( - -); - -export default DashboardModalSlot; diff --git a/src/plugin-slots/FooterSlot/README.md b/src/plugin-slots/FooterSlot/README.md deleted file mode 100644 index f7bfc3004..000000000 --- a/src/plugin-slots/FooterSlot/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# Footer Slot - -### Slot ID: `org.openedx.frontend.layout.footer.v1` - -### Slot ID Aliases -* `footer_slot` - -## Description - -This slot is used to replace/modify/hide the footer. - -The implementation of the `FooterSlot` component lives in [the `frontend-component-footer` repository](https://github.com/openedx/frontend-component-footer/). - -## Example - -The following `env.config.jsx` will replace the default footer. - -![Screenshot of Default Footer](./images/default_footer.png) - -with a simple custom footer - -![Screenshot of Custom Footer](./images/custom_footer.png) - -```jsx -import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; - -const config = { - pluginSlots: { - 'org.openedx.frontend.layout.footer.v1': { - plugins: [ - { - // Hide the default footer - op: PLUGIN_OPERATIONS.Hide, - widgetId: 'default_contents', - }, - { - // Insert a custom footer - op: PLUGIN_OPERATIONS.Insert, - widget: { - id: 'custom_footer', - type: DIRECT_PLUGIN, - RenderWidget: () => ( -

🦶

- ), - }, - }, - ] - } - }, -} - -export default config; -``` diff --git a/src/plugin-slots/FooterSlot/images/custom_footer.png b/src/plugin-slots/FooterSlot/images/custom_footer.png deleted file mode 100644 index 8681ef489..000000000 Binary files a/src/plugin-slots/FooterSlot/images/custom_footer.png and /dev/null differ diff --git a/src/plugin-slots/FooterSlot/images/default_footer.png b/src/plugin-slots/FooterSlot/images/default_footer.png deleted file mode 100644 index 24475738d..000000000 Binary files a/src/plugin-slots/FooterSlot/images/default_footer.png and /dev/null differ diff --git a/src/plugin-slots/NoCoursesViewSlot/README.md b/src/plugin-slots/NoCoursesViewSlot/README.md deleted file mode 100644 index c6c62eacd..000000000 --- a/src/plugin-slots/NoCoursesViewSlot/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# No Courses View Slot - -### Slot ID: `org.openedx.frontend.learner_dashboard.no_courses_view.v1` - -### Slot ID Aliases -* `no_courses_view_slot` - -## Description - -This slot is used for replacing or adding content around the `NoCoursesView` component. The `NoCoursesViewSlot` only renders if the learner has not yet enrolled in any courses. - -## Example - -The space will show the `NoCoursesView` by default. This can be disabled in the configuration with the `keepDefault` boolean. - -![Screenshot of the no courses view](./images/no_courses_view_slot.png) - -Setting the MFE's `env.config.jsx` to the following will replace the default experience with a custom call-to-action component. - -![Screenshot of a custom no courses view](./images/readme_custom_no_courses_view.png) - -```js -import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; - -const config = { - pluginSlots: { - 'org.openedx.frontend.learner_dashboard.no_courses_view.v1': { - // Hide the default NoCoursesView component - keepDefault: false, - plugins: [ - { - op: PLUGIN_OPERATIONS.Insert, - widget: { - id: 'custom_no_courses_CTA', - type: DIRECT_PLUGIN, - priority: 60, - RenderWidget: () => ( -

- Check out our catalog of courses and start learning today! -

- ), - }, - }, - ], - }, - }, -} - -export default config; -``` \ No newline at end of file diff --git a/src/plugin-slots/NoCoursesViewSlot/index.jsx b/src/plugin-slots/NoCoursesViewSlot/index.jsx deleted file mode 100644 index 564b7602e..000000000 --- a/src/plugin-slots/NoCoursesViewSlot/index.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; - -import { PluginSlot } from '@openedx/frontend-plugin-framework'; -import NoCoursesView from 'containers/CoursesPanel/NoCoursesView'; - -export const NoCoursesViewSlot = () => ( - - - -); - -export default NoCoursesViewSlot; diff --git a/src/plugin-slots/README.md b/src/plugin-slots/README.md deleted file mode 100644 index 63968921c..000000000 --- a/src/plugin-slots/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# `frontend-app-learner-dashboard` Plugin Slots - -* [`org.openedx.frontend.learner_dashboard.course_card_action.v1`](./CourseCardActionSlot/) -* [`org.openedx.frontend.layout.footer.v1`](./FooterSlot/) -* [`org.openedx.frontend.learner_dashboard.widget_sidebar.v1`](./WidgetSidebarSlot/) -* [`org.openedx.frontend.learner_dashboard.course_list.v1`](./CourseListSlot/) -* [`org.openedx.frontend.learner_dashboard.no_courses_view.v1`](./NoCoursesViewSlot/) -* [`org.openedx.frontend.learner_dashboard.dashboard_modal.v1`](./DashboardModalSlot) diff --git a/src/plugin-slots/WidgetSidebarSlot/README.md b/src/plugin-slots/WidgetSidebarSlot/README.md deleted file mode 100644 index cb70bba45..000000000 --- a/src/plugin-slots/WidgetSidebarSlot/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Widget Sidebar Slot - -### Slot ID: `org.openedx.frontend.learner_dashboard.widget_sidebar.v1` - -### Slot ID Aliases -* `widget_sidebar_slot` - -## Description - -This slot is used for adding content to the right-hand sidebar. - -## Example - -The space will show the `LookingForChallengeWidget` by default. This can be disabled in the configuration with the `keepDefault` boolean. - -![Screenshot of the widget sidebar](./images/widget_sidebar_slot.png) - -Setting the MFE's `env.config.jsx` to the following will replace the default experience with a custom sidebar component. - -![Screenshot of a custom call-to-action in the sidebar](./images/readme_custom_sidebar.png) - -```js -import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; - -const config = { - pluginSlots: { - 'org.openedx.frontend.learner_dashboard.widget_sidebar.v1': { - // Hide the default LookingForChallenge component - keepDefault: false, - plugins: [ - { - op: PLUGIN_OPERATIONS.Insert, - widget: { - id: 'custom_sidebar_panel', - type: DIRECT_PLUGIN, - priority: 60, - RenderWidget: () => ( -
-

- Sidebar Menu -

-

- sidebar item #1 -

-

- sidebar item #2 -

-

- sidebar item #3 -

-
- ), - }, - }, - ], - }, - }, -} - -export default config; -``` \ No newline at end of file diff --git a/src/plugin-slots/WidgetSidebarSlot/__snapshots__/index.test.jsx.snap b/src/plugin-slots/WidgetSidebarSlot/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 38ac3254f..000000000 --- a/src/plugin-slots/WidgetSidebarSlot/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`WidgetSidebar snapshots 1`] = ` - - - -`; diff --git a/src/plugin-slots/WidgetSidebarSlot/index.jsx b/src/plugin-slots/WidgetSidebarSlot/index.jsx deleted file mode 100644 index 370027ce4..000000000 --- a/src/plugin-slots/WidgetSidebarSlot/index.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; - -import { PluginSlot } from '@openedx/frontend-plugin-framework'; -import LookingForChallengeWidget from 'widgets/LookingForChallengeWidget'; - -// eslint-disable-next-line arrow-body-style -export const WidgetSidebarSlot = () => ( - - - -); - -export default WidgetSidebarSlot; diff --git a/src/plugin-slots/WidgetSidebarSlot/index.test.jsx b/src/plugin-slots/WidgetSidebarSlot/index.test.jsx deleted file mode 100644 index 591ddccbc..000000000 --- a/src/plugin-slots/WidgetSidebarSlot/index.test.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import { shallow } from '@edx/react-unit-test-utils'; - -import WidgetSidebarSlot from '.'; - -jest.mock('widgets/LookingForChallengeWidget', () => 'LookingForChallengeWidget'); - -jest.mock('@openedx/frontend-plugin-framework', () => ({ - PluginSlot: 'PluginSlot', -})); - -describe('WidgetSidebar', () => { - beforeEach(() => jest.resetAllMocks()); - - test('snapshots', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); - }); -}); diff --git a/src/providers.ts b/src/providers.ts new file mode 100644 index 000000000..7c3bd9c51 --- /dev/null +++ b/src/providers.ts @@ -0,0 +1,11 @@ +import { AppProvider } from '@openedx/frontend-base'; + +import GlobalDataProvider from './data/contexts/GlobalDataProvider'; +import { MasqueradeProvider } from './data/context/MasqueradeProvider'; + +const providers: AppProvider[] = [ + GlobalDataProvider, + MasqueradeProvider, +]; + +export default providers; diff --git a/src/routes.jsx b/src/routes.jsx new file mode 100644 index 000000000..7bb82dd12 --- /dev/null +++ b/src/routes.jsx @@ -0,0 +1,19 @@ +import { authenticatedLoader, homeRole } from '@openedx/frontend-base'; +import { dashboardRole } from './constants'; + +const routes = [ + { + id: 'org.openedx.frontend.route.learnerDashboard.main', + path: '/learner-dashboard', + loader: authenticatedLoader, + handle: { + roles: [dashboardRole, homeRole] + }, + async lazy () { + const module = await import(/* webpackChunkName: "learner-dashboard-main" */ './Main'); + return { Component: module.default }; + }, + } +]; + +export default routes; diff --git a/src/segment.js b/src/segment.js index c2eb562a7..d39e4bda8 100644 --- a/src/segment.js +++ b/src/segment.js @@ -55,8 +55,7 @@ import { configuration } from './config'; }; // For each of our methods, generate a queueing stub. - for (let i = 0; i < analytics.methods.length; i++) { - const key = analytics.methods[i]; + for (const key of analytics.methods) { analytics[key] = analytics.factory(key); } diff --git a/src/setupTest.jsx b/src/setupTest.jsx index b4a1363ac..804e88fd0 100755 --- a/src/setupTest.jsx +++ b/src/setupTest.jsx @@ -1,228 +1,27 @@ -/* eslint-disable import/no-extraneous-dependencies */ import '@testing-library/jest-dom'; -jest.mock('react', () => ({ - ...jest.requireActual('react'), - useRef: jest.fn((val) => ({ current: val, useRef: true })), - useCallback: jest.fn((cb, prereqs) => ({ useCallback: { cb, prereqs } })), - useEffect: jest.fn((cb, prereqs) => ({ useEffect: { cb, prereqs } })), - useMemo: jest.fn((cb, prereqs) => cb(prereqs)), - useContext: jest.fn(context => context), - useState: jest.fn(), -})); +import siteConfig from 'site.config'; +import { addAppConfigs, configureAnalytics, configureAuth, configureLogging, getSiteConfig, mergeSiteConfig, MockAnalyticsService, MockAuthService, MockLoggingService } from '@openedx/frontend-base'; -jest.mock('reselect', () => ({ - createSelector: jest.fn((preSelectors, cb) => ({ preSelectors, cb })), -})); +mergeSiteConfig(siteConfig); +addAppConfigs(); -// Mock react-redux hooks -// unmock for integration tests -jest.mock('react-redux', () => { - const dispatch = jest.fn((...args) => ({ dispatch: args })).mockName('react-redux.dispatch'); - return { - connect: (mapStateToProps, mapDispatchToProps) => (component) => ({ - mapStateToProps, - mapDispatchToProps, - component, - }), - useDispatch: jest.fn(() => dispatch), - useSelector: jest.fn((selector) => ({ useSelector: selector })), - }; -}); +export function initializeMockServices() { + const loggingService = configureLogging(MockLoggingService, { + config: getSiteConfig(), + }); -jest.mock('moment', () => ({ - __esModule: true, - default: (date) => ({ - toDate: jest.fn().mockReturnValue(date), - }), -})); + const authService = configureAuth(MockAuthService, { + config: getSiteConfig(), + loggingService, + }); -jest.mock('@edx/frontend-platform/react', () => ({ - ...jest.requireActual('@edx/frontend-platform/react'), - ErrorPage: () => 'ErrorPage', -})); + const analyticsService = configureAnalytics(MockAnalyticsService, { + config: getSiteConfig(), + httpClient: authService.getAuthenticatedHttpClient(), + loggingService, + }); -jest.mock('@edx/frontend-platform/i18n', () => { - const i18n = jest.requireActual('@edx/frontend-platform/i18n'); - const PropTypes = jest.requireActual('prop-types'); - const { formatMessage } = jest.requireActual('./testUtils'); - const formatDate = jest.fn(date => new Date(date).toLocaleDateString()).mockName('useIntl.formatDate'); - return { - ...i18n, - intlShape: PropTypes.shape({ - formatMessage: PropTypes.func, - }), - useIntl: () => ({ - formatMessage, - formatDate, - }), - defineMessages: m => m, - FormattedMessage: () => 'FormattedMessage', - }; -}); + return { analyticsService, authService, loggingService }; +} -jest.mock('@openedx/paragon', () => jest.requireActual('testUtils').mockNestedComponents({ - Alert: { - Heading: 'Alert.Heading', - }, - AlertModal: 'AlertModal', - MarketingModal: 'MarketingModal', - ActionRow: 'ActionRow', - Badge: 'Badge', - Button: 'Button', - Card: { - Body: 'Card.Body', - Footer: 'Card.Footer', - Header: 'Card.Header', - ImageCap: 'Card.ImageCap', - Section: 'Card.Section', - }, - CardGrid: 'CardGrid', - Chip: 'Chip', - Col: 'Col', - Collapsible: { - Advanced: 'Collapsible.Advanced', - Body: 'Collapsible.Body', - Trigger: 'Collapsible.Trigger', - Visible: 'Collapsible.Visible', - }, - Container: 'Container', - DataTable: { - EmptyTable: 'DataTable.EmptyTable', - Table: 'DataTable.Table', - TableControlBar: 'DataTable.TableControlBar', - TableController: 'DataTable.TableController', - TableFooter: 'DataTable.TableFooter', - }, - Dropdown: { - Item: 'Dropdown.Item', - Menu: 'Dropdown.Menu', - Toggle: 'Dropdown.Toggle', - Header: 'Dropdown.Header', - Divider: 'Dropdown.Divider', - }, - Form: { - Checkbox: 'Form.Checkbox', - CheckboxSet: 'Form.CheckboxSet', - Control: { - Feedback: 'Form.Control.Feedback', - }, - Group: 'Form.Group', - Label: 'Form.Label', - Radio: 'Form.Radio', - RadioSet: 'Form.RadioSet', - Switch: 'Form.Switch', - }, - FormControl: 'FormControl', - FormControlFeedback: 'FormControlFeedback', - FormGroup: 'FormGroup', - FormLabel: 'FormLabel', - FullscreenModal: 'FullscreenModal', - Hyperlink: 'Hyperlink', - Icon: 'Icon', - IconButton: 'IconButton', - Image: 'Image', - MailtoLink: 'MailtoLink', - ModalDialog: { - Header: 'ModalDialog.Header', - Body: 'ModalDialog.Body', - Hero: 'ModalDialog.Hero', - }, - ModalPopup: 'ModalPopup', - ModalCloseButton: 'ModalCloseButton', - MultiSelectDropdownFilter: 'MultiSelectDropdownFilter', - OverlayTrigger: 'OverlayTrigger', - Popover: { - Content: 'Popover.Content', - }, - Row: 'Row', - Sheet: 'Sheet', - StatefulButton: 'StatefulButton', - TextFilter: 'TextFilter', - Truncate: 'Truncate', - Skeleton: 'Skeleton', - Spinner: 'Spinner', - PageBanner: 'PageBanner', - Pagination: 'Pagination', - - useWindowSize: () => jest.fn(), - useToggle: () => jest.fn().mockImplementation((val) => [ - val, - jest.fn().mockName('useToggle.setTrue'), - jest.fn().mockName('useToggle.setFalse'), - ]), - useCheckboxSetValues: () => jest.fn().mockImplementation((values) => ([values, { - add: jest.fn().mockName('useCheckboxSetValues.add'), - remove: jest.fn().mockName('useCheckboxSetValues.remove'), - }])), - breakpoints: () => ({ - extraSmall: { - minWidth: 0, - maxWidth: 575, - }, - small: { - minWidth: 576, - maxWidth: 767, - }, - medium: { - minWidth: 768, - maxWidth: 991, - }, - large: { - minWidth: 992, - maxWidth: 1199, - }, - extraLarge: { - minWidth: 1200, - maxWidth: 100000, - }, - }), -})); - -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: 'FontAwesomeIcon', -})); -jest.mock('@fortawesome/free-solid-svg-icons', () => ({ - faUserCircle: jest.fn().mockName('fa-user-circle-icon'), -})); - -jest.mock('@openedx/paragon/icons', () => ({ - ArrowBack: jest.fn().mockName('icons.ArrowBack'), - ArrowDropDown: jest.fn().mockName('icons.ArrowDropDown'), - ArrowDropUp: jest.fn().mockName('icons.ArrowDropUp'), - Book: jest.fn().mockName('icons.Book'), - Cancel: jest.fn().mockName('icons.Cancel'), - Close: jest.fn().mockName('icons.Close'), - CheckCircle: jest.fn().mockName('icons.CheckCircle'), - ChevronLeft: jest.fn().mockName('icons.ChevronLeft'), - ChevronRight: jest.fn().mockName('icons.ChevronRight'), - Highlight: jest.fn().mockName('icons.Highlight'), - Info: jest.fn().mockName('icons.Info'), - InfoOutline: jest.fn().mockName('icons.InfoOutline'), - Launch: jest.fn().mockName('icons.Launch'), - Locked: jest.fn().mockName('icons.Locked'), - MoreVert: jest.fn().mockName('icons.MoreVert'), - Tune: jest.fn().mockName('icons.Tune'), - PersonSearch: jest.fn().mockName('icons.PersonSearch'), - Program: jest.fn().mockName('icons.Program'), - Search: jest.fn().mockName('icons.Search'), -})); - -jest.mock('data/constants/app', () => ({ - ...jest.requireActual('data/constants/app'), - locationId: 'fake-location-id', -})); - -jest.mock('utils', () => ({ - ...jest.requireActual('utils'), - nullMethod: jest.fn().mockName('utils.nullMethod'), -})); - -jest.mock('utils/hooks', () => { - const formatDate = jest.fn(date => new Date(date).toLocaleDateString()) - .mockName('utils.formatDate'); - return { - formatDate, - useFormatDate: () => formatDate, - }; -}); diff --git a/src/slots.tsx b/src/slots.tsx new file mode 100644 index 000000000..d9fcc07c1 --- /dev/null +++ b/src/slots.tsx @@ -0,0 +1,9 @@ +import { SlotOperation } from '@openedx/frontend-base'; + +import { learnerDashboardHeaderApp } from './widgets/LearnerDashboardHeader'; + +const slots: SlotOperation[] = [ + ...(learnerDashboardHeaderApp.slots as []) +]; + +export default slots; diff --git a/src/slots/CourseBannerSlot/README.md b/src/slots/CourseBannerSlot/README.md new file mode 100644 index 000000000..e38a57074 --- /dev/null +++ b/src/slots/CourseBannerSlot/README.md @@ -0,0 +1,44 @@ +# Course Banner Slot + +### Slot ID: `org.openedx.frontend.slot.learnerDashboard.courseCardBanner.v1` + +### Slot Props + +* `cardId` + +## Description + +This slot is used for replacing or adding content for the `CourseBanner` component. This banner is rendered as a child of the `CourseCard`. + +The default CourseBanner looks like this when audit access has expired for the course: +![Screenshot of the default CourseBanner when audit access has expired](./images/course_banner_slot_default.png) + +## Example + +The following configuration will render a custom implementation of a CourseBanner under every `CourseCard`. + +![Screenshot of custom banner added under CourseCard](./images/custom_course_banner.png) + +```js +import { WidgetOperationTypes } from '@openedx/frontend-base'; +import { Alert } from '@openedx/paragon'; + +const myComponent = ({ cardId }) => ( + + Course banner for course with {cardId} + +) + +const config = { + slots: [ + { + slotId: 'org.openedx.frontend.slot.learnerDashboard.courseCardBanner.v1', + id: 'my.widget', + op: WidgetOperationTypes.REPLACE, + component: myComponent + }, + ] +} + +export default config; +``` diff --git a/src/plugin-slots/CourseBannerSlot/images/course_banner_slot_default.png b/src/slots/CourseBannerSlot/images/course_banner_slot_default.png similarity index 100% rename from src/plugin-slots/CourseBannerSlot/images/course_banner_slot_default.png rename to src/slots/CourseBannerSlot/images/course_banner_slot_default.png diff --git a/src/plugin-slots/CourseBannerSlot/images/custom_course_banner.png b/src/slots/CourseBannerSlot/images/custom_course_banner.png similarity index 100% rename from src/plugin-slots/CourseBannerSlot/images/custom_course_banner.png rename to src/slots/CourseBannerSlot/images/custom_course_banner.png diff --git a/src/slots/CourseBannerSlot/index.jsx b/src/slots/CourseBannerSlot/index.jsx new file mode 100644 index 000000000..fc9a0d9e4 --- /dev/null +++ b/src/slots/CourseBannerSlot/index.jsx @@ -0,0 +1,18 @@ +import PropTypes from 'prop-types'; +import { Slot } from '@openedx/frontend-base'; +import CourseBanner from '../../containers/CourseCard/components/CourseCardBanners/CourseBanner'; + +const CourseBannerSlot = ({ cardId }) => ( + + + +); + +CourseBannerSlot.propTypes = { + cardId: PropTypes.string.isRequired, +}; + +export default CourseBannerSlot; diff --git a/src/slots/CourseCardActionSlot/README.md b/src/slots/CourseCardActionSlot/README.md new file mode 100644 index 000000000..f1145cadf --- /dev/null +++ b/src/slots/CourseCardActionSlot/README.md @@ -0,0 +1,53 @@ +# Course Card Action Slot + +### Slot ID: `org.openedx.frontend.slot.learnerDashboard.courseCardAction.v1` + +### Slot Props + +* `cardId` + +## Description + +This slot is used for adding content in the Action buttons section of each Course Card. + +## Example + +The following configuration will render the `cardId` of the course as `

` elements in a `

`. + +![Screenshot of Content added after the Sequence Container](./images/post_course_card_action.png) + +```js +import { WidgetOperationTypes } from '@openedx/frontend-base'; +import ActionButton from 'containers/CourseCard/components/CourseCardActions/ActionButton'; + +const myComponent = ({ cardId }) => ( + + 📚: {cardId} + +) + +const mySlotId = 'org.openedx.frontend.slot.learnerDashboard.courseCardAction.v1'; + +const config = { + slots: [ + { + slotId: mySlotId, + id: 'my.first.widget', + op: WidgetOperationTypes.APPEND, + element: ( + + Custom Button + + ) + }, + { + slotId: mySlotId, + id: 'my.second.widget', + op: WidgetOperationTypes.APPEND, + component: myComponent + }, + ] +} + +export default config; +``` diff --git a/src/plugin-slots/CourseCardActionSlot/images/post_course_card_action.png b/src/slots/CourseCardActionSlot/images/post_course_card_action.png similarity index 100% rename from src/plugin-slots/CourseCardActionSlot/images/post_course_card_action.png rename to src/slots/CourseCardActionSlot/images/post_course_card_action.png diff --git a/src/slots/CourseCardActionSlot/index.jsx b/src/slots/CourseCardActionSlot/index.jsx new file mode 100644 index 000000000..244d25e10 --- /dev/null +++ b/src/slots/CourseCardActionSlot/index.jsx @@ -0,0 +1,15 @@ +import PropTypes from 'prop-types'; +import { Slot } from '@openedx/frontend-base'; + +const CourseCardActionSlot = ({ cardId }) => ( + +); + +CourseCardActionSlot.propTypes = { + cardId: PropTypes.string.isRequired, +}; + +export default CourseCardActionSlot; diff --git a/src/slots/CourseListSlot/README.md b/src/slots/CourseListSlot/README.md new file mode 100644 index 000000000..693e00489 --- /dev/null +++ b/src/slots/CourseListSlot/README.md @@ -0,0 +1,54 @@ +# Course List Slot + +### Slot ID: `org.openedx.frontend.slot.learnerDashboard.courseList.v1` + +### Slot Props + +* `courseListData` + +## Description + +This slot is used for replacing or adding content around the `CourseList` component. The `CourseListSlot` is only rendered if the learner has enrolled in at least one course. + +## Example + +The space will show the `CourseList` component by default. + +![Screenshot of the CourseListSlot](./images/course_list_slot.png) + +Using the following configuration will replace the slot's default content with a list of course titles. + +![Screenshot of a custom course list](./images/readme_custom_course_list.png) + +```js +import { WidgetOperationTypes } from '@openedx/frontend-base'; + +const myComponent = ({ courseListData }) => { + // Extract the "visibleList" + const courses = courseListData.visibleList; + + // Render a list of course names + return ( +
+ {courses.map(courseData => ( +

+ {courseData.course.courseName} +

+ ))} +
+ ) +} + +const config = { + slots: [ + { + slotId: 'org.openedx.frontend.slot.learnerDashboard.courseList.v1', + id: 'my.widget', + op: WidgetOperationTypes.REPLACE, + component: myComponent + }, + ] +} + +export default config; +``` diff --git a/src/plugin-slots/CourseListSlot/images/course_list_slot.png b/src/slots/CourseListSlot/images/course_list_slot.png similarity index 100% rename from src/plugin-slots/CourseListSlot/images/course_list_slot.png rename to src/slots/CourseListSlot/images/course_list_slot.png diff --git a/src/plugin-slots/CourseListSlot/images/readme_custom_course_list.png b/src/slots/CourseListSlot/images/readme_custom_course_list.png similarity index 100% rename from src/plugin-slots/CourseListSlot/images/readme_custom_course_list.png rename to src/slots/CourseListSlot/images/readme_custom_course_list.png diff --git a/src/slots/CourseListSlot/index.jsx b/src/slots/CourseListSlot/index.jsx new file mode 100644 index 000000000..f7becb900 --- /dev/null +++ b/src/slots/CourseListSlot/index.jsx @@ -0,0 +1,17 @@ +import { Slot } from '@openedx/frontend-base'; +import { CourseList, courseListDataShape } from '../../containers/CoursesPanel/CourseList'; + +export const CourseListSlot = ({ courseListData }) => ( + + + +); + +CourseListSlot.propTypes = { + courseListData: courseListDataShape, +}; + +export default CourseListSlot; diff --git a/src/slots/DashboardModalSlot/README.md b/src/slots/DashboardModalSlot/README.md new file mode 100644 index 000000000..50cda6887 --- /dev/null +++ b/src/slots/DashboardModalSlot/README.md @@ -0,0 +1,30 @@ +# Dashboard Modal Slot + +### Slot ID: `org.openedx.frontend.slot.learnerDashboard.dashboardModal.v1` + +## Description + +This slot is used for the modal on a dashboard. The following configuration will render it. + +## Example + +Learner dashboard will show modal +![Screenshot of the dashboard modal](./images/dashboard_modal_slot.png) + +```js +import { WidgetOperationTypes } from '@openedx/frontend-base'; +import { ModalDialog } from '@openedx/paragon'; + +const config = { + slots: [ + { + slotId: 'org.openedx.frontend.slot.learnerDashboard.dashboardModal.v1', + id: 'my.widget', + op: WidgetOperationTypes.Append, + element: () + }, + ] +} + +export default config; +``` diff --git a/src/plugin-slots/DashboardModalSlot/images/dashboard_modal_slot.png b/src/slots/DashboardModalSlot/images/dashboard_modal_slot.png similarity index 100% rename from src/plugin-slots/DashboardModalSlot/images/dashboard_modal_slot.png rename to src/slots/DashboardModalSlot/images/dashboard_modal_slot.png diff --git a/src/slots/DashboardModalSlot/index.jsx b/src/slots/DashboardModalSlot/index.jsx new file mode 100644 index 000000000..dfebd7b52 --- /dev/null +++ b/src/slots/DashboardModalSlot/index.jsx @@ -0,0 +1,7 @@ +import { Slot } from '@openedx/frontend-base'; + +const DashboardModalSlot = () => ( + +); + +export default DashboardModalSlot; diff --git a/src/slots/NoCoursesViewSlot/README.md b/src/slots/NoCoursesViewSlot/README.md new file mode 100644 index 000000000..59e7cd666 --- /dev/null +++ b/src/slots/NoCoursesViewSlot/README.md @@ -0,0 +1,34 @@ +# No Courses View Slot + +### Slot ID: `org.openedx.frontend.slot.learnerDashboard.noCoursesView.v1` + +## Description + +This slot is used for replacing or adding content around the `NoCoursesView` component. The `NoCoursesViewSlot` only renders if the learner has not yet enrolled in any courses. + +## Example + +The space will show the `NoCoursesView` by default. + +![Screenshot of the no courses view](./images/no_courses_view_slot.png) + +Setting configuration to the following will replace the default experience with a custom call-to-action component. + +![Screenshot of a custom no courses view](./images/readme_custom_no_courses_view.png) + +```js +import { WidgetOperationTypes } from '@openedx/frontend-base'; + +const config = { + slots: [ + { + slotId: 'org.openedx.frontend.slot.learnerDashboard.noCoursesView.v1', + id: 'my.widget', + op: WidgetOperationTypes.APPEND, + element: (

Check out our catalog of courses and start learning today!

) + }, + ] +} + +export default config; +``` diff --git a/src/plugin-slots/NoCoursesViewSlot/images/no_course_view_slot.png b/src/slots/NoCoursesViewSlot/images/no_course_view_slot.png similarity index 100% rename from src/plugin-slots/NoCoursesViewSlot/images/no_course_view_slot.png rename to src/slots/NoCoursesViewSlot/images/no_course_view_slot.png diff --git a/src/plugin-slots/NoCoursesViewSlot/images/readme_custom_no_courses.png b/src/slots/NoCoursesViewSlot/images/readme_custom_no_courses.png similarity index 100% rename from src/plugin-slots/NoCoursesViewSlot/images/readme_custom_no_courses.png rename to src/slots/NoCoursesViewSlot/images/readme_custom_no_courses.png diff --git a/src/slots/NoCoursesViewSlot/index.jsx b/src/slots/NoCoursesViewSlot/index.jsx new file mode 100644 index 000000000..06b1600b7 --- /dev/null +++ b/src/slots/NoCoursesViewSlot/index.jsx @@ -0,0 +1,11 @@ +import { Slot } from '@openedx/frontend-base'; + +import NoCoursesView from '../../containers/CoursesPanel/NoCoursesView'; + +export const NoCoursesViewSlot = () => ( + + + +); + +export default NoCoursesViewSlot; diff --git a/src/slots/README.md b/src/slots/README.md new file mode 100644 index 000000000..ba7937a27 --- /dev/null +++ b/src/slots/README.md @@ -0,0 +1,8 @@ +# `frontend-app-learner-dashboard` Slots + +* [`org.openedx.frontend.slot.learnerDashboard.courseCardBanner.v1`](./CourseBannerSlot/) +* [`org.openedx.frontend.slot.learnerDashboard.courseCardAction.v1`](./CourseCardActionSlot/) +* [`org.openedx.frontend.slot.learnerDashboard.courseList.v1`](./CourseListSlot/) +* [`org.openedx.frontend.slot.learnerDashboard.dashboardModal.v1`](./DashboardModalSlot) +* [`org.openedx.frontend.slot.learnerDashboard.noCoursesView.v1`](./NoCoursesViewSlot/) +* [`org.openedx.frontend.slot.learnerDashboard.widgetSidebar.v1`](./WidgetSidebarSlot/) diff --git a/src/slots/WidgetSidebarSlot/README.md b/src/slots/WidgetSidebarSlot/README.md new file mode 100644 index 000000000..3eb3869c8 --- /dev/null +++ b/src/slots/WidgetSidebarSlot/README.md @@ -0,0 +1,51 @@ +# Widget Sidebar Slot + +### Slot ID: `org.openedx.frontend.slot.learnerDashboard.widgetSidebar.v1` + +## Description + +This slot is used for adding content to the right-hand sidebar. + +## Example + +The slot renders the "Looking for a Challenge" widget by default. + +![Screenshot of the widget sidebar](./images/widget_sidebar_slot.png) + +Setting configuration to the following will replace the default content with a custom sidebar component. + +![Screenshot of a custom call-to-action in the sidebar](./images/readme_custom_sidebar.png) + +```js +import { WidgetOperationTypes } from '@openedx/frontend-base'; + +const myComponent = () => ( +
+

+ Sidebar Menu +

+

+ sidebar item #1 +

+

+ sidebar item #2 +

+

+ sidebar item #3 +

+
+) + +const config = { + slots: [ + { + slotId: 'org.openedx.frontend.slot.learnerDashboard.widgetSidebar.v1', + id: 'my.widget', + op: WidgetOperationTypes.REPLACE, + component: myComponent + }, + ] +} + +export default config; +``` diff --git a/src/plugin-slots/WidgetSidebarSlot/images/readme_custom_sidebar.png b/src/slots/WidgetSidebarSlot/images/readme_custom_sidebar.png similarity index 100% rename from src/plugin-slots/WidgetSidebarSlot/images/readme_custom_sidebar.png rename to src/slots/WidgetSidebarSlot/images/readme_custom_sidebar.png diff --git a/src/plugin-slots/WidgetSidebarSlot/images/widget_sidebar_slot.png b/src/slots/WidgetSidebarSlot/images/widget_sidebar_slot.png similarity index 100% rename from src/plugin-slots/WidgetSidebarSlot/images/widget_sidebar_slot.png rename to src/slots/WidgetSidebarSlot/images/widget_sidebar_slot.png diff --git a/src/slots/WidgetSidebarSlot/index.jsx b/src/slots/WidgetSidebarSlot/index.jsx new file mode 100644 index 000000000..69ddb605a --- /dev/null +++ b/src/slots/WidgetSidebarSlot/index.jsx @@ -0,0 +1,10 @@ +import { Slot } from '@openedx/frontend-base'; +import LookingForChallengeWidget from '../../widgets/LookingForChallengeWidget'; + +export const WidgetSidebarSlot = () => ( + + + +); + +export default WidgetSidebarSlot; diff --git a/src/slots/WidgetSidebarSlot/index.test.jsx b/src/slots/WidgetSidebarSlot/index.test.jsx new file mode 100644 index 000000000..9aba780e8 --- /dev/null +++ b/src/slots/WidgetSidebarSlot/index.test.jsx @@ -0,0 +1,26 @@ +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; +import { MemoryRouter } from 'react-router'; +import { useInitializeLearnerHome } from '@src/data/hooks'; +import WidgetSidebarSlot from '.'; + +jest.mock('@src/data/hooks', () => ({ + useInitializeLearnerHome: jest.fn(), +})); + +const courseSearchUrl = 'mock-url'; + +describe('WidgetSidebar', () => { + it('renders PluginSlot with correct children', () => { + useInitializeLearnerHome.mockReturnValueOnce({ data: { platformSettings: { courseSearchUrl } } }); + render( + + + + + , + ); + const pluginSlot = screen.getByText('Looking for a new challenge?'); + expect(pluginSlot).toBeDefined(); + }); +}); diff --git a/src/style.scss b/src/style.scss new file mode 100644 index 000000000..aa30042fd --- /dev/null +++ b/src/style.scss @@ -0,0 +1,36 @@ +$fa-font-path: "~font-awesome/fonts"; +@import "~font-awesome/scss/font-awesome"; + +$input-focus-box-shadow: var(--pgn-elevation-form-input-base); // hack to get upgrade to paragon 4.0.0 to work + +#learnerDashboardRoot { + main { + flex-grow: 1; + } + + // Removing a odd 1.5 scaling on checkboxes.: + input[type=checkbox] { + transform: none; + } + + .text-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .alert.alert-info .alert-icon { + color: black; + } + + #paragon-portal-root { + .pgn__modal-layer { + .pgn__modal-close-container { + right: 1rem !important; + } + } + .confirm-modal .pgn__modal-body { + overflow: hidden; + } + } +} diff --git a/src/test/app.test.jsx b/src/test/app.test.jsx deleted file mode 100644 index f3380a2b8..000000000 --- a/src/test/app.test.jsx +++ /dev/null @@ -1,283 +0,0 @@ -/* eslint-disable */ -import React from 'react'; -import * as redux from 'redux'; -import { Provider } from 'react-redux'; -import { - act, - render, - waitFor, - within, - prettyDOM, -} from '@testing-library/react'; -import { - initialize, - mergeConfig, -} from '@edx/frontend-platform'; - -import { useIntl, IntlProvider } from '@edx/frontend-platform/i18n'; - -import { useFormatDate } from 'utils/hooks'; - -import api from 'data/services/lms/api'; -import * as fakeData from 'data/services/lms/fakeData/courses'; -import { RequestKeys, RequestStates } from 'data/constants/requests'; -import reducers from 'data/redux'; -import { selectors } from 'data/redux'; -import { apiHooks } from 'hooks'; -import { cardId as genCardId } from 'data/redux/app/reducer'; - -import messages from 'i18n'; - -import App from 'App'; -import Inspector from './inspector'; -import appMessages from './messages'; - -jest.unmock('@openedx/paragon'); -jest.unmock('@openedx/paragon/icons'); -jest.unmock('@edx/frontend-platform/i18n'); -jest.unmock('@edx/frontend-component-footer'); -jest.unmock('react'); -jest.unmock('react-redux'); -jest.unmock('reselect'); -jest.unmock('hooks'); - -jest.mock('plugin-slots/WidgetSidebarSlot', () => jest.fn(() => 'widget-sidebar')); -jest.mock('components/NoticesWrapper', () => 'notices-wrapper'); - -jest.mock('@edx/frontend-platform', () => ({ - ...jest.requireActual('@edx/frontend-platform'), - getConfig: () => jest.requireActual('../config').configuration, -})); - -jest.mock('@edx/frontend-platform/analytics', () => ({ - sendTrackEvent: jest.fn(), -})); - -jest.mock('@edx/frontend-platform/auth', () => ({ - getAuthenticatedHttpClient: jest.fn(), - getLoginRedirectUrl: jest.fn(), -})); - -jest.mock('@edx/frontend-enterprise-hotjar', () => ({ - initializeHotjar: jest.fn(), -})); - -jest.mock('@edx/frontend-platform/i18n', () => ({ - ...jest.requireActual('@edx/frontend-platform/i18n'), - useIntl: () => ({ - formatMessage: jest.requireActual('testUtils').formatMessage, - formatDate: (date) => `Date-${date}`, - }), -})); - -jest.mock('@edx/frontend-platform/logging', () => ({ - logError: jest.fn(), -})); - -jest.mock('utils/hooks', () => { - const formatDate = jest.fn(date => `Date-${date}`); - return { - formatDate, - useFormatDate: () => formatDate, - }; -}); - - -const configureStore = () => redux.createStore( - reducers, -); - -let el; -let store; -let state; -let retryLink; -let inspector; - -/** - * Simple wrapper for updating the top-level state variable, that also returns the new value - * @return {obj} - current redux store state - */ -const getState = () => { - state = store.getState(); - return state; -}; - -/** - * Object to be filled with resolve/reject functions for all controlled network comm channels - */ -const resolveFns = { -}; -/** - * Mock the api with jest functions that can be tested against. - */ -const mockNetworkError = (reject) => () => reject(new Error({ - response: { status: ErrorStatuses.badRequest }, -})); - -const mockForbiddenError = (reject) => () => reject(new Error({ - response: { status: ErrorStatuses.forbidden }, -})); - - -const allCourses = [ - ...fakeData.courseRunData, - ...fakeData.entitlementData, -]; - -const { compileCourseRunData, compileEntitlementData } = fakeData; - -const initCourses = jest.fn(() => []); - -let initializeApp; - -const mockApi = () => { - api.initializeList = jest.fn(() => new Promise( - (resolve, reject) => { - resolveFns.init = { - success: () => { - const data = { - courses: initCourses(), - ...fakeData.globalData, - }; - resolve({ data }); - }, - }; - })); -}; - -/** - * load and configure the store, render the element, and populate the top-level state object - */ -const renderEl = async () => { - store = configureStore(); - el = await render( - - - - - , - ); - getState(); -}; - -const waitForEqual = async (valFn, expected, key) => waitFor(() => { - expect(valFn(), `${key} is expected to equal ${expected}`).toEqual(expected); -}); -const waitForRequestStatus = (key, status) => waitForEqual( - () => getState().requests[key].status, - status, - key, -); - -const loadApp = async (courses) => { - initCourses.mockReturnValue(courses.map(compileCourseRunData)); - await renderEl(); - inspector = new Inspector(el); - await waitForRequestStatus(RequestKeys.initialize, RequestStates.pending); - resolveFns.init.success(); - await waitForRequestStatus(RequestKeys.initialize, RequestStates.completed); -} - -const courseNames = [ - 'course-name-1', - 'course-name-2', - 'course-name-3', -]; - -describe('ESG app integration tests', () => { - beforeEach(() => { - mockApi(); - }); - - test('initialization', async () => { - await loadApp([{ courseName: courseNames[0] }]); - }); - - describe('course cards', () => { - const courseNames = [ - 'course-name-0', - 'course-name-1', - 'course-name-2', - ]; - const testCourse = async (index, tests) => { - await getState(); - const cards = inspector.get.courseCards; - const card = cards.at(index); - const cardId = genCardId(index); - const cardDetails = inspector.get.card.details(card); - const courseData = selectors.app.courseCard.course(state, cardId); - const { courseName } = selectors.app.courseCard.course(state, cardId); - inspector.verifyText(inspector.get.card.header(card), courseName); - if (tests.length > index) { - tests[index]({ cardId, cardDetails }); - } - } - - const loadCourse = async (course) => { - await loadApp([course].map(compileCourseRunData)); - await waitForRequestStatus(RequestKeys.initialize, RequestStates.pending); - resolveFns.init.success(); - await waitForRequestStatus(RequestKeys.initialize, RequestStates.completed); - }; - - describe('audit courses', () => { - test('audit', async () => { - const courses = [ - { courseName: courseNames[0] }, // audit, course run not started - { - courseName: courseNames[1], - enrollment: { - coursewareAccess: { - isTooEarly: true, - hasUnmetPrerequisites: false, - isStaff: false, - }, - }, - }, // audit, course run not started, is too early - { - courseName: courseNames[2], - courseRun: { - courseRun: { isStarted: true }, - }, - enrollment: { - accessExpirationDate: fakeData.pastDate, - canUpgrade: false, - isAuditAccessExpired: true, - hasStarted: true, - }, - }, // audit, course run and learner started, access expired, cannot upgrade - ]; - const formatDate = useFormatDate(); - await loadApp(courses); - await testCourse(0, [ - ({ cardId, cardDetails }) => { - const enrollment = selectors.app.courseCard.enrollment(state, cardId); - const courseRun = selectors.app.courseCard.courseRun(state, cardId); - const courseProvider = selectors.app.courseCard.courseProvider(state, cardId); - const course = selectors.app.courseCard.course(state, cardId); - expect(enrollment.isAudit).toEqual(true); - expect(courseRun.isStarted).toEqual(false); - expect(enrollment.canUpgrade).toEqual(true); - [ - courseProvider.name, - course.courseNumber, - appMessages.withValues.CourseCardDetails.courseStarts({ - startDate: formatDate(new Date(courseRun.startDate)), - }), - ].forEach(value => inspector.verifyTextIncludes(cardDetails, value)); - }, - ]); - await testCourse(1, [ - ({ cardId, cardDetails }) => { - const enrollment = selectors.app.courseCard.enrollment(state, cardId); - const courseRun = selectors.app.courseCard.courseRun(state, cardId); - expect(enrollment.isAudit).toEqual(true); - expect(courseRun.isStarted).toEqual(false); - expect(enrollment.coursewareAccess.isTooEarly).toEqual(true); - expect(enrollment.hasAccess).toEqual(false); - }, - ]); - }); - }); - }); -}); diff --git a/src/test/inspector.js b/src/test/inspector.js deleted file mode 100644 index c04bf5e5b..000000000 --- a/src/test/inspector.js +++ /dev/null @@ -1,50 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { within } from '@testing-library/react'; - -// import fakeData from 'data/services/lms/fakeData'; -// import { gradingStatusTransform } from 'data/redux/grading/selectors/selected'; - -// import appMessages from './messages'; - -/** - * App inspector class providing methods to return elements from within - * the virtual DOM - * @props {Root Node} el - Root app render node. - */ -class Inspector { - constructor(el) { - this.el = el; - this.getByRole = this.el.getByRole; - this.getByText = this.el.getByText; - this.getByLabelText = this.el.getByLabelText; - this.findByText = this.el.findByText; - this.findByLabelText = this.el.findByLabelText; - } - - get get() { - return { - courseCards: this.el.getAllByTestId('CourseCard'), - card: { - header: (card) => within(card).getByTestId('CourseCardTitle'), - details: (card) => within(card).getByTestId('CourseCardDetails'), - // banners: (card) => within(card).getByTestId('CourseCardBanners'), - // programsBadge: (card) => within(card).getByTestId('RelatedProgramsBadge'), - // actions: (card) => within(card).getByTestId('CourseCardActions'), - }, - }; - } - - /** - * Returns promises for attempting to find elements within the DOM - */ - get find() { - return { - }; - } - - verifyText = (el, text) => within(el).getByText(text); - - verifyTextIncludes = (el, text) => within(el).getByText(text, { exact: false }); -} - -export default Inspector; diff --git a/src/test/messages.js b/src/test/messages.js deleted file mode 100644 index 16bf61c8e..000000000 --- a/src/test/messages.js +++ /dev/null @@ -1,29 +0,0 @@ -import CourseCardDetails from 'containers/CourseCard/components/CourseCardDetails/messages'; - -const mapMessages = (messages) => Object.keys(messages).reduce( - (acc, key) => ({ ...acc, [key]: messages[key].defaultMessage }), - {}, -); - -const mapMessagesWithValues = (messages) => Object.keys(messages).reduce( - (acc, key) => ({ - ...acc, - [key]: (values) => { - let message = messages[key].defaultMessage; - if (values) { - Object.keys(values).forEach(valueKey => { - message = message.replaceAll(`{${valueKey}}`, values[valueKey]); - }); - } - return message; - }, - }), - {}, -); - -export default { - CourseCardDetails: mapMessages(CourseCardDetails), - withValues: { - CourseCardDetails: mapMessagesWithValues(CourseCardDetails), - }, -}; diff --git a/src/test/utils.js b/src/test/utils.js deleted file mode 100644 index 7eba5e312..000000000 --- a/src/test/utils.js +++ /dev/null @@ -1,3 +0,0 @@ -export const mockSuccess = (returnValFn) => (...args) => Promise.resolve(returnValFn(...args)); - -export const mockFailure = (returnValFn) => (...args) => Promise.reject(returnValFn(...args)); diff --git a/src/testUtils.js b/src/testUtils.js index 7955134fa..a052d1665 100644 --- a/src/testUtils.js +++ b/src/testUtils.js @@ -1,6 +1,6 @@ import react from 'react'; -import { StrictDict } from 'utils'; +import { StrictDict } from '@src/utils'; /** * Mocked formatMessage provided by react-intl @@ -12,11 +12,9 @@ export const formatMessage = (msg, values) => { } // check if value is not a primitive type. if (Object.values(values).filter(value => Object(value) === value).length) { - // eslint-disable-next-line react/jsx-filename-extension return ; } Object.keys(values).forEach((key) => { - // eslint-disable-next-line message = message.replaceAll(`{${key}}`, values[key]); }); return message; diff --git a/src/tracking/constants.js b/src/tracking/constants.js index 731d2fba9..734ffc637 100644 --- a/src/tracking/constants.js +++ b/src/tracking/constants.js @@ -1,4 +1,4 @@ -import { StrictDict } from 'utils'; +import { StrictDict } from '../utils'; export const categories = StrictDict({ dashboard: 'dashboard', diff --git a/src/tracking/trackers/course.js b/src/tracking/trackers/course.js index 8513692ae..34b3905f9 100644 --- a/src/tracking/trackers/course.js +++ b/src/tracking/trackers/course.js @@ -1,4 +1,4 @@ -import { createEventTracker, createLinkTracker } from 'data/services/segment/utils'; +import { createEventTracker, createLinkTracker } from '../../data/services/segment/utils'; import { categories, eventNames } from '../constants'; import * as module from './course'; diff --git a/src/tracking/trackers/course.test.js b/src/tracking/trackers/course.test.js index 7715b1133..1f6e21b4c 100644 --- a/src/tracking/trackers/course.test.js +++ b/src/tracking/trackers/course.test.js @@ -1,9 +1,9 @@ -import { keyStore } from 'utils'; -import { createEventTracker, createLinkTracker } from 'data/services/segment/utils'; -import { categories, eventNames } from '../constants'; +import { keyStore } from '@src/utils'; +import { createEventTracker, createLinkTracker } from '@src/data/services/segment/utils'; +import { categories, eventNames } from '@src/tracking/constants'; import * as trackers from './course'; -jest.mock('data/services/segment/utils', () => ({ +jest.mock('@src/data/services/segment/utils', () => ({ createEventTracker: jest.fn(args => ({ createEventTracker: args })), createLinkTracker: jest.fn((cb, href) => ({ createLinkTracker: { cb, href } })), })); diff --git a/src/tracking/trackers/credit.js b/src/tracking/trackers/credit.js index b14175e3d..aeb27322b 100644 --- a/src/tracking/trackers/credit.js +++ b/src/tracking/trackers/credit.js @@ -1,5 +1,5 @@ -import { createEventTracker, createLinkTracker } from 'data/services/segment/utils'; -import { creditPurchaseUrl } from 'data/services/lms/urls'; +import { createEventTracker, createLinkTracker } from '../../data/services/segment/utils'; +import { creditPurchaseUrl } from '../../data/services/lms/urls'; import { categories, eventNames } from '../constants'; /** diff --git a/src/tracking/trackers/credit.test.js b/src/tracking/trackers/credit.test.js index 69b536c79..cfc83dd93 100644 --- a/src/tracking/trackers/credit.test.js +++ b/src/tracking/trackers/credit.test.js @@ -1,15 +1,15 @@ -import { createEventTracker, createLinkTracker } from 'data/services/segment/utils'; -import { creditPurchaseUrl } from 'data/services/lms/urls'; +import { createEventTracker, createLinkTracker } from '@src/data/services/segment/utils'; +import { creditPurchaseUrl } from '@src/data/services/lms/urls'; -import { eventNames, categories } from '../constants'; +import { eventNames, categories } from '@src/tracking/constants'; import * as trackers from './credit'; -jest.mock('data/services/segment/utils', () => ({ +jest.mock('@src/data/services/segment/utils', () => ({ createEventTracker: jest.fn(args => ({ createEventTracker: args })), createLinkTracker: jest.fn((cb, href) => ({ createLinkTracker: { cb, href } })), })); -jest.mock('data/services/lms/urls', () => ({ +jest.mock('@src/data/services/lms/urls', () => ({ creditPurchaseUrl: jest.fn(courseId => `credit-purchase-url/${courseId}`), })); diff --git a/src/tracking/trackers/engagement.js b/src/tracking/trackers/engagement.js index a8475f1f2..d56deae18 100644 --- a/src/tracking/trackers/engagement.js +++ b/src/tracking/trackers/engagement.js @@ -1,4 +1,4 @@ -import { createEventTracker } from 'data/services/segment/utils'; +import { createEventTracker } from '../../data/services/segment/utils'; import { categories, eventNames } from '../constants'; export const engagementOptions = { diff --git a/src/tracking/trackers/engagement.test.js b/src/tracking/trackers/engagement.test.js index 904450dd1..a8587a8f2 100644 --- a/src/tracking/trackers/engagement.test.js +++ b/src/tracking/trackers/engagement.test.js @@ -1,8 +1,8 @@ -import { createEventTracker } from 'data/services/segment/utils'; -import { eventNames } from '../constants'; +import { createEventTracker } from '@src/data/services/segment/utils'; +import { eventNames } from '@src/tracking/constants'; import * as trackers from './engagement'; -jest.mock('data/services/segment/utils', () => ({ +jest.mock('@src/data/services/segment/utils', () => ({ createEventTracker: jest.fn(args => ({ createEventTracker: args })), })); diff --git a/src/tracking/trackers/entitlements.js b/src/tracking/trackers/entitlements.js index 995688ab2..684b5ab17 100644 --- a/src/tracking/trackers/entitlements.js +++ b/src/tracking/trackers/entitlements.js @@ -1,4 +1,4 @@ -import { createEventTracker } from 'data/services/segment/utils'; +import { createEventTracker } from '../../data/services/segment/utils'; import { eventNames } from '../constants'; /** diff --git a/src/tracking/trackers/entitlements.test.js b/src/tracking/trackers/entitlements.test.js index e3aaacc95..f4f3f4044 100644 --- a/src/tracking/trackers/entitlements.test.js +++ b/src/tracking/trackers/entitlements.test.js @@ -1,8 +1,8 @@ -import { createEventTracker } from 'data/services/segment/utils'; -import { eventNames } from '../constants'; +import { createEventTracker } from '@src/data/services/segment/utils'; +import { eventNames } from '@src/tracking/constants'; import * as trackers from './entitlements'; -jest.mock('data/services/segment/utils', () => ({ +jest.mock('@src/data/services/segment/utils', () => ({ createEventTracker: jest.fn(args => ({ createEventTracker: args })), })); diff --git a/src/tracking/trackers/filter.js b/src/tracking/trackers/filter.js index d4193b1ef..4239d0db8 100644 --- a/src/tracking/trackers/filter.js +++ b/src/tracking/trackers/filter.js @@ -1,4 +1,4 @@ -import { createEventTracker } from 'data/services/segment/utils'; +import { createEventTracker } from '../../data/services/segment/utils'; import { categories, eventNames } from '../constants'; export const filterClicked = () => createEventTracker( diff --git a/src/tracking/trackers/filter.test.js b/src/tracking/trackers/filter.test.js index ce4f4a918..9b48af6e6 100644 --- a/src/tracking/trackers/filter.test.js +++ b/src/tracking/trackers/filter.test.js @@ -1,8 +1,8 @@ -import { createEventTracker } from 'data/services/segment/utils'; -import { eventNames, categories } from '../constants'; +import { createEventTracker } from '@src/data/services/segment/utils'; +import { eventNames, categories } from '@src/tracking/constants'; import * as trackers from './filter'; -jest.mock('data/services/segment/utils', () => ({ +jest.mock('@src/data/services/segment/utils', () => ({ createEventTracker: jest.fn(() => () => {}), })); diff --git a/src/tracking/trackers/findCourses.js b/src/tracking/trackers/findCourses.js index 495f32a36..53ed2e427 100644 --- a/src/tracking/trackers/findCourses.js +++ b/src/tracking/trackers/findCourses.js @@ -1,4 +1,4 @@ -import { createLinkTracker, createEventTracker } from 'data/services/segment/utils'; +import { createLinkTracker, createEventTracker } from '../../data/services/segment/utils'; import { categories, eventNames } from '../constants'; export const findCoursesClicked = (href, args = {}) => createLinkTracker( diff --git a/src/tracking/trackers/findCourses.test.js b/src/tracking/trackers/findCourses.test.js index 8737dbff3..45c8fd408 100644 --- a/src/tracking/trackers/findCourses.test.js +++ b/src/tracking/trackers/findCourses.test.js @@ -1,8 +1,8 @@ -import { createLinkTracker, createEventTracker } from 'data/services/segment/utils'; +import { createLinkTracker, createEventTracker } from '@src/data/services/segment/utils'; import { findCoursesClicked } from './findCourses'; -import { categories, eventNames } from '../constants'; +import { categories, eventNames } from '@src/tracking/constants'; -jest.mock('data/services/segment/utils', () => ({ +jest.mock('@src/data/services/segment/utils', () => ({ createEventTracker: jest.fn((args) => ({ createEventTracker: args })), createLinkTracker: jest.fn((args) => ({ createLinkTracker: args })), })); diff --git a/src/tracking/trackers/socialShare.js b/src/tracking/trackers/socialShare.js index b4bae2af0..75920ccc2 100644 --- a/src/tracking/trackers/socialShare.js +++ b/src/tracking/trackers/socialShare.js @@ -1,4 +1,4 @@ -import api from 'data/services/lms/api'; +import { logShare } from '@src/data/services/lms/api'; /** * Track Social Share event click. @@ -6,6 +6,6 @@ import api from 'data/services/lms/api'; * @param {string} site - sharing destination ('facebook', 'twitter') * @return {func} - Callback that tracks the event when fired. */ -export const shareClicked = (courseId, site) => () => api.logShare({ courseId, site }); +export const shareClicked = (courseId, site) => () => logShare({ courseId, site }); export default shareClicked; diff --git a/src/tracking/trackers/socialShare.test.js b/src/tracking/trackers/socialShare.test.js index 74dfe8c30..c9a79110d 100644 --- a/src/tracking/trackers/socialShare.test.js +++ b/src/tracking/trackers/socialShare.test.js @@ -1,7 +1,7 @@ -import api from 'data/services/lms/api'; +import api from '@src/data/services/lms/api'; import * as trackers from './socialShare'; -jest.mock('data/services/lms/api', () => ({ +jest.mock('@src/data/services/lms/api', () => ({ logShare: jest.fn(args => ({ logShare: args })), })); diff --git a/src/utils/StrictDict.js b/src/utils/StrictDict.js index a127e48a7..941089950 100644 --- a/src/utils/StrictDict.js +++ b/src/utils/StrictDict.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ const strictGet = (target, name) => { if (name === Symbol.toStringTag) { return target; @@ -12,10 +11,6 @@ const strictGet = (target, name) => { return target[name]; } - console.log(name.toString()); - console.error({ target, name }); - const e = Error(`invalid property "${name.toString()}"`); - console.error(e.stack); return undefined; }; diff --git a/src/utils/StrictDict.test.js b/src/utils/StrictDict.test.js index de65e6573..5e1184510 100644 --- a/src/utils/StrictDict.test.js +++ b/src/utils/StrictDict.test.js @@ -19,7 +19,7 @@ describe('StrictDict', () => { window.console.log = jest.fn(); window.Error = jest.fn(error => ({ stack: error })); }); - afterAll(() => { + afterEach(() => { window.console.error = consoleError; window.console.log = consoleLog; window.Error = windowError; @@ -50,17 +50,9 @@ describe('StrictDict', () => { expect(Object.entries(dict)).toEqual(Object.entries(rawDict)); }); describe('missing key', () => { - it('logs error with target, name, and error stack', () => { - // eslint-ignore-next-line no-unused-vars - const callBadKey = () => dict.fakeKey; - callBadKey(); - expect(window.console.error.mock.calls).toEqual([ - [{ target: dict, name: 'fakeKey' }], - [Error('invalid property "fakeKey"').stack], - ]); - }); - it('returns undefined', () => { - expect(dict.fakeKey).toEqual(undefined); + it('returns undefined for missing key', () => { + // Accessing a missing key should return undefined + expect(dict.fakeKey).toBeUndefined(); }); }); }); diff --git a/src/utils/dataTransformers.test.ts b/src/utils/dataTransformers.test.ts new file mode 100644 index 000000000..434f200d4 --- /dev/null +++ b/src/utils/dataTransformers.test.ts @@ -0,0 +1,629 @@ +import { FilterKeys, SortKeys, ListPageSize } from '@src/data/constants/app'; +import { + getVisibleList, + getTransformedCourseDataList, + getTransformedCourseDataObject, +} from './dataTransformers'; + +const mockGet = jest.fn(); + +Object.defineProperty(window, 'location', { + value: { + search: '', + }, + writable: true, +}); + +// Mock URLSearchParams constructor +Object.defineProperty(globalThis, 'URLSearchParams', { + value: jest.fn().mockImplementation(() => ({ + get: mockGet, + })), + writable: true, +}); + +interface VisibleListResult { + visibleList: { + course: { courseName: string }, + enrollment: { lastEnrolled: Date }, + }[], + numPages: number, +} + +describe('dataTransformers', () => { + const mockCourses = [ + { + course: { + courseName: 'Introduction to React', + courseNumber: 'CS101', + }, + enrollment: { + isEnrolled: true, + isVerified: false, + hasStarted: true, + lastEnrolled: '2024-01-15T00:00:00Z', + }, + courseRun: { + isArchived: false, + }, + }, + { + course: { + courseName: 'Advanced JavaScript', + courseNumber: 'CS201', + }, + enrollment: { + isEnrolled: true, + isVerified: true, + hasStarted: false, + lastEnrolled: '2024-02-01T00:00:00Z', + }, + courseRun: { + isArchived: false, + }, + }, + { + course: { + courseName: 'Data Structures', + courseNumber: 'CS301', + }, + enrollment: { + isEnrolled: false, + isVerified: false, + hasStarted: false, + lastEnrolled: null, + }, + courseRun: null, + }, + { + course: { + courseName: 'Algorithms', + courseNumber: 'CS401', + }, + enrollment: { + isEnrolled: true, + isVerified: false, + hasStarted: true, + lastEnrolled: '2024-01-01T00:00:00Z', + }, + courseRun: { + isArchived: true, + }, + }, + ]; + + beforeEach(() => { + jest.clearAllMocks(); + mockGet.mockReturnValue(null); + window.location.search = ''; + }); + + describe('getTransformedCourseDataObject', () => { + it('should transform courses array into object with cardId keys', () => { + const result = getTransformedCourseDataObject(mockCourses); + + expect(Object.keys(result)).toEqual(['card-0', 'card-1', 'card-2', 'card-3']); + expect(result['card-0']).toMatchObject({ + ...mockCourses[0], + cardId: 'card-0', + }); + expect(result['card-1']).toMatchObject({ + ...mockCourses[1], + cardId: 'card-1', + }); + }); + + it('should add current timestamp to lastEnrolled when null', () => { + const coursesWithNullEnrollment = [ + { + course: { courseName: 'Test Course' }, + enrollment: { + lastEnrolled: null, + isEnrolled: true, + }, + }, + ]; + + const result = getTransformedCourseDataObject(coursesWithNullEnrollment); + const transformedCourse = result['card-0']; + + expect(transformedCourse.enrollment.lastEnrolled).toBeGreaterThan(0); + expect(typeof transformedCourse.enrollment.lastEnrolled).toBe('number'); + }); + + it('should preserve existing lastEnrolled timestamp', () => { + const existingTimestamp = '2024-01-15T00:00:00Z'; + const coursesWithTimestamp = [ + { + enrollment: { + lastEnrolled: existingTimestamp, + }, + }, + ]; + + const result = getTransformedCourseDataObject(coursesWithTimestamp); + + expect(result['card-0'].enrollment.lastEnrolled).toBe(existingTimestamp); + }); + + it('should handle empty courses array', () => { + const result = getTransformedCourseDataObject([]); + + expect(result).toEqual({}); + expect(Object.keys(result)).toHaveLength(0); + }); + + it('should handle courses without enrollment property', () => { + const coursesWithoutEnrollment = [ + { + course: { courseName: 'Test Course' }, + }, + ]; + + const result = getTransformedCourseDataObject(coursesWithoutEnrollment); + + expect(result['card-0']).toMatchObject({ + course: { courseName: 'Test Course' }, + cardId: 'card-0', + }); + }); + + it('should generate sequential cardId for multiple courses', () => { + const manyCourses = Array.from({ length: 5 }, (_, i) => ({ + course: { courseName: `Course ${i}` }, + })); + + const result = getTransformedCourseDataObject(manyCourses); + + expect(Object.keys(result)).toEqual([ + 'card-0', 'card-1', 'card-2', 'card-3', 'card-4', + ]); + }); + }); + + describe('getTransformedCourseDataList', () => { + it('should transform courses array into array with cardId properties', () => { + const result = getTransformedCourseDataList(mockCourses); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(4); + expect(result[0]).toMatchObject({ + ...mockCourses[0], + cardId: 'card-0', + }); + expect(result[1]).toMatchObject({ + ...mockCourses[1], + cardId: 'card-1', + }); + }); + + it('should return empty array for empty input', () => { + const result = getTransformedCourseDataList([]); + + expect(result).toEqual([]); + }); + + it('should maintain course order', () => { + interface MockCourseType { + course: { courseName: string }, + } + const result = getTransformedCourseDataList(mockCourses) as MockCourseType[]; + + expect(result[0].course.courseName).toBe('Introduction to React'); + expect(result[1].course.courseName).toBe('Advanced JavaScript'); + expect(result[2].course.courseName).toBe('Data Structures'); + expect(result[3].course.courseName).toBe('Algorithms'); + }); + }); + + describe('getVisibleList', () => { + const transformedCourses = [ + { + course: { courseName: 'Introduction to React' }, + enrollment: { + isEnrolled: true, + isVerified: false, + hasStarted: true, + lastEnrolled: new Date('2024-01-15'), + }, + courseRun: { isArchived: false }, + }, + { + course: { courseName: 'Advanced JavaScript' }, + enrollment: { + isEnrolled: true, + isVerified: true, + hasStarted: false, + lastEnrolled: new Date('2024-02-01'), + }, + courseRun: { isArchived: false }, + }, + { + course: { courseName: 'Data Structures' }, + enrollment: { + isEnrolled: false, + isVerified: false, + hasStarted: false, + lastEnrolled: new Date('2024-01-10'), + }, + courseRun: null, + }, + { + course: { courseName: 'Algorithms' }, + enrollment: { + isEnrolled: true, + isVerified: false, + hasStarted: true, + lastEnrolled: new Date('2024-01-01'), + }, + courseRun: { isArchived: true }, + }, + ]; + + describe('filtering', () => { + it('should filter courses by notEnrolled', () => { + const result = getVisibleList( + transformedCourses, + [FilterKeys.notEnrolled], + SortKeys.title, + 1, + ) as VisibleListResult; + + expect(result.visibleList).toHaveLength(1); + expect(result.visibleList[0].course.courseName).toBe('Data Structures'); + }); + + it('should filter courses by done (archived)', () => { + const result = getVisibleList( + transformedCourses, + [FilterKeys.done], + SortKeys.title, + 1, + ) as VisibleListResult; + + expect(result.visibleList).toHaveLength(1); + expect(result.visibleList[0].course.courseName).toBe('Algorithms'); + }); + + it('should filter courses by upgraded (verified)', () => { + const result = getVisibleList( + transformedCourses, + [FilterKeys.upgraded], + SortKeys.title, + 1, + ) as VisibleListResult; + + expect(result.visibleList).toHaveLength(1); + expect(result.visibleList[0].course.courseName).toBe('Advanced JavaScript'); + }); + + it('should filter courses by inProgress (hasStarted)', () => { + const result = getVisibleList( + transformedCourses, + [FilterKeys.inProgress], + SortKeys.title, + 1, + ) as VisibleListResult; + + expect(result.visibleList).toHaveLength(2); + const courseNames = result.visibleList.map(c => c.course.courseName).sort(); + expect(courseNames).toEqual(['Algorithms', 'Introduction to React']); + }); + + it('should filter courses by notStarted', () => { + const result = getVisibleList( + transformedCourses, + [FilterKeys.notStarted], + SortKeys.title, + 1, + ) as VisibleListResult; + + expect(result.visibleList).toHaveLength(2); + const courseNames = result.visibleList.map(c => c.course.courseName).sort(); + expect(courseNames).toEqual(['Advanced JavaScript', 'Data Structures']); + }); + + it('should apply multiple filters with AND logic', () => { + const result = getVisibleList( + transformedCourses, + [FilterKeys.upgraded, FilterKeys.notStarted], + SortKeys.title, + 1, + ) as VisibleListResult; + + expect(result.visibleList).toHaveLength(1); + expect(result.visibleList[0].course.courseName).toBe('Advanced JavaScript'); + }); + + it('should return all courses when no filters applied', () => { + const result = getVisibleList( + transformedCourses, + [], + SortKeys.title, + 1, + ); + + expect(result.visibleList).toHaveLength(4); + }); + + it('should return empty list when filters match no courses', () => { + const result = getVisibleList( + transformedCourses, + [FilterKeys.notEnrolled, FilterKeys.upgraded], + SortKeys.title, + 1, + ); + + expect(result.visibleList).toHaveLength(0); + }); + }); + + describe('sorting', () => { + it('should sort by title (alphabetically)', () => { + const result = getVisibleList( + transformedCourses, + [], + SortKeys.title, + 1, + ) as VisibleListResult; + + const sortedNames = result.visibleList.map(c => c.course.courseName); + expect(sortedNames).toEqual([ + 'Advanced JavaScript', + 'Algorithms', + 'Data Structures', + 'Introduction to React', + ]); + }); + + it('should sort by enrolled date (newest first - reverse order)', () => { + const result = getVisibleList( + transformedCourses, + [], + SortKeys.enrolled, + 1, + ) as VisibleListResult; + + const enrolledDates = result.visibleList.map(c => c.enrollment.lastEnrolled); + expect(enrolledDates[0]).toEqual(new Date('2024-02-01')); + expect(enrolledDates[1]).toEqual(new Date('2024-01-15')); + expect(enrolledDates[2]).toEqual(new Date('2024-01-10')); + expect(enrolledDates[3]).toEqual(new Date('2024-01-01')); + }); + + it('should handle courses with identical sort values', () => { + const identicalCourses = [ + { + course: { courseName: 'Same Name' }, + enrollment: { lastEnrolled: new Date('2024-01-01') }, + }, + { + course: { courseName: 'Same Name' }, + enrollment: { lastEnrolled: new Date('2024-01-01') }, + }, + ]; + + const result = getVisibleList( + identicalCourses, + [], + SortKeys.title, + 1, + ); + + expect(result.visibleList).toHaveLength(2); + }); + }); + + describe('pagination', () => { + const manyCourses = Array.from({ length: 25 }, (_, i) => ({ + course: { courseName: `Course ${i.toString().padStart(2, '0')}` }, + enrollment: { + isEnrolled: true, + hasStarted: false, + lastEnrolled: new Date(`2024-01-${(i + 1).toString().padStart(2, '0')}`), + }, + courseRun: { isArchived: false }, + })); + + it('should paginate results correctly for first page', () => { + const result = getVisibleList( + manyCourses, + [], + SortKeys.title, + 1, + ) as VisibleListResult; + + expect(result.visibleList).toHaveLength(ListPageSize); + expect(result.numPages).toBe(Math.ceil(25 / ListPageSize)); + expect(result.visibleList[0].course.courseName).toBe('Course 00'); + }); + + it('should paginate results correctly for second page', () => { + const listSize = 50; + const manyCoursesList = Array.from({ length: listSize }, (_, i) => ({ + course: { courseName: `Course ${i.toString().padStart(2, '0')}` }, + })); + const result = getVisibleList( + manyCoursesList, + [], + SortKeys.title, + 2, + ) as VisibleListResult; + expect(result.visibleList).toHaveLength(listSize - ListPageSize); + const expectedFirstCourse = `Course ${ListPageSize.toString().padStart(2, '0')}`; + expect(result.visibleList[0].course.courseName).toBe(expectedFirstCourse); + }); + + it('should handle last page with fewer items', () => { + const result = getVisibleList( + manyCourses, + [], + SortKeys.title, + Math.ceil(25 / ListPageSize), + ); + + expect(result.visibleList).toHaveLength(25 % ListPageSize || ListPageSize); + }); + + it('should calculate correct number of pages', () => { + const result = getVisibleList( + manyCourses, + [], + SortKeys.title, + 1, + ); + + expect(result.numPages).toBe(Math.ceil(25 / ListPageSize)); + }); + + it('should disable pagination when query parameter is set', () => { + mockGet.mockReturnValue('1'); + + const result = getVisibleList( + manyCourses, + [], + SortKeys.title, + 1, + ); + + expect(result.visibleList).toHaveLength(25); + expect(result.numPages).toBe(1); + }); + + it('should use pagination when disable_pagination is not 1', () => { + mockGet.mockReturnValue('0'); + + const result = getVisibleList( + manyCourses, + [], + SortKeys.title, + 1, + ); + + expect(result.visibleList).toHaveLength(ListPageSize); + }); + + it('should handle empty courses array with pagination', () => { + const result = getVisibleList( + [], + [], + SortKeys.title, + 1, + ); + + expect(result.visibleList).toHaveLength(0); + expect(result.numPages).toBe(0); + }); + }); + + describe('edge cases', () => { + it('should handle courses with missing properties', () => { + const coursesWithMissingProps = [ + { + course: { courseName: 'Course 1' }, + enrollment: {}, // Missing properties + courseRun: null, + }, + ]; + + expect(() => { + getVisibleList( + coursesWithMissingProps, + [FilterKeys.inProgress], + SortKeys.title, + 1, + ); + }).not.toThrow(); + }); + + it('should handle invalid page number', () => { + const result = getVisibleList( + transformedCourses, + [], + SortKeys.title, + 0, + ); + + // Should handle gracefully, possibly returning empty or adjusting + expect(result.visibleList).toBeDefined(); + }); + + it('should handle very large page number', () => { + const result = getVisibleList( + transformedCourses, + [], + SortKeys.title, + 999, + ); + + expect(result.visibleList).toHaveLength(0); + }); + + it('should handle null courseRun in done filter', () => { + const coursesWithNullCourseRun = [ + { + course: { courseName: 'Test Course' }, + enrollment: { isEnrolled: true }, + courseRun: null, + }, + ]; + + const result = getVisibleList( + coursesWithNullCourseRun, + [FilterKeys.done], + SortKeys.title, + 1, + ); + + expect(result.visibleList).toHaveLength(0); + }); + }); + + describe('integration scenarios', () => { + it('should handle complex filtering, sorting, and pagination together', () => { + const result = getVisibleList( + transformedCourses, + [FilterKeys.inProgress], + SortKeys.enrolled, + 1, + ) as VisibleListResult; + + // Should get in-progress courses, sorted by enrollment date (newest first) + expect(result.visibleList).toHaveLength(2); + expect(result.visibleList[0].course.courseName).toBe('Introduction to React'); + expect(result.visibleList[1].course.courseName).toBe('Algorithms'); + }); + + it('should maintain functionality with realistic course data structure', () => { + const realisticCourses = [ + { + course: { + courseName: 'Introduction to Computer Science', + courseNumber: 'CS50', + }, + enrollment: { + isEnrolled: true, + isVerified: true, + hasStarted: true, + lastEnrolled: new Date('2024-01-15'), + }, + courseRun: { + isArchived: false, + startDate: '2024-02-01', + }, + }, + ]; + + const result = getVisibleList( + realisticCourses, + [], + SortKeys.title, + 1, + ) as VisibleListResult; + + expect(result.visibleList).toHaveLength(1); + expect(result.visibleList[0].course.courseName).toBe('Introduction to Computer Science'); + }); + }); + }); +}); diff --git a/src/utils/dataTransformers.ts b/src/utils/dataTransformers.ts new file mode 100644 index 000000000..73d35de17 --- /dev/null +++ b/src/utils/dataTransformers.ts @@ -0,0 +1,71 @@ +import { FilterKeys, ListPageSize, SortKeys } from '@src/data/constants/app'; +import StrictDict from './StrictDict'; + +const cardId = (val) => `card-${val}`; + +const transformCourseData = (courses) => { + const now = Date.now(); + return courses.reduce( + (obj, curr, index) => { + const out = { ...curr, cardId: cardId(index) }; + if (out.enrollment?.lastEnrolled === null) { + out.enrollment.lastEnrolled = now; + } + return { ...obj, [cardId(index)]: out }; + }, + {}, + ); +}; + +const getTransformedCourseDataObject = (courses) => transformCourseData(courses); + +const getTransformedCourseDataList = (courses) => Object.values(transformCourseData(courses)); + +const getVisibleList = (courses: any[], filters: string[], sortBy: string, pageNumber: number) => { + const courseFilters = StrictDict({ + [FilterKeys.notEnrolled]: (course) => !course.enrollment.isEnrolled, + [FilterKeys.done]: (course) => course.courseRun !== null && course.courseRun.isArchived, + [FilterKeys.upgraded]: (course) => course.enrollment.isVerified, + [FilterKeys.inProgress]: (course) => course.enrollment.hasStarted, + [FilterKeys.notStarted]: (course) => !course.enrollment.hasStarted, + }); + + const transforms = StrictDict({ + [SortKeys.enrolled]: ({ enrollment }) => new Date(enrollment?.lastEnrolled), + [SortKeys.title]: ({ course }) => course.courseName.toLowerCase(), + }); + + const courseFilterFn = filtersList => (filtersList.length + ? course => filtersList.reduce((match, filter) => match && courseFilters[filter](course), true) + : () => true); + + const sortFn = (transform, { reverse }) => (v1, v2) => { + const [a, b] = [v1, v2].map(transform); + if (a === b) { + return 0; + } + return (((a as any) > (b as any)) ? 1 : -1) * (reverse ? -1 : 1); + }; + + const list = courses + .filter(courseFilterFn(filters)) + .sort(sortFn(transforms[sortBy], { reverse: sortBy === SortKeys.enrolled })); + + const querySearch = new URLSearchParams(window.location.search); + const disablePagination = querySearch.get('disable_pagination'); + const pageSize = Number(disablePagination) === 1 ? 0 : ListPageSize; + + if (pageSize === 0) { + return { + visibleList: list, + numPages: 1, + }; + } + const result = { + visibleList: list.slice((pageNumber - 1) * pageSize, pageNumber * pageSize), + numPages: Math.ceil(list.length / pageSize), + }; + return result; +}; + +export { getVisibleList, getTransformedCourseDataList, getTransformedCourseDataObject }; diff --git a/src/utils/hooks.js b/src/utils/hooks.js index 941f931d1..5d43544c1 100644 --- a/src/utils/hooks.js +++ b/src/utils/hooks.js @@ -1,4 +1,4 @@ -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import dateFormatter from './dateFormatter'; diff --git a/src/utils/hooks.test.tsx b/src/utils/hooks.test.tsx new file mode 100644 index 000000000..fc62e934d --- /dev/null +++ b/src/utils/hooks.test.tsx @@ -0,0 +1,55 @@ +import type { ReactNode } from 'react'; +import { render } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; +import { useFormatDate } from './hooks'; +import dateFormatter from './dateFormatter'; + +jest.mock('./dateFormatter'); +const TestComponent = ({ date }: { date: Date | string }) => { + const formatDate = useFormatDate(); + const formattedDate = formatDate(date); + + return
{formattedDate}
; +}; + +const renderWithIntl = (component: ReactNode) => render( + + {component} + , +); + +describe('useFormatDate hook', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call dateFormatter with formatDate function and date', () => { + const mockDate = new Date('2024-01-15'); + const mockFormattedDate = 'January 15, 2024'; + + (dateFormatter as jest.Mock).mockReturnValue(mockFormattedDate); + + const { getByText } = renderWithIntl(); + + expect(dateFormatter).toHaveBeenCalledWith( + expect.any(Function), + mockDate, + ); + expect(getByText(mockFormattedDate)).toBeInTheDocument(); + }); + + it('should handle different date formats', () => { + const stringDate = '2024-12-25'; + const mockFormattedDate = 'December 25, 2024'; + + (dateFormatter as jest.Mock).mockReturnValue(mockFormattedDate); + + const { getByText } = renderWithIntl(); + + expect(dateFormatter).toHaveBeenCalledWith( + expect.any(Function), + stringDate, + ); + expect(getByText(mockFormattedDate)).toBeInTheDocument(); + }); +}); diff --git a/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/ConfirmEmailBanner.scss b/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/ConfirmEmailBanner.scss similarity index 100% rename from src/containers/LearnerDashboardHeader/ConfirmEmailBanner/ConfirmEmailBanner.scss rename to src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/ConfirmEmailBanner.scss diff --git a/src/assets/confirm-email.svg b/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/assets/confirm-email.svg similarity index 100% rename from src/assets/confirm-email.svg rename to src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/assets/confirm-email.svg diff --git a/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/hooks.js b/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/hooks.js similarity index 52% rename from src/containers/LearnerDashboardHeader/ConfirmEmailBanner/hooks.js rename to src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/hooks.js index 3797c682f..3ef6ee84f 100644 --- a/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/hooks.js +++ b/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/hooks.js @@ -1,23 +1,17 @@ -import React from 'react'; +import { useState } from 'react'; -import { StrictDict } from 'utils'; -import { apiHooks, reduxHooks } from 'hooks'; - -import * as module from './hooks'; - -export const state = StrictDict({ - showPageBanner: (val) => React.useState(val), // eslint-disable-line - showConfirmModal: (val) => React.useState(val), // eslint-disable-line -}); +import { useInitializeLearnerHome, useSendConfirmEmail } from '@src/data/hooks'; export const useConfirmEmailBannerData = () => { - const { isNeeded } = reduxHooks.useEmailConfirmationData(); - const [showPageBanner, setShowPageBanner] = module.state.showPageBanner(isNeeded); - const [showConfirmModal, setShowConfirmModal] = module.state.showConfirmModal(false); + const { data: learnerData } = useInitializeLearnerHome(); + const isNeeded = learnerData?.emailConfirmation?.isNeeded || false; + const sendEmailUrl = learnerData?.emailConfirmation?.sendEmailUrl || ''; + const { mutate: sendConfirmEmail } = useSendConfirmEmail(sendEmailUrl); + const [showPageBanner, setShowPageBanner] = useState(isNeeded); + const [showConfirmModal, setShowConfirmModal] = useState(false); const closePageBanner = () => setShowPageBanner(false); const closeConfirmModal = () => setShowConfirmModal(false); const openConfirmModal = () => setShowConfirmModal(true); - const sendConfirmEmail = apiHooks.useSendConfirmEmail(); const openConfirmModalButtonClick = () => { sendConfirmEmail(); diff --git a/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/hooks.test.jsx b/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/hooks.test.jsx new file mode 100644 index 000000000..934048f25 --- /dev/null +++ b/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/hooks.test.jsx @@ -0,0 +1,111 @@ +import { renderHook, act } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { useInitializeLearnerHome } from '@src/data/hooks'; +import * as api from '@src/data/services/lms/api'; +import * as hooks from './hooks'; + +jest.mock('@src/data/hooks', () => ({ + ...jest.requireActual('@src/data/hooks'), + useInitializeLearnerHome: jest.fn(), +})); + +jest.mock('@src/data/services/lms/api', () => ({ + sendConfirmEmail: jest.fn(), +})); + +const emailConfirmation = { + isNeeded: true, +}; + +const createWrapper = () => { + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, + }); + const wrapper = ({ children }) => {children}; + return wrapper; +}; + +describe('ConfirmEmailBanner hooks', () => { + beforeEach(() => { + jest.clearAllMocks(); + api.sendConfirmEmail.mockResolvedValue({}); + }); + + describe('useEmailConfirmationData', () => { + it('show page banner on unverified email', () => { + useInitializeLearnerHome.mockReturnValue({ data: { emailConfirmation: { ...emailConfirmation } } }); + + const { result } = renderHook(() => hooks.useConfirmEmailBannerData(), { + wrapper: createWrapper(), + }); + + expect(result.current.isNeeded).toEqual(emailConfirmation.isNeeded); + }); + + it('hide page banner on verified email', () => { + useInitializeLearnerHome.mockReturnValue({ data: { emailConfirmation: { isNeeded: false } } }); + + const { result } = renderHook(() => hooks.useConfirmEmailBannerData(), { + wrapper: createWrapper(), + }); + + expect(result.current.isNeeded).toEqual(false); + }); + }); + + describe('behavior', () => { + beforeEach(() => { + useInitializeLearnerHome.mockReturnValue({ data: { emailConfirmation: { ...emailConfirmation } } }); + }); + + it('closePageBanner', () => { + const { result } = renderHook(() => hooks.useConfirmEmailBannerData(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.closePageBanner(); + }); + + expect(result.current.showPageBanner).toEqual(false); + }); + + it('closeConfirmModal', () => { + const { result } = renderHook(() => hooks.useConfirmEmailBannerData(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.closeConfirmModal(); + }); + + expect(result.current.showConfirmModal).toEqual(false); + }); + + it('openConfirmModalButtonClick', async () => { + const { result } = renderHook(() => hooks.useConfirmEmailBannerData(), { + wrapper: createWrapper(), + }); + + await act(async () => { + result.current.openConfirmModalButtonClick(); + }); + + expect(result.current.showConfirmModal).toEqual(true); + expect(api.sendConfirmEmail).toHaveBeenCalled(); + }); + + it('userConfirmEmailButtonClick', () => { + const { result } = renderHook(() => hooks.useConfirmEmailBannerData(), { + wrapper: createWrapper(), + }); + + act(() => { + result.current.userConfirmEmailButtonClick(); + }); + + expect(result.current.showConfirmModal).toEqual(false); + expect(result.current.showPageBanner).toEqual(false); + }); + }); +}); diff --git a/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/index.jsx b/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/index.jsx similarity index 87% rename from src/containers/LearnerDashboardHeader/ConfirmEmailBanner/index.jsx rename to src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/index.jsx index 806a50c47..e538a3c83 100644 --- a/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/index.jsx +++ b/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/index.jsx @@ -1,15 +1,7 @@ -/* eslint-disable max-len */ -import React from 'react'; -import { - Button, - Image, - MarketingModal, - ModalDialog, - PageBanner, -} from '@openedx/paragon'; -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; +import { Button, Image, MarketingModal, ModalDialog, PageBanner } from '@openedx/paragon'; -import confirmEmailSVG from 'assets/confirm-email.svg'; +import confirmEmailSVG from './assets/confirm-email.svg'; import messages from './messages'; import './ConfirmEmailBanner.scss'; import useConfirmEmailBannerData from './hooks'; @@ -70,6 +62,5 @@ export const ConfirmEmailBanner = () => { ); }; -ConfirmEmailBanner.propTypes = {}; export default ConfirmEmailBanner; diff --git a/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/messages.js b/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/messages.js similarity index 95% rename from src/containers/LearnerDashboardHeader/ConfirmEmailBanner/messages.js rename to src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/messages.js index 591dc9f79..b967f9319 100644 --- a/src/containers/LearnerDashboardHeader/ConfirmEmailBanner/messages.js +++ b/src/widgets/LearnerDashboardHeader/ConfirmEmailBanner/messages.js @@ -1,4 +1,4 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ confirmNowButton: { diff --git a/src/widgets/LearnerDashboardHeader/CoursesLink.jsx b/src/widgets/LearnerDashboardHeader/CoursesLink.jsx new file mode 100644 index 000000000..ed74dec19 --- /dev/null +++ b/src/widgets/LearnerDashboardHeader/CoursesLink.jsx @@ -0,0 +1,14 @@ +import { useIntl } from '@openedx/frontend-base'; + +import messages from './messages'; + +export default function CoursesLink() { + const { formatMessage } = useIntl(); + + return ( +
+ {formatMessage(messages.course)} +
+ ); +} + diff --git a/src/widgets/LearnerDashboardHeader/DiscoverLinkMenuItem.jsx b/src/widgets/LearnerDashboardHeader/DiscoverLinkMenuItem.jsx new file mode 100644 index 000000000..5fe1115d1 --- /dev/null +++ b/src/widgets/LearnerDashboardHeader/DiscoverLinkMenuItem.jsx @@ -0,0 +1,30 @@ +import { useContext } from 'react'; +import { LinkMenuItem, useIntl } from '@openedx/frontend-base'; + +import GlobalDataContext from '../../data/contexts/GlobalDataContext'; +import urls from '../../data/services/lms/urls'; +import messages from './messages'; + +function DiscoverLink() { + const { formatMessage } = useIntl(); + + return ( +
+ {formatMessage(messages.discoverNew)} +
+ ); +} + +export default function DiscoverLinkMenuItem({ variant = 'hyperlink' }) { + const { platformSettings } = useContext(GlobalDataContext); + const { courseSearchUrl } = platformSettings; + const url = urls.baseAppUrl(courseSearchUrl); + + return ( + } + url={url} + variant={variant} + /> + ); +} diff --git a/src/widgets/LearnerDashboardHeader/MasqueradeBar/hooks.js b/src/widgets/LearnerDashboardHeader/MasqueradeBar/hooks.js new file mode 100644 index 000000000..cc4956e4c --- /dev/null +++ b/src/widgets/LearnerDashboardHeader/MasqueradeBar/hooks.js @@ -0,0 +1,53 @@ +import { useState, useMemo } from 'react'; +import { useIntl } from '@openedx/frontend-base'; + +import { useMasquerade } from '../../../data/context'; +import { useInitializeLearnerHome } from '../../../data/hooks'; + +import messages from './messages'; + +export const useMasqueradeBarData = ({ + authenticatedUser, +}) => { + const { formatMessage } = useIntl(); + const [masqueradeInput, setMasqueradeInput] = useState(''); + const { masqueradeUser, setMasqueradeUser } = useMasquerade(); + const { + isError, error, isPending, + } = useInitializeLearnerHome(); + + const handleMasqueradeInputChange = (e) => setMasqueradeInput(e.target.value); + const handleClearMasquerade = () => { + setMasqueradeUser(undefined); + setMasqueradeInput(''); + }; + const handleMasqueradeSubmit = (user) => (e) => { + setMasqueradeUser(user); + e.preventDefault(); + }; + + const isMasqueradingFailed = !!masqueradeUser && !!masqueradeInput && isError; + const isMasqueradingPending = !!masqueradeUser && isPending; + const isMasquerading = !!masqueradeUser && !isError && !isPending; + const masqueradeErrorMessage = useMemo(() => { + if (masqueradeUser && error) { + return (error.customAttributes?.httpErrorStatus === 404 ? messages.NoStudentFound : messages.UnknownError); + } + return null; + }, [error, masqueradeUser]); + + return { + canMasquerade: authenticatedUser?.administrator, + isMasquerading, + isMasqueradingFailed, + isMasqueradingPending, + masqueradeErrorMessage, + masqueradeInput, + handleMasqueradeSubmit, + handleClearMasquerade, + handleMasqueradeInputChange, + formatMessage, + }; +}; + +export default useMasqueradeBarData; diff --git a/src/containers/MasqueradeBar/index.jsx b/src/widgets/LearnerDashboardHeader/MasqueradeBar/index.jsx similarity index 81% rename from src/containers/MasqueradeBar/index.jsx rename to src/widgets/LearnerDashboardHeader/MasqueradeBar/index.jsx index e69b9ceb4..83bc3d321 100644 --- a/src/containers/MasqueradeBar/index.jsx +++ b/src/widgets/LearnerDashboardHeader/MasqueradeBar/index.jsx @@ -1,8 +1,9 @@ -import React from 'react'; -import { AppContext } from '@edx/frontend-platform/react'; +import { useContext } from 'react'; +import { SiteContext } from '@openedx/frontend-base'; import { Chip, + Container, Form, FormControl, FormControlFeedback, @@ -18,7 +19,7 @@ import { useMasqueradeBarData } from './hooks'; import './index.scss'; export const MasqueradeBar = () => { - const { authenticatedUser } = React.useContext(AppContext); + const { authenticatedUser } = useContext(SiteContext); const { canMasquerade, @@ -36,11 +37,12 @@ export const MasqueradeBar = () => { if (!canMasquerade) { return null; } return ( -
-
+
+ + {isMasquerading ? ( <> - + {formatMessage(messages.ViewingAs)} @@ -54,7 +56,7 @@ export const MasqueradeBar = () => { ) : ( <> - + {formatMessage(messages.ViewAs)} @@ -72,7 +74,7 @@ export const MasqueradeBar = () => { )} { )} +
); }; diff --git a/src/widgets/LearnerDashboardHeader/MasqueradeBar/index.scss b/src/widgets/LearnerDashboardHeader/MasqueradeBar/index.scss new file mode 100644 index 000000000..6ceb16d48 --- /dev/null +++ b/src/widgets/LearnerDashboardHeader/MasqueradeBar/index.scss @@ -0,0 +1,38 @@ +@use "@openedx/paragon/styles/css/core/custom-media-breakpoints.css"; + +.masquerade-bar { + display: flex; + align-items: flex-start; + padding: var(--pgn-spacing-spacer-3) 0; + margin-bottom: var(--pgn-spacing-spacer-2); + + .masquerade-form-label { + padding: var(--pgn-spacing-spacer-2) var(--pgn-spacing-spacer-3) var(--pgn-spacing-spacer-2) 0; + display: flex; + align-items: center; + margin-bottom: 0; + white-space: nowrap; + + &>.pgn__icon { + margin-right: var(--pgn-spacing-spacer-3); + } + } + + .masquerade-form-input { + margin-bottom: 0; + flex-grow: 1; + max-width: var(--pgn-size-breakpoint-md); + } + + .masquerade-chip { + padding: var(--pgn-spacing-spacer-2) var(--pgn-spacing-spacer-3); + font-size: var(--pgn-typography-font-size-base); + } +} + +@media (--pgn-size-breakpoint-max-width-md) { + .masquerade-bar { + margin: auto; + padding: var(--pgn-spacing-spacer-3) 0; + } +} diff --git a/src/containers/MasqueradeBar/messages.js b/src/widgets/LearnerDashboardHeader/MasqueradeBar/messages.js similarity index 94% rename from src/containers/MasqueradeBar/messages.js rename to src/widgets/LearnerDashboardHeader/MasqueradeBar/messages.js index a6b721dcf..8113fd18d 100644 --- a/src/containers/MasqueradeBar/messages.js +++ b/src/widgets/LearnerDashboardHeader/MasqueradeBar/messages.js @@ -1,4 +1,4 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ ViewAs: { diff --git a/src/widgets/LearnerDashboardHeader/OrderHistoryLinkMenuItem.jsx b/src/widgets/LearnerDashboardHeader/OrderHistoryLinkMenuItem.jsx new file mode 100644 index 000000000..9c93723e3 --- /dev/null +++ b/src/widgets/LearnerDashboardHeader/OrderHistoryLinkMenuItem.jsx @@ -0,0 +1,29 @@ +import { LinkMenuItem, getAppConfig, useIntl } from '@openedx/frontend-base'; + +import { appId } from '../../constants'; +import messages from './messages'; + +function OrderHistoryLink() { + const { formatMessage } = useIntl(); + + return ( +
+ {formatMessage(messages.orderHistory)} +
+ ); +} + +export default function OrderHistoryLinkMenuItem({ variant = 'hyperlink' }) { + const { ORDER_HISTORY_URL: url } = getAppConfig(appId); + + if (!url) + return null; + + return ( + } + url={url} + variant={variant} + /> + ); +} diff --git a/src/widgets/LearnerDashboardHeader/ProgramsLinkMenuItem.jsx b/src/widgets/LearnerDashboardHeader/ProgramsLinkMenuItem.jsx new file mode 100644 index 000000000..b0efaecfe --- /dev/null +++ b/src/widgets/LearnerDashboardHeader/ProgramsLinkMenuItem.jsx @@ -0,0 +1,26 @@ +import { LinkMenuItem, useIntl } from '@openedx/frontend-base'; + +import urls from '../../data/services/lms/urls'; +import messages from './messages'; + +function ProgramsLink() { + const { formatMessage } = useIntl(); + + return ( +
+ {formatMessage(messages.program)} +
+ ); +} + +export default function ProgramsLinkMenuItem({ variant = 'hyperlink' }) { + const url = urls.programsUrl(); + + return ( + } + url={url} + variant={variant} + /> + ); +} diff --git a/src/widgets/LearnerDashboardHeader/app.tsx b/src/widgets/LearnerDashboardHeader/app.tsx new file mode 100644 index 000000000..ef8a708c7 --- /dev/null +++ b/src/widgets/LearnerDashboardHeader/app.tsx @@ -0,0 +1,94 @@ +import { App, LinkMenuItem, WidgetOperationTypes, getAppConfig, helpButtonSlotOperation } from '@openedx/frontend-base'; + +import { appId, dashboardRole } from '../../constants'; + +import ConfirmEmailBanner from './ConfirmEmailBanner'; +import MasqueradeBar from './MasqueradeBar'; +import CoursesLink from './CoursesLink'; +import DiscoverLinkMenuItem from './DiscoverLinkMenuItem'; +import ProgramsLinkMenuItem from './ProgramsLinkMenuItem'; +import OrderHistoryLinkMenuItem from './OrderHistoryLinkMenuItem'; + +const app: App = { + appId: 'org.openedx.frontend.app.learnerDashboard.header', + slots: [ + { + slotId: 'org.openedx.frontend.slot.header.main.v1', + id: 'org.openedx.frontend.widget.learnerDashboard.headerConfirmEmail.v1', + op: WidgetOperationTypes.PREPEND, + component: ConfirmEmailBanner, + condition: { + active: [dashboardRole] + } + }, + { + slotId: 'org.openedx.frontend.slot.header.primaryLinks.v1', + id: 'org.openedx.frontend.widget.learnerDashboard.headerLinkCourses.v1', + op: WidgetOperationTypes.APPEND, + element: ( + } + role={dashboardRole} + variant="navLink" + /> + ), + condition: { + active: [dashboardRole] + } + }, + { + slotId: 'org.openedx.frontend.slot.header.primaryLinks.v1', + id: 'org.openedx.frontend.widget.learnerDashboard.headerLinkPrograms.v1', + op: WidgetOperationTypes.APPEND, + element: ( + + ), + condition: { + active: [dashboardRole], + callback: () => getAppConfig(appId).ENABLE_PROGRAMS === true, + } + }, + { + slotId: 'org.openedx.frontend.slot.header.primaryLinks.v1', + id: 'org.openedx.frontend.widget.learnerDashboard.headerLinkDiscover.v1', + op: WidgetOperationTypes.APPEND, + element: ( + + ), + condition: { + active: [dashboardRole] + } + }, + helpButtonSlotOperation({ appId, role: dashboardRole }), + { + slotId: 'org.openedx.frontend.slot.header.authenticatedMenu.v1', + id: 'org.openedx.frontend.widget.learnerDashboard.headerLinkOrderHistory.v1', + op: WidgetOperationTypes.INSERT_BEFORE, + relatedId: 'org.openedx.frontend.widget.header.desktopAuthenticatedMenuLogout.v1', + element: ( + + ), + condition: { + active: [dashboardRole], + callback: () => getAppConfig(appId).ORDER_HISTORY_URL ? true : false, + } + }, + { + slotId: 'org.openedx.frontend.slot.header.main.v1', + id: 'org.openedx.frontend.widget.learnerDashboard.headerMasqueradeBar.v1', + op: WidgetOperationTypes.APPEND, + component: MasqueradeBar, + condition: { + active: [dashboardRole] + } + }, + ] +}; + +export default app; diff --git a/src/widgets/LearnerDashboardHeader/hooks.js b/src/widgets/LearnerDashboardHeader/hooks.js new file mode 100644 index 000000000..50f80c107 --- /dev/null +++ b/src/widgets/LearnerDashboardHeader/hooks.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { useIntl } from '@openedx/frontend-base'; +import { StrictDict } from '@src/utils'; + +import getLearnerHeaderMenu from './LearnerDashboardMenu'; + +export const state = StrictDict({ + isOpen: (val) => React.useState(val), // eslint-disable-line +}); + +export const useLearnerDashboardHeaderMenu = ({ + courseSearchUrl, authenticatedUser, +}) => { + const { formatMessage } = useIntl(); + return getLearnerHeaderMenu(formatMessage, courseSearchUrl, authenticatedUser); +}; + +export default { + useLearnerDashboardHeaderMenu, +}; diff --git a/src/widgets/LearnerDashboardHeader/index.ts b/src/widgets/LearnerDashboardHeader/index.ts new file mode 100644 index 000000000..afb319e30 --- /dev/null +++ b/src/widgets/LearnerDashboardHeader/index.ts @@ -0,0 +1 @@ +export { default as learnerDashboardHeaderApp } from './app'; diff --git a/src/containers/LearnerDashboardHeader/messages.js b/src/widgets/LearnerDashboardHeader/messages.js similarity index 93% rename from src/containers/LearnerDashboardHeader/messages.js rename to src/widgets/LearnerDashboardHeader/messages.js index 08c71cd78..06aa8fb4f 100644 --- a/src/containers/LearnerDashboardHeader/messages.js +++ b/src/widgets/LearnerDashboardHeader/messages.js @@ -1,4 +1,4 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ dashboard: { @@ -16,11 +16,6 @@ const messages = defineMessages({ defaultMessage: 'SWITCH DASHBOARD', description: 'Switch Dashboard header in the user menu', }, - help: { - id: 'learnerVariantDashboard.help.label', - defaultMessage: 'Help', - description: 'The text for the link to the Help Center', - }, profile: { id: 'learnerVariantDashboard.menu.profile.label', defaultMessage: 'Profile', diff --git a/src/widgets/LookingForChallengeWidget/__snapshots__/index.test.jsx.snap b/src/widgets/LookingForChallengeWidget/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 1400cc896..000000000 --- a/src/widgets/LookingForChallengeWidget/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,45 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LookingForChallengeWidget snapshots default 1`] = ` - - - -

- Looking for a new challenge? -

-
- - , - } - } - /> - -
-
-
-`; diff --git a/src/widgets/LookingForChallengeWidget/index.jsx b/src/widgets/LookingForChallengeWidget/index.jsx index 9410c99af..61d0ae041 100644 --- a/src/widgets/LookingForChallengeWidget/index.jsx +++ b/src/widgets/LookingForChallengeWidget/index.jsx @@ -1,12 +1,10 @@ -import React from 'react'; - -import { useIntl } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@openedx/frontend-base'; import { Card, Hyperlink, Icon } from '@openedx/paragon'; import { ArrowForward } from '@openedx/paragon/icons'; -import { reduxHooks } from 'hooks'; -import moreCoursesSVG from 'assets/more-courses-sidewidget.svg'; -import { baseAppUrl } from 'data/services/lms/urls'; +import { useInitializeLearnerHome } from '@src/data/hooks'; +import moreCoursesSVG from '../../assets/more-courses-sidewidget.svg'; +import { baseAppUrl } from '../../data/services/lms/urls'; import { findCoursesWidgetClicked } from './track'; import messages from './messages'; @@ -16,7 +14,8 @@ export const arrowIcon = (); export const LookingForChallengeWidget = () => { const { formatMessage } = useIntl(); - const { courseSearchUrl } = reduxHooks.usePlatformSettingsData(); + const { data: learnerData } = useInitializeLearnerHome(); + const courseSearchUrl = learnerData?.platformSettings?.courseSearchUrl || ''; const hyperlinkDestination = baseAppUrl(courseSearchUrl) || ''; return ( diff --git a/src/widgets/LookingForChallengeWidget/index.test.jsx b/src/widgets/LookingForChallengeWidget/index.test.jsx index db6c39786..5f98ecb54 100644 --- a/src/widgets/LookingForChallengeWidget/index.test.jsx +++ b/src/widgets/LookingForChallengeWidget/index.test.jsx @@ -1,13 +1,18 @@ -import { shallow } from '@edx/react-unit-test-utils'; - +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@openedx/frontend-base'; import LookingForChallengeWidget from '.'; +import messages from './messages'; + +const courseSearchUrl = 'http://localhost:18000/course-search-url'; -jest.mock('hooks', () => ({ - reduxHooks: { - usePlatformSettingsData: () => ({ - courseSearchUrl: 'http://localhost:18000/course-search-url', - }), - }, +jest.mock('@src/data/hooks', () => ({ + useInitializeLearnerHome: () => ({ + data: { + platformSettings: { + courseSearchUrl, + }, + }, + }), })); jest.mock('./track', () => ({ @@ -15,10 +20,21 @@ jest.mock('./track', () => ({ })); describe('LookingForChallengeWidget', () => { - describe('snapshots', () => { - test('default', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); + describe('render', () => { + it('card image', () => { + render(); + const image = screen.getByRole('img', { alt: 'course side widget' }); + expect(image).toBeInTheDocument(); + }); + it('prompt', () => { + render(); + const prompt = screen.getByText(messages.lookingForChallengePrompt.defaultMessage); + expect(prompt).toBeInTheDocument(); + }); + it('hyperlink', () => { + render(); + const link = screen.getByRole('link', { href: courseSearchUrl }); + expect(link).toBeInTheDocument(); }); }); }); diff --git a/src/widgets/LookingForChallengeWidget/messages.js b/src/widgets/LookingForChallengeWidget/messages.js index 2f9bd1662..01b430c4b 100644 --- a/src/widgets/LookingForChallengeWidget/messages.js +++ b/src/widgets/LookingForChallengeWidget/messages.js @@ -1,4 +1,4 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; +import { defineMessages } from '@openedx/frontend-base'; const messages = defineMessages({ lookingForChallengePrompt: { diff --git a/src/widgets/LookingForChallengeWidget/track.js b/src/widgets/LookingForChallengeWidget/track.js index d18f1e174..d799c0cc1 100644 --- a/src/widgets/LookingForChallengeWidget/track.js +++ b/src/widgets/LookingForChallengeWidget/track.js @@ -1,5 +1,5 @@ -import { StrictDict } from 'utils'; -import track from 'tracking'; +import { StrictDict } from '../../utils'; +import track from '../../tracking'; export const linkNames = StrictDict({ findCoursesWidget: 'learner_home_widget_explore', diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 000000000..5bb6fc1d4 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "noEmit": false + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "src/**/*.test.*", + "src/**/*.spec.*", + "src/**/tests/**/*", + "src/__mocks__/**/*", + "src/setupTest.*" + ] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..bc6862d0d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "@openedx/frontend-base/tools/tsconfig.json", + "compilerOptions": { + "types": ["jest", "@testing-library/jest-dom"], + "rootDir": ".", + "outDir": "dist", + "paths": { + "@src/*": ["./src/*"] + }, + }, + "include": [ + "src/**/*", + "app.d.ts", + "babel.config.js", + "eslint.config.js", + "jest.config.js", + "site.config.*.tsx" + ] +} diff --git a/turbo.site.json b/turbo.site.json new file mode 100644 index 000000000..0f89ea084 --- /dev/null +++ b/turbo.site.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"], + "cache": false + }, + "clean": { + "cache": false + }, + "watch:build": { + "dependsOn": ["^build"], + "persistent": true, + "cache": false + }, + "//#dev:site": { + "dependsOn": ["^build"], + "persistent": true, + "cache": false + } + } +} diff --git a/webpack.dev.config.js b/webpack.dev.config.js deleted file mode 100644 index b567676bf..000000000 --- a/webpack.dev.config.js +++ /dev/null @@ -1,25 +0,0 @@ -const path = require('path'); -const { createConfig } = require('@openedx/frontend-build'); -const CopyPlugin = require('copy-webpack-plugin'); - -const config = createConfig('webpack-dev'); - -config.resolve.modules = [ - path.resolve(__dirname, './src'), - 'node_modules', -]; - -config.module.rules[0].exclude = /node_modules\/(?!(split-on-first|strict-uri-encode|@edx))/; - -config.plugins.push( - new CopyPlugin({ - patterns: [ - { - from: path.resolve(__dirname, './public/robots.txt'), - to: path.resolve(__dirname, './dist/robots.txt'), - }, - ], - }), -); - -module.exports = config; diff --git a/webpack.prod.config.js b/webpack.prod.config.js deleted file mode 100644 index 9cf64fc90..000000000 --- a/webpack.prod.config.js +++ /dev/null @@ -1,25 +0,0 @@ -const path = require('path'); -const { createConfig } = require('@openedx/frontend-build'); -const CopyPlugin = require('copy-webpack-plugin'); - -const config = createConfig('webpack-prod'); - -config.resolve.modules = [ - path.resolve(__dirname, './src'), - 'node_modules', -]; - -config.module.rules[0].exclude = /node_modules\/(?!(split-on-first|strict-uri-encode|@edx))/; - -config.plugins.push( - new CopyPlugin({ - patterns: [ - { - from: path.resolve(__dirname, './public/robots.txt'), - to: path.resolve(__dirname, './dist/robots.txt'), - }, - ], - }), -); - -module.exports = config;