diff --git a/.bundler-audit.yml b/.bundler-audit.yml new file mode 100644 index 000000000..0671df390 --- /dev/null +++ b/.bundler-audit.yml @@ -0,0 +1,6 @@ +--- +ignore: + # devise-two-factor advisory about brute-forcing TOTP + # We have rate-limits on authentication endpoints in place (including second + # factor verification) since Mastodon v3.2.0 + - CVE-2024-0227 diff --git a/.codeclimate.yml b/.codeclimate.yml deleted file mode 100644 index 59051aae7..000000000 --- a/.codeclimate.yml +++ /dev/null @@ -1,39 +0,0 @@ -version: '2' -checks: - argument-count: - enabled: false - complex-logic: - enabled: false - file-lines: - enabled: false - method-complexity: - enabled: false - method-count: - enabled: false - method-lines: - enabled: false - nested-control-flow: - enabled: false - return-statements: - enabled: false - similar-code: - enabled: false - identical-code: - enabled: false -plugins: - brakeman: - enabled: true - bundler-audit: - enabled: true - eslint: - enabled: false - rubocop: - enabled: false - sass-lint: - enabled: false -exclude_patterns: - - spec/ - - vendor/asset/ - - - app/javascript/mastodon/locales/**/*.json - - config/locales/**/*.yml diff --git a/.deepsource.toml b/.deepsource.toml deleted file mode 100644 index bcd310412..000000000 --- a/.deepsource.toml +++ /dev/null @@ -1,23 +0,0 @@ -version = 1 - -test_patterns = ["app/javascript/mastodon/**/__tests__/**"] - -exclude_patterns = [ - "db/migrate/**", - "db/post_migrate/**" -] - -[[analyzers]] -name = "ruby" -enabled = true - -[[analyzers]] -name = "javascript" -enabled = true - - [analyzers.meta] - environment = [ - "browser", - "jest", - "nodejs" - ] diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ac495e1c9..b3b1d97a2 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,16 +1,10 @@ -# [Choice] Ruby version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.1, 3.0, 2, 2.7, 2.6, 3-bullseye, 3.1-bullseye, 3.0-bullseye, 2-bullseye, 2.7-bullseye, 2.6-bullseye, 3-buster, 3.1-buster, 3.0-buster, 2-buster, 2.7-buster, 2.6-buster -ARG VARIANT=3.1-bullseye -FROM mcr.microsoft.com/vscode/devcontainers/ruby:${VARIANT} +# For details, see https://github.com/devcontainers/images/tree/main/src/ruby +FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye # Install Rails # RUN gem install rails webdrivers -# Default value to allow debug server to serve content over GitHub Codespace's port forwarding service -# The value is a comma-separated list of allowed domains -ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev" - -# [Choice] Node.js version: lts/*, 16, 14, 12, 10 -ARG NODE_VERSION="lts/*" +ARG NODE_VERSION="16" RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1" # [Optional] Uncomment this section to install additional OS packages. @@ -22,3 +16,5 @@ RUN gem install foreman # [Optional] Uncomment this line to install global node packages. RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g yarn" 2>&1 + +COPY welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt diff --git a/.devcontainer/codespaces/devcontainer.json b/.devcontainer/codespaces/devcontainer.json new file mode 100644 index 000000000..ca9156fda --- /dev/null +++ b/.devcontainer/codespaces/devcontainer.json @@ -0,0 +1,49 @@ +{ + "name": "Mastodon on GitHub Codespaces", + "dockerComposeFile": "../docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + + "features": { + "ghcr.io/devcontainers/features/sshd:1": {} + }, + + "runServices": ["app", "db", "redis"], + + "forwardPorts": [3000, 4000], + + "portsAttributes": { + "3000": { + "label": "web", + "onAutoForward": "notify" + }, + "4000": { + "label": "stream", + "onAutoForward": "silent" + } + }, + + "otherPortsAttributes": { + "onAutoForward": "silent" + }, + + "remoteEnv": { + "LOCAL_DOMAIN": "${localEnv:CODESPACE_NAME}-3000.app.github.dev", + "LOCAL_HTTPS": "true", + "STREAMING_API_BASE_URL": "https://${localEnv:CODESPACE_NAME}-4000.app.github.dev", + "DISABLE_FORGERY_REQUEST_PROTECTION": "true", + "ES_ENABLED": "", + "LIBRE_TRANSLATE_ENDPOINT": "" + }, + + "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", + "postCreateCommand": ".devcontainer/post-create.sh", + "waitFor": "postCreateCommand", + + "customizations": { + "vscode": { + "settings": {}, + "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"] + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 47497794f..fa8d6542c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,27 +1,40 @@ { - "name": "Mastodon", + "name": "Mastodon on local machine", "dockerComposeFile": "docker-compose.yml", "service": "app", - "workspaceFolder": "/workspaces/mastodon", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", - // Set *default* container specific settings.json values on container create. - "settings": {}, + "features": { + "ghcr.io/devcontainers/features/sshd:1": {} + }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "EditorConfig.EditorConfig", - "dbaeumer.vscode-eslint", - "rebornix.Ruby", - "webben.browserslist" - ], - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // This can be used to network with other containers or the host. "forwardPorts": [3000, 4000], - // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "bundle install --path vendor/bundle && yarn install && git checkout -- Gemfile.lock && ./bin/rails db:setup", + "portsAttributes": { + "3000": { + "label": "web", + "onAutoForward": "notify", + "requireLocalPort": true + }, + "4000": { + "label": "stream", + "onAutoForward": "silent", + "requireLocalPort": true + } + }, - // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "vscode" + "otherPortsAttributes": { + "onAutoForward": "silent" + }, + + "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", + "postCreateCommand": ".devcontainer/post-create.sh", + "waitFor": "postCreateCommand", + + "customizations": { + "vscode": { + "settings": {}, + "extensions": ["EditorConfig.EditorConfig", "webben.browserslist"] + } + } } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 46f42c454..20aecd71d 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -5,19 +5,12 @@ services: build: context: . dockerfile: Dockerfile - args: - # Update 'VARIANT' to pick a version of Ruby: 3, 3.1, 3.0, 2, 2.7, 2.6 - # Append -bullseye or -buster to pin to an OS version. - # Use -bullseye variants on local arm64/Apple Silicon. - VARIANT: '3.0-bullseye' - # Optional Node.js version to install - NODE_VERSION: '14' volumes: - - ..:/workspaces/mastodon:cached + - ../..:/workspaces:cached environment: RAILS_ENV: development NODE_ENV: development - + BIND: 0.0.0.0 REDIS_HOST: redis REDIS_PORT: '6379' DB_HOST: db @@ -30,10 +23,13 @@ services: LIBRE_TRANSLATE_ENDPOINT: http://libretranslate:5000 # Overrides default command so things don't shut down after the process ends. command: sleep infinity + ports: + - '127.0.0.1:3000:3000' + - '127.0.0.1:3035:3035' + - '127.0.0.1:4000:4000' networks: - external_network - internal_network - user: vscode db: image: postgres:14-alpine @@ -49,7 +45,7 @@ services: - internal_network redis: - image: redis:6-alpine + image: redis:7-alpine restart: unless-stopped volumes: - redis-data:/data @@ -74,15 +70,19 @@ services: hard: -1 libretranslate: - image: libretranslate/libretranslate:v1.2.9 + image: libretranslate/libretranslate:v1.3.11 restart: unless-stopped + volumes: + - lt-data:/home/libretranslate/.local networks: + - external_network - internal_network volumes: postgres-data: redis-data: es-data: + lt-data: networks: external_network: diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100755 index 000000000..a075cc7b3 --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -e # Fail the whole script on first error + +# Fetch Ruby gem dependencies +bundle config path 'vendor/bundle' +bundle config with 'development test' +bundle install + +# Make Gemfile.lock pristine again +git checkout -- Gemfile.lock + +# Fetch Javascript dependencies +yarn --frozen-lockfile + +# [re]create, migrate, and seed the test database +RAILS_ENV=test ./bin/rails db:setup + +# [re]create, migrate, and seed the development database +RAILS_ENV=development ./bin/rails db:setup + +# Precompile assets for development +RAILS_ENV=development ./bin/rails assets:precompile + +# Precompile assets for test +RAILS_ENV=test NODE_ENV=tests ./bin/rails assets:precompile diff --git a/.devcontainer/welcome-message.txt b/.devcontainer/welcome-message.txt new file mode 100644 index 000000000..488cf9285 --- /dev/null +++ b/.devcontainer/welcome-message.txt @@ -0,0 +1,8 @@ +👋 Welcome to "Mastodon" in GitHub Codespaces! + +🛠️ Your environment is fully setup with all the required software. + +🔍 To explore VS Code to its fullest, search using the Command Palette (Cmd/Ctrl + Shift + P or F1). + +📝 Edit away, run your app as usual, and we'll automatically make it available for you to access. + diff --git a/.editorconfig b/.editorconfig index 5f8702cf8..b5217da4a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,3 +10,4 @@ insert_final_newline = true charset = utf-8 indent_style = space indent_size = 2 +trim_trailing_whitespace = true diff --git a/.env.production.sample b/.env.production.sample index 5eecb8bde..0bf01bdc3 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -54,7 +54,7 @@ VAPID_PUBLIC_KEY= # Sending mail # ------------ -SMTP_SERVER=smtp.mailgun.org +SMTP_SERVER= SMTP_PORT=587 SMTP_LOGIN= SMTP_PASSWORD= diff --git a/.env.vagrant b/.env.vagrant index 32ed9b922..69c1bf1fb 100644 --- a/.env.vagrant +++ b/.env.vagrant @@ -2,3 +2,7 @@ VAGRANT=true LOCAL_DOMAIN=mastodon.local BIND=0.0.0.0 DB_HOST=/var/run/postgresql/ + +ES_ENABLED=true +ES_HOST=localhost +ES_PORT=9200 \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index e4ada6fe0..d5f0ae1ac 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,71 +1,71 @@ module.exports = { root: true, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:jsx-a11y/recommended', + 'plugin:import/recommended', + 'plugin:promise/recommended', + 'plugin:jsdoc/recommended', + 'plugin:prettier/recommended', + ], + env: { browser: true, node: true, es6: true, - jest: true, }, globals: { ATTACHMENT_HOST: false, }, - parser: '@babel/eslint-parser', + parser: '@typescript-eslint/parser', plugins: [ 'react', 'jsx-a11y', 'import', 'promise', + '@typescript-eslint', + 'formatjs', ], parserOptions: { sourceType: 'module', ecmaFeatures: { - experimentalObjectRestSpread: true, jsx: true, }, ecmaVersion: 2021, + requireConfigFile: false, + babelOptions: { + configFile: false, + presets: ['@babel/react', '@babel/env'], + }, }, settings: { react: { version: 'detect', }, - 'import/extensions': [ - '.js', - ], 'import/ignore': [ 'node_modules', '\\.(css|scss|json)$', ], 'import/resolver': { - node: { - paths: ['app/javascript'], - }, + typescript: {}, }, }, rules: { - 'brace-style': 'warn', - 'comma-dangle': ['error', 'always-multiline'], - 'comma-spacing': [ - 'warn', - { - before: false, - after: true, - }, - ], - 'comma-style': ['warn', 'last'], 'consistent-return': 'error', 'dot-notation': 'error', - eqeqeq: 'error', - indent: ['warn', 2], + eqeqeq: ['error', 'always', { 'null': 'ignore' }], 'jsx-quotes': ['error', 'prefer-single'], + 'no-case-declarations': 'off', 'no-catch-shadow': 'error', - 'no-cond-assign': 'error', 'no-console': [ 'warn', { @@ -75,91 +75,75 @@ module.exports = { ], }, ], - 'no-fallthrough': 'error', - 'no-irregular-whitespace': 'error', - 'no-mixed-spaces-and-tabs': 'warn', - 'no-nested-ternary': 'warn', + 'no-empty': 'off', 'no-restricted-properties': [ 'error', { property: 'substring', message: 'Use .slice instead of .substring.' }, { property: 'substr', message: 'Use .slice instead of .substr.' }, ], - 'no-trailing-spaces': 'warn', - 'no-undef': 'error', - 'no-unreachable': 'error', + 'no-restricted-syntax': [ + 'error', + { + // eslint-disable-next-line no-restricted-syntax + selector: 'Literal[value=/•/], JSXText[value=/•/]', + // eslint-disable-next-line no-restricted-syntax + message: "Use '·' (middle dot) instead of '•' (bullet)", + }, + ], + 'no-self-assign': 'off', 'no-unused-expressions': 'error', - 'no-unused-vars': [ + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ 'error', { vars: 'all', args: 'after-used', + destructuredArrayIgnorePattern: '^_', ignoreRestSiblings: true, }, ], - 'object-curly-spacing': ['error', 'always'], - 'padded-blocks': [ - 'error', - { - classes: 'always', - }, - ], - quotes: ['error', 'single'], - semi: 'error', - strict: 'off', 'valid-typeof': 'error', + 'react/jsx-filename-extension': ['error', { extensions: ['.jsx', 'tsx'] }], 'react/jsx-boolean-value': 'error', - 'react/jsx-closing-bracket-location': ['error', 'line-aligned'], - 'react/jsx-curly-spacing': 'error', + 'react/display-name': 'off', + 'react/jsx-fragments': ['error', 'syntax'], 'react/jsx-equals-spacing': 'error', - 'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'], - 'react/jsx-indent': ['error', 2], 'react/jsx-no-bind': 'error', - 'react/jsx-no-duplicate-props': 'error', - 'react/jsx-no-undef': 'error', + 'react/jsx-no-useless-fragment': 'error', + 'react/jsx-no-target-blank': 'off', 'react/jsx-tag-spacing': 'error', - 'react/jsx-uses-react': 'error', - 'react/jsx-uses-vars': 'error', + 'react/jsx-uses-react': 'off', // not needed with new JSX transform 'react/jsx-wrap-multilines': 'error', - 'react/no-multi-comp': 'off', - 'react/no-string-refs': 'error', - 'react/prop-types': 'error', + 'react/no-deprecated': 'off', + 'react/no-unknown-property': 'off', + 'react/react-in-jsx-scope': 'off', // not needed with new JSX transform 'react/self-closing-comp': 'error', + // recommended values found in https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/src/index.js 'jsx-a11y/accessible-emoji': 'warn', - 'jsx-a11y/alt-text': 'warn', - 'jsx-a11y/anchor-has-content': 'warn', - 'jsx-a11y/anchor-is-valid': [ - 'warn', - { - components: [ - 'Link', - 'NavLink', - ], - specialLink: [ - 'to', - ], - aspect: [ - 'noHref', - 'invalidHref', - 'preferButton', - ], - }, - ], - 'jsx-a11y/aria-activedescendant-has-tabindex': 'warn', - 'jsx-a11y/aria-props': 'warn', - 'jsx-a11y/aria-proptypes': 'warn', - 'jsx-a11y/aria-role': 'warn', - 'jsx-a11y/aria-unsupported-elements': 'warn', - 'jsx-a11y/heading-has-content': 'warn', - 'jsx-a11y/html-has-lang': 'warn', - 'jsx-a11y/iframe-has-title': 'warn', - 'jsx-a11y/img-redundant-alt': 'warn', - 'jsx-a11y/interactive-supports-focus': 'warn', - 'jsx-a11y/label-has-for': 'off', - 'jsx-a11y/mouse-events-have-key-events': 'warn', - 'jsx-a11y/no-access-key': 'warn', - 'jsx-a11y/no-distracting-elements': 'warn', + 'jsx-a11y/click-events-have-key-events': 'off', + 'jsx-a11y/label-has-associated-control': 'off', + 'jsx-a11y/media-has-caption': 'off', + 'jsx-a11y/no-autofocus': 'off', + // recommended rule is: + // 'jsx-a11y/no-interactive-element-to-noninteractive-role': [ + // 'error', + // { + // tr: ['none', 'presentation'], + // canvas: ['img'], + // }, + // ], + 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'off', + // recommended rule is: + // 'jsx-a11y/no-noninteractive-element-interactions': [ + // 'error', + // { + // body: ['onError', 'onLoad'], + // iframe: ['onError', 'onLoad'], + // img: ['onError', 'onLoad'], + // }, + // ], 'jsx-a11y/no-noninteractive-element-interactions': [ 'warn', { @@ -168,8 +152,18 @@ module.exports = { ], }, ], + // recommended rule is: + // 'jsx-a11y/no-noninteractive-tabindex': [ + // 'error', + // { + // tags: [], + // roles: ['tabpanel'], + // allowExpressionValues: true, + // }, + // ], + 'jsx-a11y/no-noninteractive-tabindex': 'off', 'jsx-a11y/no-onchange': 'warn', - 'jsx-a11y/no-redundant-roles': 'warn', + // recommended is full 'error' 'jsx-a11y/no-static-element-interactions': [ 'warn', { @@ -178,37 +172,213 @@ module.exports = { ], }, ], - 'jsx-a11y/role-has-required-aria-props': 'warn', - 'jsx-a11y/role-supports-aria-props': 'off', - 'jsx-a11y/scope': 'warn', - 'jsx-a11y/tabindex-no-positive': 'warn', + // See https://github.com/import-js/eslint-plugin-import/blob/main/config/recommended.js 'import/extensions': [ 'error', 'always', { js: 'never', + jsx: 'never', + mjs: 'never', + ts: 'never', + tsx: 'never', }, ], + 'import/first': 'error', 'import/newline-after-import': 'error', + 'import/no-anonymous-default-export': 'error', 'import/no-extraneous-dependencies': [ 'error', { devDependencies: [ 'config/webpack/**', + 'app/javascript/mastodon/performance.js', 'app/javascript/mastodon/test_setup.js', 'app/javascript/**/__tests__/**', ], }, ], - 'import/no-unresolved': 'error', + 'import/no-amd': 'error', + 'import/no-commonjs': 'error', + 'import/no-import-module-exports': 'error', + 'import/no-relative-packages': 'error', + 'import/no-self-import': 'error', + 'import/no-useless-path-segments': 'error', 'import/no-webpack-loader-syntax': 'error', + 'import/order': [ + 'error', + { + alphabetize: { order: 'asc' }, + 'newlines-between': 'always', + groups: [ + 'builtin', + 'external', + 'internal', + 'parent', + ['index', 'sibling'], + 'object', + ], + pathGroups: [ + // React core packages + { + pattern: '{react,react-dom,react-dom/client,prop-types}', + group: 'builtin', + position: 'after', + }, + // I18n + { + pattern: '{react-intl,intl-messageformat}', + group: 'builtin', + position: 'after', + }, + // Common React utilities + { + pattern: '{classnames,react-helmet,react-router-dom}', + group: 'external', + position: 'before', + }, + // Immutable / Redux / data store + { + pattern: '{immutable,react-redux,react-immutable-proptypes,react-immutable-pure-component,reselect}', + group: 'external', + position: 'before', + }, + // Internal packages + { + pattern: '{mastodon/**}', + group: 'internal', + position: 'after', + }, + ], + pathGroupsExcludedImportTypes: [], + }, + ], + + 'promise/always-return': 'off', 'promise/catch-or-return': [ 'error', { allowFinally: true, }, ], + 'promise/no-callback-in-promise': 'off', + 'promise/no-nesting': 'off', + 'promise/no-promise-in-callback': 'off', + + 'formatjs/blocklist-elements': 'error', + 'formatjs/enforce-default-message': ['error', 'literal'], + 'formatjs/enforce-description': 'off', // description values not currently used + 'formatjs/enforce-id': 'off', // Explicit IDs are used in the project + 'formatjs/enforce-placeholders': 'off', // Issues in short_number.jsx + 'formatjs/enforce-plural-rules': 'error', + 'formatjs/no-camel-case': 'off', // disabledAccount is only non-conforming + 'formatjs/no-complex-selectors': 'error', + 'formatjs/no-emoji': 'error', + 'formatjs/no-id': 'off', // IDs are used for translation keys + 'formatjs/no-invalid-icu': 'error', + 'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings + 'formatjs/no-multiple-plurals': 'off', // Only used by hashtag.jsx + 'formatjs/no-multiple-whitespaces': 'error', + 'formatjs/no-offset': 'error', + 'formatjs/no-useless-message': 'error', + 'formatjs/prefer-formatted-message': 'error', + 'formatjs/prefer-pound-in-plural': 'error', + + 'jsdoc/check-types': 'off', + 'jsdoc/no-undefined-types': 'off', + 'jsdoc/require-jsdoc': 'off', + 'jsdoc/require-param-description': 'off', + 'jsdoc/require-property-description': 'off', + 'jsdoc/require-returns-description': 'off', + 'jsdoc/require-returns': 'off', }, + + overrides: [ + { + files: [ + '*.config.js', + '.*rc.js', + 'ide-helper.js', + 'config/webpack/**/*', + 'config/formatjs-formatter.js', + ], + + env: { + commonjs: true, + }, + + parserOptions: { + sourceType: 'script', + }, + + rules: { + 'import/no-commonjs': 'off', + }, + }, + { + files: [ + '**/*.ts', + '**/*.tsx', + ], + + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/strict-type-checked', + 'plugin:@typescript-eslint/stylistic-type-checked', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:jsx-a11y/recommended', + 'plugin:import/recommended', + 'plugin:import/typescript', + 'plugin:promise/recommended', + 'plugin:jsdoc/recommended-typescript', + 'plugin:prettier/recommended', + ], + + parserOptions: { + project: true, + tsconfigRootDir: __dirname, + }, + + rules: { + 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], + + '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'], + '@typescript-eslint/consistent-type-exports': 'error', + '@typescript-eslint/consistent-type-imports': 'error', + "@typescript-eslint/prefer-nullish-coalescing": ['error', {ignorePrimitives: {boolean: true}}], + + 'jsdoc/require-jsdoc': 'off', + + // Those rules set stricter rules for TS files + // to enforce better practices when converting from JS + 'import/no-default-export': 'warn', + 'react/prefer-stateless-function': 'warn', + 'react/function-component-definition': ['error', { namedComponents: 'arrow-function' }], + 'react/jsx-uses-react': 'off', // not needed with new JSX transform + 'react/react-in-jsx-scope': 'off', // not needed with new JSX transform + 'react/prop-types': 'off', + }, + }, + { + files: [ + '**/__tests__/*.js', + '**/__tests__/*.jsx', + ], + + env: { + jest: true, + }, + }, + { + files: [ + 'streaming/**/*', + ], + rules: { + 'import/no-commonjs': 'off', + }, + }, + ], }; diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml deleted file mode 100644 index cdd08d2b0..000000000 --- a/.github/ISSUE_TEMPLATE/1.bug_report.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Bug Report -description: If something isn't working as expected -labels: bug -body: - - type: markdown - attributes: - value: | - Make sure that you are submitting a new bug that was not previously reported or already fixed. - - Please use a concise and distinct title for the issue. - - type: textarea - attributes: - label: Steps to reproduce the problem - description: What were you trying to do? - value: | - 1. - 2. - 3. - ... - validations: - required: true - - type: input - attributes: - label: Expected behaviour - description: What should have happened? - validations: - required: true - - type: input - attributes: - label: Actual behaviour - description: What happened? - validations: - required: true - - type: textarea - attributes: - label: Detailed description - validations: - required: false - - type: textarea - attributes: - label: Specifications - description: | - What version or commit hash of Mastodon did you find this bug in? - - If a front-end issue, what browser and operating systems were you using? - placeholder: | - Mastodon 3.5.3 (or Edge) - Ruby 2.7.6 (or v3.1.2) - Node.js 16.18.0 - - Google Chrome 106.0.5249.119 - Firefox 105.0.3 - - etc... - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/1.web_bug_report.yml b/.github/ISSUE_TEMPLATE/1.web_bug_report.yml new file mode 100644 index 000000000..20e27d103 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1.web_bug_report.yml @@ -0,0 +1,76 @@ +name: Bug Report (Web Interface) +description: If you are using Mastodon's web interface and something is not working as expected +labels: [bug, 'status/to triage', 'area/web interface'] +body: + - type: markdown + attributes: + value: | + Make sure that you are submitting a new bug that was not previously reported or already fixed. + + Please use a concise and distinct title for the issue. + - type: textarea + attributes: + label: Steps to reproduce the problem + description: What were you trying to do? + value: | + 1. + 2. + 3. + ... + validations: + required: true + - type: input + attributes: + label: Expected behaviour + description: What should have happened? + validations: + required: true + - type: input + attributes: + label: Actual behaviour + description: What happened? + validations: + required: true + - type: textarea + attributes: + label: Detailed description + validations: + required: false + - type: input + attributes: + label: Mastodon instance + description: The address of the Mastodon instance where you experienced the issue + placeholder: mastodon.social + validations: + required: true + - type: input + attributes: + label: Mastodon version + description: | + This is displayed at the bottom of the About page, eg. `v4.1.2+nightly-20230627` + placeholder: v4.1.2 + validations: + required: true + - type: input + attributes: + label: Browser name and version + description: | + What browser are you using when getting this bug? Please specify the version as well. + placeholder: Firefox 105.0.3 + validations: + required: true + - type: input + attributes: + label: Operating system + description: | + What OS are you running? Please specify the version as well. + placeholder: macOS 13.4.1 + validations: + required: true + - type: textarea + attributes: + label: Technical details + description: | + Any additional technical details you may have. This can include the full error log, inspector's output… + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/2.server_bug_report.yml b/.github/ISSUE_TEMPLATE/2.server_bug_report.yml new file mode 100644 index 000000000..49d5f5720 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2.server_bug_report.yml @@ -0,0 +1,65 @@ +name: Bug Report (server / API) +description: | + If something is not working as expected, but is not from using the web interface. +labels: [bug, 'status/to triage'] +body: + - type: markdown + attributes: + value: | + Make sure that you are submitting a new bug that was not previously reported or already fixed. + + Please use a concise and distinct title for the issue. + - type: textarea + attributes: + label: Steps to reproduce the problem + description: What were you trying to do? + value: | + 1. + 2. + 3. + ... + validations: + required: true + - type: input + attributes: + label: Expected behaviour + description: What should have happened? + validations: + required: true + - type: input + attributes: + label: Actual behaviour + description: What happened? + validations: + required: true + - type: textarea + attributes: + label: Detailed description + validations: + required: false + - type: input + attributes: + label: Mastodon instance + description: The address of the Mastodon instance where you experienced the issue + placeholder: mastodon.social + validations: + required: false + - type: input + attributes: + label: Mastodon version + description: | + This is displayed at the bottom of the About page, eg. `v4.1.2+nightly-20230627` + placeholder: v4.1.2 + validations: + required: false + - type: textarea + attributes: + label: Technical details + description: | + Any additional technical details you may have, like logs or error traces + value: | + If this is happening on your own Mastodon server, please fill out those: + - Ruby version: (from `ruby --version`, eg. v3.1.2) + - Node.js version: (from `node --version`, eg. v18.16.0) + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/2.feature_request.yml b/.github/ISSUE_TEMPLATE/3.feature_request.yml similarity index 96% rename from .github/ISSUE_TEMPLATE/2.feature_request.yml rename to .github/ISSUE_TEMPLATE/3.feature_request.yml index 6626c2876..2cabcf61e 100644 --- a/.github/ISSUE_TEMPLATE/2.feature_request.yml +++ b/.github/ISSUE_TEMPLATE/3.feature_request.yml @@ -1,6 +1,6 @@ name: Feature Request description: I have a suggestion -labels: suggestion +labels: [suggestion] body: - type: markdown attributes: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 6174c499b..c76a40345 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,4 +2,4 @@ blank_issues_enabled: true contact_links: - name: GitHub Discussions url: https://github.com/mastodon/mastodon/discussions - about: Please ask and answer questions here. \ No newline at end of file + about: Please ask and answer questions here. diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 000000000..879a564e1 --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,124 @@ +{ + $schema: 'https://docs.renovatebot.com/renovate-schema.json', + extends: [ + 'config:recommended', + ':labels(dependencies)', + ':maintainLockFilesMonthly', // update non-direct dependencies monthly + ':prConcurrentLimitNone', // Remove limit for open PRs at any time. + ':prHourlyLimit2', // Rate limit PR creation to a maximum of two per hour. + ], + minimumReleaseAge: '3', // Wait 3 days after the package has been published before upgrading it + // packageRules order is important, they are applied from top to bottom and are merged, + // meaning the most important ones must be at the bottom, for example grouping rules + // If we do not want a package to be grouped with others, we need to set its groupName + // to `null` after any other rule set it to something. + dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).', + packageRules: [ + { + // Require Dependency Dashboard Approval for major version bumps of these node packages + matchManagers: ['npm'], + matchPackageNames: [ + 'tesseract.js', // Requires code changes + 'react-hotkeys', // Requires code changes + + // Requires Webpacker upgrade or replacement + '@types/webpack', + 'babel-loader', + 'compression-webpack-plugin', + 'css-loader', + 'imports-loader', + 'mini-css-extract-plugin', + 'postcss-loader', + 'sass-loader', + 'terser-webpack-plugin', + 'webpack', + 'webpack-assets-manifest', + 'webpack-bundle-analyzer', + 'webpack-dev-server', + 'webpack-cli', + + // react-router: Requires manual upgrade + 'history', + 'react-router-dom', + ], + matchUpdateTypes: ['major'], + dependencyDashboardApproval: true, + }, + { + // Require Dependency Dashboard Approval for major version bumps of these Ruby packages + matchManagers: ['bundler'], + matchPackageNames: [ + 'rack', // Needs to be synced with Rails version + 'sprockets', // Requires manual upgrade https://github.com/rails/sprockets/blob/master/UPGRADING.md#guide-to-upgrading-from-sprockets-3x-to-4x + 'strong_migrations', // Requires manual upgrade + 'sidekiq', // Requires manual upgrade + 'sidekiq-unique-jobs', // Requires manual upgrades and sync with Sidekiq version + 'redis', // Requires manual upgrade and sync with Sidekiq version + ], + matchUpdateTypes: ['major'], + dependencyDashboardApproval: true, + }, + { + // Update Github Actions and Docker images weekly + matchManagers: ['github-actions', 'dockerfile', 'docker-compose'], + extends: ['schedule:weekly'], + }, + { + // Require Dependency Dashboard Approval for major & minor bumps for the ruby image, this needs to be synced with .ruby-version + matchManagers: ['dockerfile'], + matchPackageNames: ['moritzheiber/ruby-jemalloc'], + matchUpdateTypes: ['minor', 'major'], + dependencyDashboardApproval: true, + }, + { + // Require Dependency Dashboard Approval for major bumps for the node image, this needs to be synced with .nvmrc + matchManagers: ['dockerfile'], + matchPackageNames: ['node'], + matchUpdateTypes: ['major'], + dependencyDashboardApproval: true, + }, + { + // Require Dependency Dashboard Approval for major postgres bumps in the docker-compose file, as those break dev environments + matchManagers: ['docker-compose'], + matchPackageNames: ['postgres'], + matchUpdateTypes: ['major'], + dependencyDashboardApproval: true, + }, + { + // Update devDependencies every week, with one grouped PR + matchDepTypes: 'devDependencies', + matchUpdateTypes: ['patch', 'minor'], + groupName: 'devDependencies (non-major)', + extends: ['schedule:weekly'], + }, + { + // Group all eslint-related packages with `eslint` in the same PR + matchManagers: ['npm'], + matchPackageNames: ['eslint'], + matchPackagePrefixes: ['eslint-', '@typescript-eslint/'], + matchUpdateTypes: ['patch', 'minor'], + groupName: 'eslint (non-major)', + }, + { + // Update @types/* packages every week, with one grouped PR + matchPackagePrefixes: '@types/', + matchUpdateTypes: ['patch', 'minor'], + groupName: 'DefinitelyTyped types (non-major)', + extends: ['schedule:weekly'], + addLabels: ['typescript'], + }, + { + // We want those packages to always have their own PR + matchManagers: ['npm'], + matchPackageNames: [ + 'typescript', // Typescript has code-impacting changes in minor versions + ], + groupName: null, // We dont want them to belong to any group + }, + // Add labels depending on package manager + { matchManagers: ['npm', 'nvm'], addLabels: ['javascript'] }, + { matchManagers: ['bundler', 'ruby-version'], addLabels: ['ruby'] }, + { matchManagers: ['docker-compose', 'dockerfile'], addLabels: ['docker'] }, + { matchManagers: ['github-actions'], addLabels: ['github_actions'] }, + ], +} diff --git a/.github/workflows/build-container-image.yml b/.github/workflows/build-container-image.yml index b9aebcc46..aa9e74e7e 100644 --- a/.github/workflows/build-container-image.yml +++ b/.github/workflows/build-container-image.yml @@ -11,6 +11,10 @@ on: type: boolean push_to_images: type: string + version_prerelease: + type: string + version_metadata: + type: string flavor: type: string tags: @@ -23,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: docker/setup-qemu-action@v2 if: contains(inputs.platforms, 'linux/arm64') && !inputs.use_native_arm64_builder @@ -82,6 +86,9 @@ jobs: - uses: docker/build-push-action@v4 with: context: . + build-args: | + MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }} + MASTODON_VERSION_METADATA=${{ inputs.version_metadata }} platforms: ${{ inputs.platforms }} provenance: false builder: ${{ steps.buildx.outputs.name || steps.buildx-native.outputs.name }} diff --git a/.github/workflows/build-push-pr.yml b/.github/workflows/build-push-pr.yml new file mode 100644 index 000000000..1f647e2a1 --- /dev/null +++ b/.github/workflows/build-push-pr.yml @@ -0,0 +1,41 @@ +name: Build container image for PR +on: + pull_request: + types: [labeled, synchronize, reopened, ready_for_review, opened] + +permissions: + contents: read + packages: write + +jobs: + compute-suffix: + runs-on: ubuntu-latest + # This is only allowed to run if: + # - the PR branch is in the `mastodon/mastodon` repository + # - the PR is not a draft + # - the PR has the "build-image" label + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !github.event.pull_request.draft && contains(github.event.pull_request.labels.*.name, 'build-image') }} + steps: + # Repository needs to be cloned so `git rev-parse` below works + - name: Clone repository + uses: actions/checkout@v4 + - id: version_vars + run: | + echo mastodon_version_metadata=pr-${{ github.event.pull_request.number }}-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT + outputs: + metadata: ${{ steps.version_vars.outputs.mastodon_version_metadata }} + + build-image: + needs: compute-suffix + uses: ./.github/workflows/build-container-image.yml + with: + platforms: linux/amd64,linux/arm64 + use_native_arm64_builder: true + push_to_images: | + ghcr.io/mastodon/mastodon + version_metadata: ${{ needs.compute-suffix.outputs.metadata }} + flavor: | + latest=auto + tags: | + type=ref,event=pr + secrets: inherit diff --git a/.github/workflows/build-releases.yml b/.github/workflows/build-releases.yml index c19766b18..3b82eef9d 100644 --- a/.github/workflows/build-releases.yml +++ b/.github/workflows/build-releases.yml @@ -19,8 +19,10 @@ jobs: ghcr.io/mastodon/mastodon # Do not use cache when building releases, so apt update is always ran and the release always contain the latest packages cache: false + # Only tag with latest when ran against the latest stable branch + # This needs to be updated after each minor version release flavor: | - latest=false + latest=${{ startsWith(github.ref, 'refs/tags/v4.2.') }} tags: | type=pep440,pattern={{raw}} type=pep440,pattern=v{{major}}.{{minor}} diff --git a/.github/workflows/bundler-audit.yml b/.github/workflows/bundler-audit.yml new file mode 100644 index 000000000..bfb93a36c --- /dev/null +++ b/.github/workflows/bundler-audit.yml @@ -0,0 +1,40 @@ +name: Bundler Audit +on: + push: + branches-ignore: + - 'dependabot/**' + paths: + - 'Gemfile*' + - '.ruby-version' + - '.bundler-audit.yml' + - '.github/workflows/bundler-audit.yml' + + pull_request: + paths: + - 'Gemfile*' + - '.ruby-version' + - '.bundler-audit.yml' + - '.github/workflows/bundler-audit.yml' + + schedule: + - cron: '0 5 * * 1' + +jobs: + security: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Install native Ruby dependencies + run: sudo apt-get install -y libicu-dev libidn11-dev + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Run bundler-audit + run: bundle exec bundler-audit diff --git a/.github/workflows/check-i18n.yml b/.github/workflows/check-i18n.yml index a9d8ea2ea..39cf32ddc 100644 --- a/.github/workflows/check-i18n.yml +++ b/.github/workflows/check-i18n.yml @@ -14,24 +14,49 @@ permissions: jobs: check-i18n: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - name: Install system dependencies run: | sudo apt-get update sudo apt-get install -y libicu-dev libidn11-dev + - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.0' + ruby-version: .ruby-version bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + cache: yarn + node-version-file: '.nvmrc' + + - name: Install all yarn packages + run: yarn --frozen-lockfile + + - name: Check for missing strings in English JSON + run: | + yarn i18n:extract --throws + git diff --exit-code + - name: Check locale file normalization run: bundle exec i18n-tasks check-normalized + - name: Check for unused strings - run: bundle exec i18n-tasks unused -l en + run: bundle exec i18n-tasks unused + + - name: Check for missing strings in English YML + run: | + bundle exec i18n-tasks add-missing -l en + git diff --exit-code + - name: Check for wrong string interpolations run: bundle exec i18n-tasks check-consistent-interpolations + - name: Check that all required locale files exist run: bundle exec rake repo:check_locales_files diff --git a/.github/workflows/haml-lint-problem-matcher.json b/.github/workflows/haml-lint-problem-matcher.json new file mode 100644 index 000000000..3523ea295 --- /dev/null +++ b/.github/workflows/haml-lint-problem-matcher.json @@ -0,0 +1,17 @@ +{ + "problemMatcher": [ + { + "owner": "haml-lint", + "severity": "warning", + "pattern": [ + { + "regexp": "^(.*):(\\d+)\\s\\[W]\\s(.*):\\s(.*)$", + "file": 1, + "line": 2, + "code": 3, + "message": 4 + } + ] + } + ] +} diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml new file mode 100644 index 000000000..bd775dba2 --- /dev/null +++ b/.github/workflows/lint-css.yml @@ -0,0 +1,52 @@ +name: CSS Linting +on: + push: + branches-ignore: + - 'dependabot/**' + - 'renovate/**' + paths: + - 'package.json' + - 'yarn.lock' + - '.nvmrc' + - '.prettier*' + - 'stylelint.config.js' + - '**/*.css' + - '**/*.scss' + - '.github/workflows/lint-css.yml' + - '.github/stylelint-matcher.json' + + pull_request: + paths: + - 'package.json' + - 'yarn.lock' + - '.nvmrc' + - '.prettier*' + - 'stylelint.config.js' + - '**/*.css' + - '**/*.scss' + - '.github/workflows/lint-css.yml' + - '.github/stylelint-matcher.json' + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + cache: yarn + node-version-file: '.nvmrc' + + - name: Install all yarn packages + run: yarn --frozen-lockfile + + - uses: xt0rted/stylelint-problem-matcher@v1 + + - run: echo "::add-matcher::.github/stylelint-matcher.json" + + - name: Stylelint + run: yarn lint:sass diff --git a/.github/workflows/lint-haml.yml b/.github/workflows/lint-haml.yml new file mode 100644 index 000000000..ca9bd66a4 --- /dev/null +++ b/.github/workflows/lint-haml.yml @@ -0,0 +1,47 @@ +name: Haml Linting +on: + push: + branches-ignore: + - 'dependabot/**' + - 'renovate/**' + paths: + - '.github/workflows/haml-lint-problem-matcher.json' + - '.github/workflows/lint-haml.yml' + - '.haml-lint*.yml' + - '.rubocop*.yml' + - '.ruby-version' + - '**/*.haml' + - 'Gemfile*' + + pull_request: + paths: + - '.github/workflows/haml-lint-problem-matcher.json' + - '.github/workflows/lint-haml.yml' + - '.haml-lint*.yml' + - '.rubocop*.yml' + - '.ruby-version' + - '**/*.haml' + - 'Gemfile*' + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Install native Ruby dependencies + run: | + sudo apt-get update + sudo apt-get install -y libicu-dev libidn11-dev + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Run haml-lint + run: | + echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json" + bundle exec haml-lint diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml new file mode 100644 index 000000000..67d28589c --- /dev/null +++ b/.github/workflows/lint-js.yml @@ -0,0 +1,55 @@ +name: JavaScript Linting +on: + push: + branches-ignore: + - 'dependabot/**' + - 'renovate/**' + paths: + - 'package.json' + - 'yarn.lock' + - 'tsconfig.json' + - '.nvmrc' + - '.prettier*' + - '.eslint*' + - '**/*.js' + - '**/*.jsx' + - '**/*.ts' + - '**/*.tsx' + - '.github/workflows/lint-js.yml' + + pull_request: + paths: + - 'package.json' + - 'yarn.lock' + - 'tsconfig.json' + - '.nvmrc' + - '.prettier*' + - '.eslint*' + - '**/*.js' + - '**/*.jsx' + - '**/*.ts' + - '**/*.tsx' + - '.github/workflows/lint-js.yml' + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + cache: yarn + node-version-file: '.nvmrc' + + - name: Install all yarn packages + run: yarn --frozen-lockfile + + - name: ESLint + run: yarn lint:js --max-warnings 0 + + - name: Typecheck + run: yarn typecheck diff --git a/.github/workflows/lint-json.yml b/.github/workflows/lint-json.yml new file mode 100644 index 000000000..1d98c5267 --- /dev/null +++ b/.github/workflows/lint-json.yml @@ -0,0 +1,44 @@ +name: JSON Linting +on: + push: + branches-ignore: + - 'dependabot/**' + - 'renovate/**' + paths: + - 'package.json' + - 'yarn.lock' + - '.nvmrc' + - '.prettier*' + - '**/*.json' + - '.github/workflows/lint-json.yml' + - '!app/javascript/mastodon/locales/*.json' + + pull_request: + paths: + - 'package.json' + - 'yarn.lock' + - '.nvmrc' + - '.prettier*' + - '**/*.json' + - '.github/workflows/lint-json.yml' + - '!app/javascript/mastodon/locales/*.json' + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + cache: yarn + node-version-file: '.nvmrc' + + - name: Install all yarn packages + run: yarn --frozen-lockfile + + - name: Prettier + run: yarn lint:json diff --git a/.github/workflows/lint-md.yml b/.github/workflows/lint-md.yml new file mode 100644 index 000000000..1b3f92c97 --- /dev/null +++ b/.github/workflows/lint-md.yml @@ -0,0 +1,44 @@ +name: Markdown Linting +on: + push: + branches-ignore: + - 'dependabot/**' + - 'renovate/**' + paths: + - '.github/workflows/lint-md.yml' + - '.nvmrc' + - '.prettier*' + - '**/*.md' + - '!AUTHORS.md' + - 'package.json' + - 'yarn.lock' + + pull_request: + paths: + - '.github/workflows/lint-md.yml' + - '.nvmrc' + - '.prettier*' + - '**/*.md' + - '!AUTHORS.md' + - 'package.json' + - 'yarn.lock' + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + cache: yarn + node-version-file: '.nvmrc' + + - name: Install all yarn packages + run: yarn --frozen-lockfile + + - name: Prettier + run: yarn lint:md diff --git a/.github/workflows/lint-ruby.yml b/.github/workflows/lint-ruby.yml new file mode 100644 index 000000000..92882a084 --- /dev/null +++ b/.github/workflows/lint-ruby.yml @@ -0,0 +1,51 @@ +name: Ruby Linting +on: + push: + branches-ignore: + - 'dependabot/**' + - 'renovate/**' + paths: + - 'Gemfile*' + - '.rubocop*.yml' + - '.ruby-version' + - 'config/brakeman.ignore' + - '**/*.rb' + - '**/*.rake' + - '.github/workflows/lint-ruby.yml' + + pull_request: + paths: + - 'Gemfile*' + - '.rubocop*.yml' + - '.ruby-version' + - 'config/brakeman.ignore' + - '**/*.rb' + - '**/*.rake' + - '.github/workflows/lint-ruby.yml' + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Install native Ruby dependencies + run: sudo apt-get install -y libicu-dev libidn11-dev + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Set-up RuboCop Problem Matcher + uses: r7kamura/rubocop-problem-matchers-action@v1 + + - name: Run rubocop + run: bundle exec rubocop + + - name: Run brakeman + if: always() # Run both checks, even if the first failed + run: bundle exec brakeman diff --git a/.github/workflows/lint-yml.yml b/.github/workflows/lint-yml.yml new file mode 100644 index 000000000..e77cc9889 --- /dev/null +++ b/.github/workflows/lint-yml.yml @@ -0,0 +1,46 @@ +name: YML Linting +on: + push: + branches-ignore: + - 'dependabot/**' + - 'renovate/**' + paths: + - 'package.json' + - 'yarn.lock' + - '.nvmrc' + - '.prettier*' + - '**/*.yaml' + - '**/*.yml' + - '.github/workflows/lint-yml.yml' + - '!config/locales/*.yml' + + pull_request: + paths: + - 'package.json' + - 'yarn.lock' + - '.nvmrc' + - '.prettier*' + - '**/*.yaml' + - '**/*.yml' + - '.github/workflows/lint-yml.yml' + - '!config/locales/*.yml' + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + cache: yarn + node-version-file: '.nvmrc' + + - name: Install all yarn packages + run: yarn --frozen-lockfile + + - name: Prettier + run: yarn lint:yml diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml deleted file mode 100644 index cd8cb12c4..000000000 --- a/.github/workflows/linter.yml +++ /dev/null @@ -1,83 +0,0 @@ ---- -################################# -################################# -## Super Linter GitHub Actions ## -################################# -################################# -name: Lint Code Base - -# -# Documentation: -# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions -# - -############################# -# Start the job on all push # -############################# -on: - push: - branches-ignore: [main] - # Remove the line above to run when pushing to master - pull_request: - branches: [main] - -############### -# Set the Job # -############### -permissions: - checks: write - contents: read - pull-requests: write - statuses: write - -jobs: - build: - # Name the Job - name: Lint Code Base - # Set the agent to run on - runs-on: ubuntu-latest - - ################## - # Load all steps # - ################## - steps: - ########################## - # Checkout the code base # - ########################## - - name: Checkout Code - uses: actions/checkout@v3 - with: - # Full git history is needed to get a proper list of changed files within `super-linter` - fetch-depth: 0 - - - name: Set-up Node.js - uses: actions/setup-node@v3 - with: - node-version: 16.x - cache: yarn - - name: Install dependencies - run: yarn install --frozen-lockfile - - name: Set-up RuboCop Problem Mathcher - uses: r7kamura/rubocop-problem-matchers-action@v1 - - name: Set-up Stylelint Problem Matcher - uses: xt0rted/stylelint-problem-matcher@v1 - # https://github.com/xt0rted/stylelint-problem-matcher/issues/360 - - run: echo "::add-matcher::.github/stylelint-matcher.json" - - ################################ - # Run Linter against code base # - ################################ - - name: Lint Code Base - uses: github/super-linter@v4 - env: - CSS_FILE_NAME: stylelint.config.js - DEFAULT_BRANCH: main - NO_COLOR: 1 # https://github.com/xt0rted/stylelint-problem-matcher/issues/360 - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.js - LINTER_RULES_PATH: . - RUBY_CONFIG_FILE: .rubocop.yml - VALIDATE_ALL_CODEBASE: false - VALIDATE_CSS: true - VALIDATE_JAVASCRIPT_ES: true - VALIDATE_RUBY: true diff --git a/.github/workflows/test-chart.yml b/.github/workflows/test-chart.yml deleted file mode 100644 index b9ff80855..000000000 --- a/.github/workflows/test-chart.yml +++ /dev/null @@ -1,138 +0,0 @@ -# This is a GitHub workflow defining a set of jobs with a set of steps. -# ref: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions -# -name: Test chart - -on: - pull_request: - paths: - - "chart/**" - - "!**.md" - - ".github/workflows/test-chart.yml" - push: - paths: - - "chart/**" - - "!**.md" - - ".github/workflows/test-chart.yml" - branches-ignore: - - "dependabot/**" - workflow_dispatch: - -permissions: - contents: read - -defaults: - run: - working-directory: chart - -jobs: - lint-templates: - runs-on: ubuntu-22.04 - - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.x" - - - name: Install dependencies (yamllint) - run: pip install yamllint - - - run: helm dependency update - - - name: helm lint - run: | - helm lint . \ - --values dev-values.yaml - - - name: helm template - run: | - helm template . \ - --values dev-values.yaml \ - --output-dir rendered-templates - - - name: yamllint (only on templates we manage) - run: | - rm -rf rendered-templates/mastodon/charts - - yamllint rendered-templates \ - --config-data "{rules: {indentation: {spaces: 2}, line-length: disable}}" - - # This job helps us validate that rendered templates are valid k8s resources - # against a k8s api-server, via "helm template --validate", but also that a - # basic configuration can be used to successfully startup mastodon. - # - test-install: - runs-on: ubuntu-22.04 - timeout-minutes: 15 - - strategy: - fail-fast: false - matrix: - include: - # k3s-channel reference: https://update.k3s.io/v1-release/channels - - k3s-channel: latest - - k3s-channel: stable - - # This represents the oldest configuration we test against. - # - # The k8s version chosen is based on the oldest still supported k8s - # version among two managed k8s services, GKE, EKS. - # - GKE: https://endoflife.date/google-kubernetes-engine - # - EKS: https://endoflife.date/amazon-eks - # - # The helm client's version can influence what helper functions is - # available for use in the templates, currently we need v3.6.0 or - # higher. - # - - k3s-channel: v1.21 - helm-version: v3.6.0 - - steps: - - uses: actions/checkout@v3 - - # This action starts a k8s cluster with NetworkPolicy enforcement and - # installs both kubectl and helm. - # - # ref: https://github.com/jupyterhub/action-k3s-helm#readme - # - - uses: jupyterhub/action-k3s-helm@v3 - with: - k3s-channel: ${{ matrix.k3s-channel }} - helm-version: ${{ matrix.helm-version }} - metrics-enabled: false - traefik-enabled: false - docker-enabled: false - - - run: helm dependency update - - # Validate rendered helm templates against the k8s api-server - - name: helm template --validate - run: | - helm template --validate mastodon . \ - --values dev-values.yaml - - - name: helm install - run: | - helm install mastodon . \ - --values dev-values.yaml \ - --timeout 10m - - # This actions provides a report about the state of the k8s cluster, - # providing logs etc on anything that has failed and workloads marked as - # important. - # - # ref: https://github.com/jupyterhub/action-k8s-namespace-report#readme - # - - name: Kubernetes namespace report - uses: jupyterhub/action-k8s-namespace-report@v1 - if: always() - with: - important-workloads: >- - deploy/mastodon-sidekiq - deploy/mastodon-streaming - deploy/mastodon-web - job/mastodon-assets-precompile - job/mastodon-chewy-upgrade - job/mastodon-create-admin - job/mastodon-db-migrate diff --git a/.github/workflows/test-image-build.yml b/.github/workflows/test-image-build.yml index 71344c004..778e34177 100644 --- a/.github/workflows/test-image-build.yml +++ b/.github/workflows/test-image-build.yml @@ -1,6 +1,12 @@ name: Test container image build on: pull_request: + paths: + - .github/workflows/build-nightly.yml + - .github/workflows/build-push-pr.yml + - .github/workflows/build-releases.yml + - .github/workflows/test-image-build.yml + - Dockerfile permissions: contents: read diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml new file mode 100644 index 000000000..0ef1d9b7c --- /dev/null +++ b/.github/workflows/test-js.yml @@ -0,0 +1,48 @@ +name: JavaScript Testing +on: + push: + branches-ignore: + - 'dependabot/**' + - 'renovate/**' + paths: + - 'package.json' + - 'yarn.lock' + - '.nvmrc' + - '**/*.js' + - '**/*.jsx' + - '**/*.ts' + - '**/*.tsx' + - '**/*.snap' + - '.github/workflows/test-js.yml' + + pull_request: + paths: + - 'package.json' + - 'yarn.lock' + - '.nvmrc' + - '**/*.js' + - '**/*.jsx' + - '**/*.ts' + - '**/*.tsx' + - '**/*.snap' + - '.github/workflows/test-js.yml' + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + cache: yarn + node-version-file: '.nvmrc' + + - name: Install all yarn packages + run: yarn --frozen-lockfile + + - name: Jest testing + run: yarn jest --reporters github-actions summary diff --git a/.github/workflows/test-migrations-one-step.yml b/.github/workflows/test-migrations-one-step.yml new file mode 100644 index 000000000..59287e88c --- /dev/null +++ b/.github/workflows/test-migrations-one-step.yml @@ -0,0 +1,111 @@ +name: Test one step migrations +on: + push: + branches-ignore: + - 'dependabot/**' + - 'renovate/**' + pull_request: + +jobs: + pre_job: + runs-on: ubuntu-latest + + outputs: + should_skip: ${{ steps.skip_check.outputs.should_skip }} + + steps: + - id: skip_check + uses: fkirc/skip-duplicate-actions@v5 + with: + paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations-one-step.yml", "lib/tasks/tests.rake"]' + + test: + runs-on: ubuntu-latest + needs: pre_job + if: needs.pre_job.outputs.should_skip != 'true' + + strategy: + fail-fast: false + + matrix: + postgres: + - 14-alpine + - 15-alpine + + services: + postgres: + image: postgres:${{ matrix.postgres}} + env: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + env: + CONTINUOUS_INTEGRATION: true + DB_HOST: localhost + DB_USER: postgres + DB_PASS: postgres + DISABLE_SIMPLECOV: true + RAILS_ENV: test + BUNDLE_CLEAN: true + BUNDLE_FROZEN: true + BUNDLE_WITHOUT: 'development production' + BUNDLE_JOBS: 3 + BUNDLE_RETRY: 3 + + steps: + - uses: actions/checkout@v4 + + - name: Install native Ruby dependencies + run: | + sudo apt-get update + sudo apt-get install -y libicu-dev libidn11-dev + + - name: Set up bundler cache + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Create database + run: './bin/rails db:create' + + - name: Run migrations up to v2.0.0 + run: './bin/rails db:migrate VERSION=20171010025614' + + - name: Populate database with test data + run: './bin/rails tests:migrations:populate_v2' + + - name: Run migrations up to v2.4.0 + run: './bin/rails db:migrate VERSION=20180514140000' + + - name: Populate database with test data + run: './bin/rails tests:migrations:populate_v2_4' + + - name: Run migrations up to v2.4.3 + run: './bin/rails db:migrate VERSION=20180707154237' + + - name: Populate database with test data + run: './bin/rails tests:migrations:populate_v2_4_3' + + - name: Run all remaining migrations + run: './bin/rails db:migrate' + + - name: Check migration result + run: './bin/rails tests:migrations:check_database' diff --git a/.github/workflows/test-migrations-two-step.yml b/.github/workflows/test-migrations-two-step.yml new file mode 100644 index 000000000..8f3c84d8f --- /dev/null +++ b/.github/workflows/test-migrations-two-step.yml @@ -0,0 +1,119 @@ +name: Test two step migrations +on: + push: + branches-ignore: + - 'dependabot/**' + - 'renovate/**' + pull_request: + +jobs: + pre_job: + runs-on: ubuntu-latest + + outputs: + should_skip: ${{ steps.skip_check.outputs.should_skip }} + + steps: + - id: skip_check + uses: fkirc/skip-duplicate-actions@v5 + with: + paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations-two-step.yml", "lib/tasks/tests.rake"]' + + test: + runs-on: ubuntu-latest + needs: pre_job + if: needs.pre_job.outputs.should_skip != 'true' + + strategy: + fail-fast: false + + matrix: + postgres: + - 14-alpine + - 15-alpine + + services: + postgres: + image: postgres:${{ matrix.postgres}} + env: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + env: + CONTINUOUS_INTEGRATION: true + DB_HOST: localhost + DB_USER: postgres + DB_PASS: postgres + DISABLE_SIMPLECOV: true + RAILS_ENV: test + BUNDLE_CLEAN: true + BUNDLE_FROZEN: true + BUNDLE_WITHOUT: 'development production' + BUNDLE_JOBS: 3 + BUNDLE_RETRY: 3 + + steps: + - uses: actions/checkout@v4 + + - name: Install native Ruby dependencies + run: | + sudo apt-get update + sudo apt-get install -y libicu-dev libidn11-dev + + - name: Set up bundler cache + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Create database + run: './bin/rails db:create' + + - name: Run migrations up to v2.0.0 + run: './bin/rails db:migrate VERSION=20171010025614' + + - name: Populate database with test data + run: './bin/rails tests:migrations:populate_v2' + + - name: Run pre-deployment migrations up to v2.4.0 + run: './bin/rails db:migrate VERSION=20180514140000' + env: + SKIP_POST_DEPLOYMENT_MIGRATIONS: true + + - name: Populate database with test data + run: './bin/rails tests:migrations:populate_v2_4' + + - name: Run migrations up to v2.4.3 + run: './bin/rails db:migrate VERSION=20180707154237' + env: + SKIP_POST_DEPLOYMENT_MIGRATIONS: true + + - name: Populate database with test data + run: './bin/rails tests:migrations:populate_v2_4_3' + + - name: Run all remaining pre-deployment migrations + run: './bin/rails db:migrate' + env: + SKIP_POST_DEPLOYMENT_MIGRATIONS: true + + - name: Run all post-deployment migrations + run: './bin/rails db:migrate' + + - name: Check migration result + run: './bin/rails tests:migrations:check_database' diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml new file mode 100644 index 000000000..343dc36ca --- /dev/null +++ b/.github/workflows/test-ruby.yml @@ -0,0 +1,365 @@ +name: Ruby Testing + +on: + push: + branches-ignore: + - 'dependabot/**' + - 'renovate/**' + pull_request: + +env: + BUNDLE_CLEAN: true + BUNDLE_FROZEN: true + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + fail-fast: true + matrix: + mode: + - production + - test + env: + RAILS_ENV: ${{ matrix.mode }} + BUNDLE_WITH: ${{ matrix.mode }} + OTP_SECRET: precompile_placeholder + SECRET_KEY_BASE: precompile_placeholder + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + cache: yarn + node-version-file: '.nvmrc' + + - name: Install native Ruby dependencies + run: | + sudo apt-get update + sudo apt-get install -y libicu-dev libidn11-dev + + - name: Set up bundler cache + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - run: yarn --frozen-lockfile --production + - name: Precompile assets + # Previously had set this, but it's not supported + # export NODE_OPTIONS=--openssl-legacy-provider + run: |- + ./bin/rails assets:precompile + + - uses: actions/upload-artifact@v3 + if: matrix.mode == 'test' + with: + path: |- + ./public/assets + ./public/packs-test + name: ${{ github.sha }} + retention-days: 0 + + test: + runs-on: ubuntu-latest + + needs: + - build + + services: + postgres: + image: postgres:14-alpine + env: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + env: + DB_HOST: localhost + DB_USER: postgres + DB_PASS: postgres + DISABLE_SIMPLECOV: true + RAILS_ENV: test + ALLOW_NOPAM: true + PAM_ENABLED: true + PAM_DEFAULT_SERVICE: pam_test + PAM_CONTROLLED_SERVICE: pam_test_controlled + OIDC_ENABLED: true + OIDC_SCOPE: read + SAML_ENABLED: true + CAS_ENABLED: true + BUNDLE_WITH: 'pam_authentication test' + CI_JOBS: ${{ matrix.ci_job }}/4 + + strategy: + fail-fast: false + matrix: + ruby-version: + - '3.0' + - '3.1' + - '.ruby-version' + ci_job: + - 1 + - 2 + - 3 + - 4 + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v3 + with: + path: './public' + name: ${{ github.sha }} + + - name: Update package index + run: sudo apt-get update + + - name: Install native Ruby dependencies + run: sudo apt-get install -y libicu-dev libidn11-dev + + - name: Install additional system dependencies + run: sudo apt-get install -y ffmpeg imagemagick libpam-dev + + - name: Set up bundler cache + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version}} + bundler-cache: true + + - name: Load database schema + run: './bin/rails db:create db:schema:load db:seed' + + - run: bundle exec rake rspec_chunked + + test-e2e: + name: End to End testing + runs-on: ubuntu-latest + + needs: + - build + + services: + postgres: + image: postgres:14-alpine + env: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + env: + DB_HOST: localhost + DB_USER: postgres + DB_PASS: postgres + DISABLE_SIMPLECOV: true + RAILS_ENV: test + BUNDLE_WITH: test + + strategy: + fail-fast: false + matrix: + ruby-version: + - '3.0' + - '3.1' + - '.ruby-version' + + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v3 + with: + path: './public' + name: ${{ github.sha }} + + - name: Update package index + run: sudo apt-get update + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + cache: yarn + node-version-file: '.nvmrc' + + - name: Install native Ruby dependencies + run: sudo apt-get install -y libicu-dev libidn11-dev + + - name: Install additional system dependencies + run: sudo apt-get install -y ffmpeg imagemagick + + - name: Set up bundler cache + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version}} + bundler-cache: true + + - run: yarn --frozen-lockfile + + - name: Load database schema + run: './bin/rails db:create db:schema:load db:seed' + + - run: bundle exec rake spec:system + + - name: Archive logs + uses: actions/upload-artifact@v3 + if: failure() + with: + name: e2e-logs-${{ matrix.ruby-version }} + path: log/ + + - name: Archive test screenshots + uses: actions/upload-artifact@v3 + if: failure() + with: + name: e2e-screenshots + path: tmp/screenshots/ + + test-search: + name: Testing search + runs-on: ubuntu-latest + + needs: + - build + + services: + postgres: + image: postgres:14-alpine + env: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:7.17.13 + env: + discovery.type: single-node + xpack.security.enabled: false + options: >- + --health-cmd "curl http://localhost:9200/_cluster/health" + --health-interval 10s + --health-timeout 5s + --health-retries 10 + ports: + - 9200:9200 + + env: + DB_HOST: localhost + DB_USER: postgres + DB_PASS: postgres + DISABLE_SIMPLECOV: true + RAILS_ENV: test + BUNDLE_WITH: test + ES_ENABLED: true + ES_HOST: localhost + ES_PORT: 9200 + + strategy: + fail-fast: false + matrix: + ruby-version: + - '3.0' + - '3.1' + - '.ruby-version' + + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v3 + with: + path: './public' + name: ${{ github.sha }} + + - name: Update package index + run: sudo apt-get update + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + cache: yarn + node-version-file: '.nvmrc' + + - name: Install native Ruby dependencies + run: sudo apt-get install -y libicu-dev libidn11-dev + + - name: Install additional system dependencies + run: sudo apt-get install -y ffmpeg imagemagick + + - name: Set up bundler cache + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version}} + bundler-cache: true + + - run: yarn --frozen-lockfile + + - name: Load database schema + run: './bin/rails db:create db:schema:load db:seed' + + - run: bundle exec rake spec:search + + - name: Archive logs + uses: actions/upload-artifact@v3 + if: failure() + with: + name: test-search-logs-${{ matrix.ruby-version }} + path: log/ + + - name: Archive test screenshots + uses: actions/upload-artifact@v3 + if: failure() + with: + name: test-search-screenshots + path: tmp/screenshots/ diff --git a/.gitignore b/.gitignore index 7d76b8275..2bc8b18c8 100644 --- a/.gitignore +++ b/.gitignore @@ -44,12 +44,6 @@ /redis /elasticsearch -# ignore Helm charts -/chart/*.tgz - -# ignore Helm dependency charts -/chart/charts/*.tgz - # Ignore Apple files .DS_Store diff --git a/.haml-lint.yml b/.haml-lint.yml index 7853d81d7..d1ed30b26 100644 --- a/.haml-lint.yml +++ b/.haml-lint.yml @@ -1,108 +1,14 @@ -# Whether to ignore frontmatter at the beginning of HAML documents for -# frameworks such as Jekyll/Middleman -skip_frontmatter: false +inherits_from: .haml-lint_todo.yml exclude: - 'vendor/**/*' - - 'spec/**/*' - - 'lib/templates/**/*' - - 'app/views/kaminari/**/*' + - lib/templates/haml/scaffold/_form.html.haml + +require: + - ./lib/linter/haml_middle_dot.rb linters: AltText: - enabled: false - - ClassAttributeWithStaticValue: enabled: true - - ClassesBeforeIds: - enabled: true - - ConsecutiveComments: - enabled: true - - ConsecutiveSilentScripts: - enabled: true - max_consecutive: 2 - - EmptyObjectReference: - enabled: true - - EmptyScript: - enabled: true - - FinalNewline: - enabled: true - present: true - - HtmlAttributes: - enabled: true - - ImplicitDiv: - enabled: true - - LeadingCommentSpace: - enabled: true - - LineLength: - enabled: false - max: 80 - - MultilinePipe: - enabled: true - - MultilineScript: - enabled: true - - ObjectReferenceAttributes: - enabled: true - - RuboCop: - enabled: true - # These cops are incredibly noisy when it comes to HAML templates, so we - # ignore them. - ignored_cops: - - Lint/BlockAlignment - - Lint/EndAlignment - - Lint/Void - - Metrics/BlockLength - - Metrics/LineLength - - Style/AlignParameters - - Style/BlockNesting - - Style/ElseAlignment - - Style/EndOfLine - - Style/FileName - - Style/FinalNewline - - Style/FrozenStringLiteralComment - - Style/IfUnlessModifier - - Style/IndentationWidth - - Style/Next - - Style/TrailingBlankLines - - Style/TrailingWhitespace - - Style/WhileUntilModifier - - RubyComments: - enabled: true - - SpaceBeforeScript: - enabled: true - - SpaceInsideHashAttributes: - enabled: true - style: space - - Indentation: - enabled: true - character: space # or tab - - TagName: - enabled: true - - TrailingWhitespace: - enabled: true - - UnnecessaryInterpolation: - enabled: true - - UnnecessaryStringOutput: + MiddleDot: enabled: true diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml new file mode 100644 index 000000000..6d2aa0641 --- /dev/null +++ b/.haml-lint_todo.yml @@ -0,0 +1,47 @@ +# This configuration was generated by +# `haml-lint --auto-gen-config` +# on 2023-07-20 09:47:50 -0400 using Haml-Lint version 0.48.0. +# The point is for the user to remove these configuration records +# one by one as the lints are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of Haml-Lint, may require this file to be generated again. + +linters: + # Offense count: 951 + LineLength: + enabled: false + + # Offense count: 22 + UnnecessaryStringOutput: + enabled: false + + # Offense count: 57 + RuboCop: + enabled: false + + # Offense count: 3 + ViewLength: + exclude: + - 'app/views/admin/accounts/show.html.haml' + - 'app/views/admin/reports/show.html.haml' + - 'app/views/disputes/strikes/show.html.haml' + + # Offense count: 32 + InstanceVariables: + exclude: + - 'app/views/admin/reports/_actions.html.haml' + - 'app/views/admin/roles/_form.html.haml' + - 'app/views/admin/webhooks/_form.html.haml' + - 'app/views/auth/registrations/_status.html.haml' + - 'app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml' + - 'app/views/authorize_interactions/_post_follow_actions.html.haml' + - 'app/views/invites/_form.html.haml' + - 'app/views/relationships/_account.html.haml' + - 'app/views/shared/_og.html.haml' + + # Offense count: 3 + IdNames: + exclude: + - 'app/views/authorize_interactions/error.html.haml' + - 'app/views/oauth/authorizations/error.html.haml' + - 'app/views/shared/_error_messages.html.haml' diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..d2ae35e84 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn lint-staged diff --git a/.nvmrc b/.nvmrc index 8351c1939..b1b396bcf 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14 +20.7 diff --git a/.prettierignore b/.prettierignore index de7673eb6..91029f665 100644 --- a/.prettierignore +++ b/.prettierignore @@ -44,9 +44,6 @@ /redis /elasticsearch -# ignore Helm dependency charts -/chart/charts/*.tgz - # Ignore Apple files .DS_Store @@ -54,25 +51,28 @@ *~ *.swp -# Ignore npm debug log -npm-debug.log - -# Ignore yarn log files -yarn-error.log -yarn-debug.log - -# Ignore vagrant log files -*-cloudimg-console.log +# Ignore log files +*.log # Ignore Docker option files docker-compose.override.yml -# Ignore Helm files -/chart - # Ignore emoji map file /app/javascript/mastodon/features/emoji/emoji_map.json # Ignore locale files -/app/javascript/mastodon/locales +/app/javascript/mastodon/locales/*.json /config/locales + +# Ignore vendored CSS reset +app/javascript/styles/mastodon/reset.scss + +# Ignore Javascript pending https://github.com/mastodon/mastodon/pull/23631 +*.js +*.jsx + +# Ignore HTML till cleaned and included in CI +*.html + +# Ignore the generated AUTHORS.md +AUTHORS.md diff --git a/.prettierrc.js b/.prettierrc.js index 1d70813d5..af39b253f 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,3 +1,4 @@ module.exports = { - singleQuote: true + singleQuote: true, + jsxSingleQuote: true } diff --git a/.profile b/.profile index c6d57b609..f4826ea30 100644 --- a/.profile +++ b/.profile @@ -1 +1 @@ -LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/.apt/lib/x86_64-linux-gnu:/app/.apt/usr/lib/x86_64-linux-gnu/mesa:/app/.apt/usr/lib/x86_64-linux-gnu/pulseaudio +LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/.apt/lib/x86_64-linux-gnu:/app/.apt/usr/lib/x86_64-linux-gnu/mesa:/app/.apt/usr/lib/x86_64-linux-gnu/pulseaudio:/app/.apt/usr/lib/x86_64-linux-gnu/openblas-pthread diff --git a/.rubocop.yml b/.rubocop.yml index 92abf40e7..c8a433c72 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,315 +1,202 @@ +# Can be removed once all rules are addressed or moved to this file as documented overrides +inherit_from: .rubocop_todo.yml + +# Used for merging with exclude lists with .rubocop_todo.yml +inherit_mode: + merge: + - Exclude + require: - rubocop-rails + - rubocop-rspec + - rubocop-performance + - rubocop-capybara + - ./lib/linter/rubocop_middle_dot AllCops: - TargetRubyVersion: 2.7 - NewCops: disable + TargetRubyVersion: 3.0 # Set to minimum supported version of CI + DisplayCopNames: true + DisplayStyleGuide: true + ExtraDetails: true + UseCache: true + CacheRootDirectory: tmp + NewCops: enable # Opt-in to newly added rules Exclude: - - 'spec/**/*' - - 'db/**/*' - - 'app/views/**/*' - - 'config/**/*' + - db/schema.rb - 'bin/*' - - 'Rakefile' - 'node_modules/**/*' - 'Vagrantfile' - 'vendor/**/*' - - 'lib/json_ld/*' + - 'lib/json_ld/*' # Generated files - 'lib/templates/**/*' -Bundler/OrderedGems: - Enabled: false - -Layout/AccessModifierIndentation: - EnforcedStyle: indent - -Layout/EmptyLineAfterMagicComment: - Enabled: false - -Layout/EmptyLineAfterGuardClause: - Enabled: false - -Layout/EmptyLineBetweenDefs: - AllowAdjacentOneLineDefs: true - -Layout/EmptyLinesAroundAttributeAccessor: - Enabled: true - +# Reason: Prefer Hashes without extreme indentation +# https://docs.rubocop.org/rubocop/cops_layout.html#layoutfirsthashelementindentation Layout/FirstHashElementIndentation: EnforcedStyle: consistent -Layout/HashAlignment: - Enabled: false - -Layout/SpaceAroundMethodCallOperator: - Enabled: true - -Layout/SpaceInsideHashLiteralBraces: - EnforcedStyle: space - -Lint/DeprecatedOpenSSLConstant: - Enabled: true - -Lint/DuplicateElsifCondition: - Enabled: true - -Lint/MixedRegexpCaptureTypes: - Enabled: true - -Lint/RaiseException: - Enabled: true - -Lint/StructNewOverride: - Enabled: true +# Reason: Currently disabled in .rubocop_todo.yml +# https://docs.rubocop.org/rubocop/cops_layout.html#layoutlinelength +Layout/LineLength: + Max: 320 # Default of 120 causes a duplicate entry in generated todo file +# Reason: +# https://docs.rubocop.org/rubocop/cops_lint.html#lintuselessaccessmodifier Lint/UselessAccessModifier: ContextCreatingMethods: - class_methods -Metrics/AbcSize: - Max: 115 - Exclude: - - 'lib/mastodon/*_cli.rb' - AllowedMethods: - - process_update +## Disable most Metrics/*Length cops +# Reason: those are often triggered and force significant refactors when this happend +# but the team feel they are not really improving the code quality. +# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocklength Metrics/BlockLength: - Max: 55 - Exclude: - - 'lib/tasks/**/*' - - 'lib/mastodon/*_cli.rb' - -Metrics/BlockNesting: - Max: 3 - Exclude: - - 'lib/mastodon/*_cli.rb' + Enabled: false +# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsclasslength Metrics/ClassLength: - CountComments: false - Max: 500 - Exclude: - - 'lib/mastodon/*_cli.rb' - -Metrics/CyclomaticComplexity: - Max: 25 - Exclude: - - 'lib/mastodon/*_cli.rb' - AllowedMethods: - - process_update - -Layout/LineLength: - AllowURI: true Enabled: false +# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmethodlength Metrics/MethodLength: - CountComments: false - Max: 65 - Exclude: - - 'lib/mastodon/*_cli.rb' + Enabled: false +# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmodulelength Metrics/ModuleLength: - CountComments: false - Max: 200 - -Metrics/ParameterLists: - Max: 5 - CountKeywordArgs: true - -Metrics/PerceivedComplexity: - Max: 25 - AllowedMethods: - - process_update - -Naming/MemoizedInstanceVariableName: Enabled: false -Naming/MethodParameterName: - Enabled: true +## End Disable Metrics/*Length cops -Rails: - Enabled: true - -Rails/ApplicationController: - Enabled: false +# Reason: Currently disabled in .rubocop_todo.yml +# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsabcsize +Metrics/AbcSize: Exclude: - - 'app/controllers/well_known/**/*.rb' + - 'lib/mastodon/cli/*.rb' + - db/*migrate/**/* -Rails/BelongsTo: - Enabled: false +# Reason: +# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocknesting +Metrics/BlockNesting: + Exclude: + - 'lib/mastodon/cli/*.rb' -Rails/ContentTag: - Enabled: false +# Reason: Currently disabled in .rubocop_todo.yml +# https://docs.rubocop.org/rubocop/cops_metrics.html#metricscyclomaticcomplexity +Metrics/CyclomaticComplexity: + Exclude: + - lib/mastodon/cli/*.rb + - db/*migrate/**/* -Rails/EnumHash: - Enabled: false +# Reason: +# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsparameterlists +Metrics/ParameterLists: + CountKeywordArgs: false +# Reason: Prevailing style is argument file paths +# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsfilepath +Rails/FilePath: + EnforcedStyle: arguments + +# Reason: Prevailing style uses numeric status codes, matches RSpec/Rails/HttpStatus +# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railshttpstatus +Rails/HttpStatus: + EnforcedStyle: numeric + +# Reason: Allowed in `tootctl` CLI code and in boot ENV checker +# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsexit Rails/Exit: Exclude: - - 'lib/mastodon/*' - - 'lib/cli.rb' + - 'config/boot.rb' + - 'lib/mastodon/cli/*.rb' -Rails/FilePath: - Enabled: false +# Reason: Some single letter camel case files shouldn't be split +# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecfilepath +RSpec/FilePath: + CustomTransform: + ActivityPub: activitypub # Ignore the snake_case due to the amount of files to rename + DeepL: deepl + FetchOEmbedService: fetch_oembed_service + JsonLdHelper: jsonld_helper + OEmbedController: oembed_controller + OStatus: ostatus + NodeInfoController: nodeinfo_controller # NodeInfo isn't snake_cased for any of the instances + Exclude: + - 'spec/config/initializers/rack_attack_spec.rb' # namespaces usually have separate folder + - 'spec/lib/sanitize_config_spec.rb' # namespaces usually have separate folder -Rails/HasAndBelongsToMany: - Enabled: false +# Reason: +# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnamedsubject +RSpec/NamedSubject: + EnforcedStyle: named_only -Rails/HasManyOrHasOneDependent: - Enabled: false +# Reason: Prevailing style choice +# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnottonot +RSpec/NotToNot: + EnforcedStyle: to_not -Rails/HelperInstanceVariable: - Enabled: false - -Rails/HttpStatus: - Enabled: false - -Rails/IndexBy: - Enabled: false - -Rails/InverseOf: - Enabled: false - -Rails/LexicallyScopedActionFilter: - Enabled: false - -Rails/OutputSafety: - Enabled: true - -Rails/RakeEnvironment: - Enabled: false - -Rails/RedundantForeignKey: - Enabled: false - -Rails/SkipsModelValidations: - Enabled: false - -Rails/UniqueValidationWithoutIndex: - Enabled: false - -Style/AccessorGrouping: - Enabled: true - -Style/AccessModifierDeclarations: - Enabled: false - -Style/ArrayCoercion: - Enabled: true - -Style/BisectedAttrAccessor: - Enabled: true - -Style/CaseLikeIf: - Enabled: false +# Reason: Prevailing style uses numeric status codes, matches Rails/HttpStatus +# https://docs.rubocop.org/rubocop-rspec/cops_rspec_rails.html#rspecrailshttpstatus +RSpec/Rails/HttpStatus: + EnforcedStyle: numeric +# Reason: +# https://docs.rubocop.org/rubocop/cops_style.html#styleclassandmodulechildren Style/ClassAndModuleChildren: Enabled: false -Style/CollectionMethods: - Enabled: true - PreferredMethods: - find_all: 'select' - +# Reason: Classes mostly self-document with their names +# https://docs.rubocop.org/rubocop/cops_style.html#styledocumentation Style/Documentation: Enabled: false -Style/DoubleNegation: - Enabled: true +# Reason: Enforce modern Ruby style +# https://docs.rubocop.org/rubocop/cops_style.html#stylehashsyntax +Style/HashSyntax: + EnforcedStyle: ruby19_no_mixed_keys -Style/ExpandPathArguments: - Enabled: false - -Style/ExponentialNotation: - Enabled: true - -Style/FormatString: - Enabled: false - -Style/FormatStringToken: - Enabled: false - -Style/FrozenStringLiteralComment: - Enabled: true - -Style/GuardClause: - Enabled: false - -Style/HashAsLastArrayItem: - Enabled: false - -Style/HashEachMethods: - Enabled: true - -Style/HashLikeCase: - Enabled: true - -Style/HashTransformKeys: - Enabled: true - -Style/HashTransformValues: - Enabled: false - -Style/IfUnlessModifier: - Enabled: false - -Style/InverseMethods: - Enabled: false - -Style/Lambda: - Enabled: false - -Style/MutableConstant: - Enabled: false +# Reason: +# https://docs.rubocop.org/rubocop/cops_style.html#stylenumericliterals +Style/NumericLiterals: + AllowedPatterns: + - \d{4}_\d{2}_\d{2}_\d{6} # For DB migration date version number readability +# Reason: +# https://docs.rubocop.org/rubocop/cops_style.html#stylepercentliteraldelimiters Style/PercentLiteralDelimiters: PreferredDelimiters: '%i': '()' '%w': '()' -Style/PerlBackrefs: - AutoCorrect: false - -Style/RedundantAssignment: - Enabled: false - -Style/RedundantFetchBlock: - Enabled: true - -Style/RedundantFileExtensionInRequire: - Enabled: true - -Style/RedundantRegexpCharacterClass: - Enabled: false - -Style/RedundantRegexpEscape: - Enabled: false - -Style/RedundantReturn: - Enabled: true - +# Reason: Prefer less indentation in conditional assignments +# https://docs.rubocop.org/rubocop/cops_style.html#styleredundantbegin Style/RedundantBegin: Enabled: false -Style/RegexpLiteral: - Enabled: false - +# Reason: Overridden to reduce implicit StandardError rescues +# https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror Style/RescueStandardError: - Enabled: false + EnforcedStyle: implicit -Style/SignalException: - Enabled: false - -Style/SlicingWithRange: - Enabled: true +# Reason: Simplify some spec layouts +# https://docs.rubocop.org/rubocop/cops_style.html#stylesemicolon +Style/Semicolon: + AllowAsExpressionSeparator: true +# Reason: Originally disabled for CodeClimate, and no config consensus has been found +# https://docs.rubocop.org/rubocop/cops_style.html#stylesymbolarray Style/SymbolArray: Enabled: false +# Reason: +# https://docs.rubocop.org/rubocop/cops_style.html#styletrailingcommainarrayliteral Style/TrailingCommaInArrayLiteral: EnforcedStyleForMultiline: 'comma' +# Reason: +# https://docs.rubocop.org/rubocop/cops_style.html#styletrailingcommainhashliteral Style/TrailingCommaInHashLiteral: EnforcedStyleForMultiline: 'comma' -Style/UnpackFirst: - Enabled: false +Style/MiddleDot: + Enabled: true diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 000000000..de4782b17 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,864 @@ +# This configuration was generated by +# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` +# using RuboCop version 1.56.3. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include. +# Include: **/*.gemfile, **/Gemfile, **/gems.rb +Bundler/OrderedGems: + Exclude: + - 'Gemfile' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: with_first_argument, with_fixed_indentation +Layout/ArgumentAlignment: + Exclude: + - 'config/initializers/cors.rb' + - 'config/initializers/session_store.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. +# SupportedHashRocketStyles: key, separator, table +# SupportedColonStyles: key, separator, table +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Layout/HashAlignment: + Exclude: + - 'config/environments/production.rb' + - 'config/initializers/rack_attack.rb' + - 'config/routes.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowDoxygenCommentStyle, AllowGemfileRubyComment. +Layout/LeadingCommentSpace: + Exclude: + - 'config/application.rb' + - 'config/initializers/3_omniauth.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. +# URISchemes: http, https +Layout/LineLength: + Exclude: + - 'app/models/account.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: require_no_space, require_space +Layout/SpaceInLambdaLiteral: + Exclude: + - 'config/environments/production.rb' + - 'config/initializers/content_security_policy.rb' + +# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches. +Lint/DuplicateBranch: + Exclude: + - 'app/lib/account_statuses_filter.rb' + +# Configuration parameters: AllowComments, AllowEmptyLambdas. +Lint/EmptyBlock: + Exclude: + - 'spec/controllers/api/v2/search_controller_spec.rb' + - 'spec/fabricators/access_token_fabricator.rb' + - 'spec/fabricators/conversation_fabricator.rb' + - 'spec/fabricators/system_key_fabricator.rb' + - 'spec/lib/activitypub/adapter_spec.rb' + - 'spec/models/user_role_spec.rb' + +Lint/NonLocalExitFromIterator: + Exclude: + - 'app/helpers/jsonld_helper.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +Lint/OrAssignmentToConstant: + Exclude: + - 'lib/sanitize_ext/sanitize_config.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +Lint/UnusedBlockArgument: + Exclude: + - 'config/initializers/content_security_policy.rb' + - 'config/initializers/doorkeeper.rb' + - 'config/initializers/paperclip.rb' + - 'config/initializers/simple_form.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +Lint/UselessAssignment: + Exclude: + - 'app/services/activitypub/process_status_update_service.rb' + - 'config/initializers/3_omniauth.rb' + - 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb' + - 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb' + - 'spec/controllers/api/v1/favourites_controller_spec.rb' + - 'spec/controllers/concerns/account_controller_concern_spec.rb' + - 'spec/helpers/jsonld_helper_spec.rb' + - 'spec/models/account_spec.rb' + - 'spec/models/domain_block_spec.rb' + - 'spec/models/status_spec.rb' + - 'spec/models/user_spec.rb' + - 'spec/models/webauthn_credentials_spec.rb' + - 'spec/services/account_search_service_spec.rb' + - 'spec/services/post_status_service_spec.rb' + - 'spec/services/precompute_feed_service_spec.rb' + - 'spec/services/resolve_url_service_spec.rb' + - 'spec/views/statuses/show.html.haml_spec.rb' + +# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. +Metrics/AbcSize: + Max: 149 + +# Configuration parameters: CountBlocks, Max. +Metrics/BlockNesting: + Exclude: + - 'lib/tasks/mastodon.rake' + +# Configuration parameters: AllowedMethods, AllowedPatterns. +Metrics/CyclomaticComplexity: + Max: 25 + +# Configuration parameters: AllowedMethods, AllowedPatterns. +Metrics/PerceivedComplexity: + Max: 27 + +# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. +# SupportedStyles: snake_case, normalcase, non_integer +# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 +Naming/VariableNumber: + Exclude: + - 'db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb' + - 'db/migrate/20180514140000_revert_index_change_on_statuses_for_api_v1_accounts_account_id_statuses.rb' + - 'db/migrate/20190820003045_update_statuses_index.rb' + - 'db/migrate/20190823221802_add_local_index_to_statuses.rb' + - 'db/migrate/20200119112504_add_public_index_to_statuses.rb' + - 'spec/models/account_spec.rb' + - 'spec/models/domain_block_spec.rb' + - 'spec/models/user_spec.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: SafeMultiline. +Performance/DeletePrefix: + Exclude: + - 'app/models/featured_tag.rb' + +Performance/MapMethodChain: + Exclude: + - 'app/models/feed.rb' + - 'lib/mastodon/cli/maintenance.rb' + - 'spec/services/bulk_import_service_spec.rb' + - 'spec/services/import_service_spec.rb' + +RSpec/AnyInstance: + Exclude: + - 'spec/controllers/activitypub/inboxes_controller_spec.rb' + - 'spec/controllers/admin/accounts_controller_spec.rb' + - 'spec/controllers/admin/resets_controller_spec.rb' + - 'spec/controllers/admin/settings/branding_controller_spec.rb' + - 'spec/controllers/api/v1/media_controller_spec.rb' + - 'spec/controllers/auth/sessions_controller_spec.rb' + - 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb' + - 'spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb' + - 'spec/lib/request_spec.rb' + - 'spec/lib/status_filter_spec.rb' + - 'spec/models/account_spec.rb' + - 'spec/models/setting_spec.rb' + - 'spec/services/activitypub/process_collection_service_spec.rb' + - 'spec/validators/follow_limit_validator_spec.rb' + - 'spec/workers/activitypub/delivery_worker_spec.rb' + - 'spec/workers/web/push_notification_worker_spec.rb' + +# Configuration parameters: CountAsOne. +RSpec/ExampleLength: + Max: 22 + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: implicit, each, example +RSpec/HookArgument: + Exclude: + - 'spec/controllers/api/v1/streaming_controller_spec.rb' + - 'spec/controllers/well_known/webfinger_controller_spec.rb' + - 'spec/helpers/instance_helper_spec.rb' + - 'spec/models/user_spec.rb' + - 'spec/rails_helper.rb' + - 'spec/serializers/activitypub/note_serializer_spec.rb' + - 'spec/serializers/activitypub/update_poll_serializer_spec.rb' + - 'spec/services/import_service_spec.rb' + +# Configuration parameters: AssignmentOnly. +RSpec/InstanceVariable: + Exclude: + - 'spec/controllers/api/v1/streaming_controller_spec.rb' + - 'spec/controllers/auth/confirmations_controller_spec.rb' + - 'spec/controllers/auth/passwords_controller_spec.rb' + - 'spec/controllers/auth/sessions_controller_spec.rb' + - 'spec/controllers/concerns/export_controller_concern_spec.rb' + - 'spec/controllers/home_controller_spec.rb' + - 'spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb' + - 'spec/controllers/statuses_cleanup_controller_spec.rb' + - 'spec/models/concerns/account_finder_concern_spec.rb' + - 'spec/models/concerns/account_interactions_spec.rb' + - 'spec/models/public_feed_spec.rb' + - 'spec/serializers/activitypub/note_serializer_spec.rb' + - 'spec/serializers/activitypub/update_poll_serializer_spec.rb' + - 'spec/services/remove_status_service_spec.rb' + - 'spec/services/search_service_spec.rb' + - 'spec/services/unblock_domain_service_spec.rb' + +RSpec/LetSetup: + Exclude: + - 'spec/controllers/admin/accounts_controller_spec.rb' + - 'spec/controllers/admin/action_logs_controller_spec.rb' + - 'spec/controllers/admin/instances_controller_spec.rb' + - 'spec/controllers/admin/reports/actions_controller_spec.rb' + - 'spec/controllers/admin/statuses_controller_spec.rb' + - 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb' + - 'spec/controllers/api/v1/admin/accounts_controller_spec.rb' + - 'spec/controllers/api/v1/filters_controller_spec.rb' + - 'spec/controllers/api/v1/followed_tags_controller_spec.rb' + - 'spec/controllers/api/v2/admin/accounts_controller_spec.rb' + - 'spec/controllers/api/v2/filters/keywords_controller_spec.rb' + - 'spec/controllers/api/v2/filters/statuses_controller_spec.rb' + - 'spec/controllers/auth/confirmations_controller_spec.rb' + - 'spec/controllers/auth/passwords_controller_spec.rb' + - 'spec/controllers/auth/sessions_controller_spec.rb' + - 'spec/controllers/follower_accounts_controller_spec.rb' + - 'spec/controllers/following_accounts_controller_spec.rb' + - 'spec/controllers/oauth/authorized_applications_controller_spec.rb' + - 'spec/controllers/oauth/tokens_controller_spec.rb' + - 'spec/controllers/settings/imports_controller_spec.rb' + - 'spec/lib/activitypub/activity/delete_spec.rb' + - 'spec/lib/vacuum/applications_vacuum_spec.rb' + - 'spec/lib/vacuum/preview_cards_vacuum_spec.rb' + - 'spec/models/account_spec.rb' + - 'spec/models/account_statuses_cleanup_policy_spec.rb' + - 'spec/models/canonical_email_block_spec.rb' + - 'spec/models/status_spec.rb' + - 'spec/models/user_spec.rb' + - 'spec/services/account_statuses_cleanup_service_spec.rb' + - 'spec/services/activitypub/fetch_featured_collection_service_spec.rb' + - 'spec/services/activitypub/fetch_remote_status_service_spec.rb' + - 'spec/services/activitypub/process_account_service_spec.rb' + - 'spec/services/activitypub/process_collection_service_spec.rb' + - 'spec/services/batched_remove_status_service_spec.rb' + - 'spec/services/block_domain_service_spec.rb' + - 'spec/services/bulk_import_service_spec.rb' + - 'spec/services/delete_account_service_spec.rb' + - 'spec/services/import_service_spec.rb' + - 'spec/services/notify_service_spec.rb' + - 'spec/services/remove_status_service_spec.rb' + - 'spec/services/report_service_spec.rb' + - 'spec/services/resolve_account_service_spec.rb' + - 'spec/services/suspend_account_service_spec.rb' + - 'spec/services/unallow_domain_service_spec.rb' + - 'spec/services/unsuspend_account_service_spec.rb' + - 'spec/workers/scheduler/user_cleanup_scheduler_spec.rb' + +RSpec/MessageChain: + Exclude: + - 'spec/controllers/api/v1/media_controller_spec.rb' + - 'spec/models/concerns/remotable_spec.rb' + - 'spec/models/session_activation_spec.rb' + - 'spec/models/setting_spec.rb' + +# Configuration parameters: EnforcedStyle. +# SupportedStyles: have_received, receive +RSpec/MessageSpies: + Exclude: + - 'spec/controllers/admin/accounts_controller_spec.rb' + - 'spec/helpers/admin/account_moderation_notes_helper_spec.rb' + - 'spec/lib/webfinger_resource_spec.rb' + - 'spec/models/admin/account_action_spec.rb' + - 'spec/models/concerns/remotable_spec.rb' + - 'spec/models/follow_request_spec.rb' + - 'spec/models/identity_spec.rb' + - 'spec/models/session_activation_spec.rb' + - 'spec/models/setting_spec.rb' + - 'spec/services/activitypub/fetch_replies_service_spec.rb' + - 'spec/services/activitypub/process_collection_service_spec.rb' + - 'spec/spec_helper.rb' + - 'spec/validators/status_length_validator_spec.rb' + +RSpec/MultipleExpectations: + Max: 8 + +# Configuration parameters: AllowSubject. +RSpec/MultipleMemoizedHelpers: + Max: 21 + +# Configuration parameters: AllowedGroups. +RSpec/NestedGroups: + Max: 6 + +RSpec/RepeatedDescription: + Exclude: + - 'spec/controllers/activitypub/outboxes_controller_spec.rb' + +RSpec/RepeatedExample: + Exclude: + - 'spec/controllers/activitypub/outboxes_controller_spec.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +Rails/ApplicationController: + Exclude: + - 'app/controllers/health_controller.rb' + +# Configuration parameters: Include. +# Include: db/**/*.rb +Rails/CreateTableWithTimestamps: + Exclude: + - 'db/migrate/20170508230434_create_conversation_mutes.rb' + - 'db/migrate/20170823162448_create_status_pins.rb' + - 'db/migrate/20171116161857_create_list_accounts.rb' + - 'db/migrate/20180929222014_create_account_conversations.rb' + - 'db/migrate/20181007025445_create_pghero_space_stats.rb' + - 'db/migrate/20190103124649_create_scheduled_statuses.rb' + - 'db/migrate/20220824233535_create_status_trends.rb' + - 'db/migrate/20221006061337_create_preview_card_trends.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Severity. +Rails/DuplicateAssociation: + Exclude: + - 'app/serializers/activitypub/collection_serializer.rb' + - 'app/serializers/activitypub/note_serializer.rb' + +# Configuration parameters: Include. +# Include: app/**/*.rb, config/**/*.rb, lib/**/*.rb +Rails/Exit: + Exclude: + - 'config/initializers/sidekiq.rb' + +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/HasAndBelongsToMany: + Exclude: + - 'app/models/concerns/account_associations.rb' + - 'app/models/preview_card.rb' + - 'app/models/status.rb' + - 'app/models/tag.rb' + +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/HasManyOrHasOneDependent: + Exclude: + - 'app/models/concerns/account_counters.rb' + - 'app/models/conversation.rb' + - 'app/models/custom_emoji.rb' + - 'app/models/custom_emoji_category.rb' + - 'app/models/domain_block.rb' + - 'app/models/invite.rb' + - 'app/models/status.rb' + - 'app/models/user.rb' + - 'app/models/web/push_subscription.rb' + +Rails/I18nLocaleTexts: + Exclude: + - 'lib/tasks/mastodon.rake' + - 'spec/helpers/flashes_helper_spec.rb' + +# Configuration parameters: Include. +# Include: app/controllers/**/*.rb, app/mailers/**/*.rb +Rails/LexicallyScopedActionFilter: + Exclude: + - 'app/controllers/auth/passwords_controller.rb' + - 'app/controllers/auth/registrations_controller.rb' + - 'app/controllers/auth/sessions_controller.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +Rails/NegateInclude: + Exclude: + - 'app/controllers/concerns/signature_verification.rb' + - 'app/helpers/jsonld_helper.rb' + - 'app/lib/activitypub/activity/create.rb' + - 'app/lib/activitypub/activity/move.rb' + - 'app/lib/feed_manager.rb' + - 'app/lib/link_details_extractor.rb' + - 'app/models/concerns/attachmentable.rb' + - 'app/models/concerns/remotable.rb' + - 'app/models/custom_filter.rb' + - 'app/services/activitypub/process_status_update_service.rb' + - 'app/services/fetch_link_card_service.rb' + - 'app/workers/web/push_notification_worker.rb' + - 'lib/paperclip/color_extractor.rb' + +Rails/OutputSafety: + Exclude: + - 'config/initializers/simple_form.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: Include. +# Include: **/Rakefile, **/*.rake +Rails/RakeEnvironment: + Exclude: + - 'lib/tasks/auto_annotate_models.rake' + - 'lib/tasks/db.rake' + - 'lib/tasks/emojis.rake' + - 'lib/tasks/mastodon.rake' + - 'lib/tasks/repo.rake' + - 'lib/tasks/statistics.rake' + +# Configuration parameters: Include. +# Include: db/**/*.rb +Rails/ReversibleMigration: + Exclude: + - 'db/migrate/20160223164502_make_uris_nullable_in_statuses.rb' + - 'db/migrate/20161122163057_remove_unneeded_indexes.rb' + - 'db/migrate/20170205175257_remove_devices.rb' + - 'db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb' + - 'db/migrate/20170520145338_change_language_filter_to_opt_out.rb' + - 'db/migrate/20170609145826_remove_default_language_from_statuses.rb' + - 'db/migrate/20170711225116_fix_null_booleans.rb' + - 'db/migrate/20171129172043_add_index_on_stream_entries.rb' + - 'db/migrate/20171212195226_remove_duplicate_indexes_in_lists.rb' + - 'db/migrate/20171226094803_more_faster_index_on_notifications.rb' + - 'db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb' + - 'db/migrate/20180617162849_remove_unused_indexes.rb' + - 'db/migrate/20190726034905_add_is_exclusive_to_lists.rb' + - 'db/migrate/20220827195229_change_canonical_email_blocks_nullable.rb' + - 'db/migrate/20221202035831_add_keep_local_to_account_statuses_cleanup_policies.rb' + +# Configuration parameters: ForbiddenMethods, AllowedMethods. +# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all +Rails/SkipsModelValidations: + Exclude: + - 'app/controllers/admin/invites_controller.rb' + - 'app/controllers/concerns/session_tracking_concern.rb' + - 'app/models/concerns/account_merging.rb' + - 'app/models/concerns/expireable.rb' + - 'app/models/status.rb' + - 'app/models/trends/links.rb' + - 'app/models/trends/preview_card_batch.rb' + - 'app/models/trends/preview_card_provider_batch.rb' + - 'app/models/trends/status_batch.rb' + - 'app/models/trends/statuses.rb' + - 'app/models/trends/tag_batch.rb' + - 'app/models/trends/tags.rb' + - 'app/models/user.rb' + - 'app/services/activitypub/process_status_update_service.rb' + - 'app/services/approve_appeal_service.rb' + - 'app/services/block_domain_service.rb' + - 'app/services/delete_account_service.rb' + - 'app/services/process_mentions_service.rb' + - 'app/services/unallow_domain_service.rb' + - 'app/services/unblock_domain_service.rb' + - 'app/services/update_status_service.rb' + - 'app/workers/activitypub/post_upgrade_worker.rb' + - 'app/workers/move_worker.rb' + - 'app/workers/scheduler/ip_cleanup_scheduler.rb' + - 'app/workers/scheduler/scheduled_statuses_scheduler.rb' + - 'db/migrate/20161203164520_add_from_account_id_to_notifications.rb' + - 'db/migrate/20170105224407_add_shortcode_to_media_attachments.rb' + - 'db/migrate/20170209184350_add_reply_to_statuses.rb' + - 'db/migrate/20170304202101_add_type_to_media_attachments.rb' + - 'db/migrate/20180528141303_fix_accounts_unique_index.rb' + - 'db/migrate/20180609104432_migrate_web_push_subscriptions2.rb' + - 'db/migrate/20181207011115_downcase_custom_emoji_domains.rb' + - 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb' + - 'db/migrate/20191007013357_update_pt_locales.rb' + - 'db/migrate/20220316233212_update_kurdish_locales.rb' + - 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb' + - 'db/post_migrate/20200917193528_migrate_notifications_type.rb' + - 'db/post_migrate/20201017234926_fill_account_suspension_origin.rb' + - 'db/post_migrate/20220617202502_migrate_roles.rb' + - 'db/post_migrate/20221101190723_backfill_admin_action_logs.rb' + - 'db/post_migrate/20221206114142_backfill_admin_action_logs_again.rb' + - 'lib/mastodon/cli/accounts.rb' + - 'lib/mastodon/cli/main.rb' + - 'lib/mastodon/cli/maintenance.rb' + - 'spec/controllers/api/v1/admin/accounts_controller_spec.rb' + - 'spec/lib/activitypub/activity/follow_spec.rb' + - 'spec/services/follow_service_spec.rb' + - 'spec/services/update_account_service_spec.rb' + +# Configuration parameters: Include. +# Include: db/**/*.rb +Rails/ThreeStateBooleanColumn: + Exclude: + - 'db/migrate/20160325130944_add_admin_to_users.rb' + - 'db/migrate/20161123093447_add_sensitive_to_statuses.rb' + - 'db/migrate/20170123203248_add_reject_media_to_domain_blocks.rb' + - 'db/migrate/20170127165745_add_devise_two_factor_to_users.rb' + - 'db/migrate/20170209184350_add_reply_to_statuses.rb' + - 'db/migrate/20170330163835_create_imports.rb' + - 'db/migrate/20170905165803_add_local_to_statuses.rb' + - 'db/migrate/20171210213213_add_local_only_flag_to_statuses.rb' + - 'db/migrate/20181203021853_add_discoverable_to_accounts.rb' + - 'db/migrate/20190509164208_add_by_moderator_to_tombstone.rb' + - 'db/migrate/20190726034905_add_is_exclusive_to_lists.rb' + - 'db/migrate/20190805123746_add_capabilities_to_tags.rb' + - 'db/migrate/20191212163405_add_hide_collections_to_accounts.rb' + - 'db/migrate/20200309150742_add_forwarded_to_reports.rb' + - 'db/migrate/20210609202149_create_login_activities.rb' + - 'db/migrate/20210621221010_add_skip_sign_in_token_to_users.rb' + - 'db/migrate/20211031031021_create_preview_card_providers.rb' + - 'db/migrate/20211115032527_add_trendable_to_preview_cards.rb' + - 'db/migrate/20220202200743_add_trendable_to_accounts.rb' + - 'db/migrate/20220202200926_add_trendable_to_statuses.rb' + - 'db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb' + - 'db/migrate/20221202035831_add_keep_local_to_account_statuses_cleanup_policies.rb' + +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/UniqueValidationWithoutIndex: + Exclude: + - 'app/models/account_alias.rb' + - 'app/models/custom_filter_status.rb' + - 'app/models/identity.rb' + - 'app/models/webauthn_credential.rb' + +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/UnusedIgnoredColumns: + Exclude: + - 'app/models/account.rb' + - 'app/models/account_stat.rb' + - 'app/models/admin/action_log.rb' + - 'app/models/custom_filter.rb' + - 'app/models/email_domain_block.rb' + - 'app/models/report.rb' + - 'app/models/status_edit.rb' + - 'app/models/user.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: exists, where +Rails/WhereExists: + Exclude: + - 'app/controllers/activitypub/inboxes_controller.rb' + - 'app/controllers/admin/email_domain_blocks_controller.rb' + - 'app/controllers/auth/registrations_controller.rb' + - 'app/lib/activitypub/activity/create.rb' + - 'app/lib/delivery_failure_tracker.rb' + - 'app/lib/feed_manager.rb' + - 'app/lib/status_cache_hydrator.rb' + - 'app/lib/suspicious_sign_in_detector.rb' + - 'app/models/concerns/account_interactions.rb' + - 'app/models/featured_tag.rb' + - 'app/models/poll.rb' + - 'app/models/session_activation.rb' + - 'app/models/status.rb' + - 'app/models/user.rb' + - 'app/policies/status_policy.rb' + - 'app/serializers/rest/announcement_serializer.rb' + - 'app/serializers/rest/tag_serializer.rb' + - 'app/services/activitypub/fetch_remote_status_service.rb' + - 'app/services/app_sign_up_service.rb' + - 'app/services/vote_service.rb' + - 'app/validators/reaction_validator.rb' + - 'app/validators/vote_validator.rb' + - 'app/workers/move_worker.rb' + - 'db/migrate/20190529143559_preserve_old_layout_for_existing_users.rb' + - 'lib/tasks/tests.rake' + - 'spec/models/account_spec.rb' + - 'spec/services/activitypub/process_collection_service_spec.rb' + - 'spec/services/purge_domain_service_spec.rb' + - 'spec/services/unallow_domain_service_spec.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowOnConstant, AllowOnSelfClass. +Style/CaseEquality: + Exclude: + - 'config/initializers/trusted_proxies.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowedMethods, AllowedPatterns. +# AllowedMethods: ==, equal?, eql? +Style/ClassEqualityComparison: + Exclude: + - 'app/helpers/jsonld_helper.rb' + - 'app/serializers/activitypub/outbox_serializer.rb' + +Style/ClassVars: + Exclude: + - 'config/initializers/devise.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/CombinableLoops: + Exclude: + - 'app/models/form/custom_emoji_batch.rb' + - 'app/models/form/ip_block_batch.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowedVars. +Style/FetchEnvVar: + Exclude: + - 'app/lib/redis_configuration.rb' + - 'app/lib/translation_service.rb' + - 'config/environments/development.rb' + - 'config/environments/production.rb' + - 'config/initializers/2_limited_federation_mode.rb' + - 'config/initializers/3_omniauth.rb' + - 'config/initializers/blacklists.rb' + - 'config/initializers/cache_buster.rb' + - 'config/initializers/content_security_policy.rb' + - 'config/initializers/devise.rb' + - 'config/initializers/paperclip.rb' + - 'config/initializers/vapid.rb' + - 'lib/mastodon/premailer_webpack_strategy.rb' + - 'lib/mastodon/redis_config.rb' + - 'lib/tasks/repo.rake' + - 'spec/features/profile_spec.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, MaxUnannotatedPlaceholdersAllowed, AllowedMethods, AllowedPatterns. +# SupportedStyles: annotated, template, unannotated +# AllowedMethods: redirect +Style/FormatStringToken: + Exclude: + - 'app/models/privacy_policy.rb' + - 'config/initializers/devise.rb' + - 'lib/paperclip/color_extractor.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/GlobalStdStream: + Exclude: + - 'config/boot.rb' + - 'config/environments/development.rb' + - 'config/environments/production.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. +Style/GuardClause: + Exclude: + - 'app/controllers/admin/confirmations_controller.rb' + - 'app/controllers/auth/confirmations_controller.rb' + - 'app/controllers/auth/passwords_controller.rb' + - 'app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb' + - 'app/lib/activitypub/activity/block.rb' + - 'app/lib/request.rb' + - 'app/lib/request_pool.rb' + - 'app/lib/webfinger.rb' + - 'app/lib/webfinger_resource.rb' + - 'app/models/concerns/account_counters.rb' + - 'app/models/concerns/ldap_authenticable.rb' + - 'app/models/tag.rb' + - 'app/models/user.rb' + - 'app/services/fan_out_on_write_service.rb' + - 'app/services/post_status_service.rb' + - 'app/services/process_hashtags_service.rb' + - 'app/workers/move_worker.rb' + - 'app/workers/redownload_avatar_worker.rb' + - 'app/workers/redownload_header_worker.rb' + - 'app/workers/redownload_media_worker.rb' + - 'app/workers/remote_account_refresh_worker.rb' + - 'config/initializers/devise.rb' + - 'db/migrate/20170901141119_truncate_preview_cards.rb' + - 'db/post_migrate/20220704024901_migrate_settings_to_user_roles.rb' + - 'lib/devise/two_factor_ldap_authenticatable.rb' + - 'lib/devise/two_factor_pam_authenticatable.rb' + - 'lib/mastodon/cli/accounts.rb' + - 'lib/mastodon/cli/maintenance.rb' + - 'lib/mastodon/cli/media.rb' + - 'lib/paperclip/attachment_extensions.rb' + - 'lib/tasks/repo.rake' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: braces, no_braces +Style/HashAsLastArrayItem: + Exclude: + - 'app/controllers/admin/statuses_controller.rb' + - 'app/controllers/api/v1/statuses_controller.rb' + - 'app/models/concerns/account_counters.rb' + - 'app/models/concerns/status_threading_concern.rb' + - 'app/models/status.rb' + - 'app/services/batched_remove_status_service.rb' + - 'app/services/notify_service.rb' + - 'db/migrate/20181024224956_migrate_account_conversations.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/HashTransformValues: + Exclude: + - 'app/serializers/rest/web_push_subscription_serializer.rb' + - 'app/services/import_service.rb' + +# This cop supports safe autocorrection (--autocorrect). +Style/IfUnlessModifier: + Exclude: + - 'config/environments/production.rb' + - 'config/initializers/devise.rb' + - 'config/initializers/ffmpeg.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: InverseMethods, InverseBlocks. +Style/InverseMethods: + Exclude: + - 'app/models/custom_filter.rb' + - 'app/services/update_account_service.rb' + - 'spec/controllers/activitypub/replies_controller_spec.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: line_count_dependent, lambda, literal +Style/Lambda: + Exclude: + - 'config/initializers/simple_form.rb' + - 'config/routes.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/MapToHash: + Exclude: + - 'app/models/status.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: literals, strict +Style/MutableConstant: + Exclude: + - 'app/models/tag.rb' + - 'app/services/delete_account_service.rb' + - 'lib/mastodon/migration_warning.rb' + +# This cop supports safe autocorrection (--autocorrect). +Style/NilLambda: + Exclude: + - 'config/initializers/paperclip.rb' + +# Configuration parameters: AllowedMethods. +# AllowedMethods: respond_to_missing? +Style/OptionalBooleanParameter: + Exclude: + - 'app/helpers/admin/account_moderation_notes_helper.rb' + - 'app/helpers/jsonld_helper.rb' + - 'app/lib/admin/system_check/message.rb' + - 'app/lib/request.rb' + - 'app/lib/webfinger.rb' + - 'app/services/block_domain_service.rb' + - 'app/services/fetch_resource_service.rb' + - 'app/workers/domain_block_worker.rb' + - 'app/workers/unfollow_follow_worker.rb' + - 'lib/mastodon/redis_config.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: PreferredDelimiters. +Style/PercentLiteralDelimiters: + Exclude: + - 'config/deploy.rb' + - 'config/initializers/doorkeeper.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: short, verbose +Style/PreferredHashMethods: + Exclude: + - 'config/initializers/paperclip.rb' + +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantConstantBase: + Exclude: + - 'config/environments/production.rb' + - 'config/initializers/sidekiq.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: SafeForConstants. +Style/RedundantFetchBlock: + Exclude: + - 'config/initializers/1_hosts.rb' + - 'config/initializers/chewy.rb' + - 'config/initializers/devise.rb' + - 'config/initializers/paperclip.rb' + - 'config/puma.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowMultipleReturnValues. +Style/RedundantReturn: + Exclude: + - 'app/controllers/api/v1/directories_controller.rb' + - 'app/controllers/auth/confirmations_controller.rb' + - 'app/lib/ostatus/tag_manager.rb' + - 'app/models/form/import.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. +# AllowedMethods: present?, blank?, presence, try, try! +Style/SafeNavigation: + Exclude: + - 'app/models/concerns/account_finder_concern.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: only_raise, only_fail, semantic +Style/SignalException: + Exclude: + - 'lib/devise/two_factor_ldap_authenticatable.rb' + - 'lib/devise/two_factor_pam_authenticatable.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/SingleArgumentDig: + Exclude: + - 'lib/webpacker/manifest_extensions.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: require_parentheses, require_no_parentheses +Style/StabbyLambdaParentheses: + Exclude: + - 'config/environments/production.rb' + - 'config/initializers/content_security_policy.rb' + +# This cop supports safe autocorrection (--autocorrect). +Style/StderrPuts: + Exclude: + - 'config/boot.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: Mode. +Style/StringConcatenation: + Exclude: + - 'config/initializers/paperclip.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiterals: + Exclude: + - 'config/environments/production.rb' + - 'config/initializers/backtrace_silencers.rb' + - 'config/initializers/http_client_proxy.rb' + - 'config/initializers/rack_attack.rb' + - 'config/initializers/webauthn.rb' + - 'config/routes.rb' + +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments. +# AllowedMethods: define_method, mail, respond_to +Style/SymbolProc: + Exclude: + - 'config/initializers/3_omniauth.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, AllowSafeAssignment. +# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex +Style/TernaryParentheses: + Exclude: + - 'config/environments/development.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInArguments: + Exclude: + - 'config/initializers/paperclip.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInHashLiteral: + Exclude: + - 'config/environments/production.rb' + - 'config/environments/test.rb' + +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, MinSize, WordRegex. +# SupportedStyles: percent, brackets +Style/WordArray: + Exclude: + - 'app/helpers/languages_helper.rb' + - 'config/initializers/cors.rb' + - 'spec/controllers/settings/imports_controller_spec.rb' + - 'spec/models/form/import_spec.rb' diff --git a/.ruby-version b/.ruby-version index 818bd47ab..b347b11ea 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.0.6 +3.2.3 diff --git a/.yarnclean b/.yarnclean index 0cc2b50d7..21eb734a6 100644 --- a/.yarnclean +++ b/.yarnclean @@ -44,3 +44,6 @@ Gruntfile.js # for specific ignore !.svgo.yml !sass-lint/**/*.yml + +# breaks lint-staged or generally anything using https://github.com/eemeli/yaml/issues/384 +!**/yaml/dist/**/doc diff --git a/AUTHORS.md b/AUTHORS.md index 18b9f2d70..78cc37a17 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -9,33 +9,40 @@ and provided thanks to the work of the following contributors: * [ClearlyClaire](https://github.com/ClearlyClaire) * [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) * [ykzts](https://github.com/ykzts) -* [akihikodaki](https://github.com/akihikodaki) * [mjankowski](https://github.com/mjankowski) +* [akihikodaki](https://github.com/akihikodaki) +* [nschonni](https://github.com/nschonni) +* [renovate[bot]](https://github.com/apps/renovate) * [unarist](https://github.com/unarist) * [noellabo](https://github.com/noellabo) +* [tribela](https://github.com/tribela) * [abcang](https://github.com/abcang) * [yiskah](https://github.com/yiskah) -* [tribela](https://github.com/tribela) * [mayaeh](https://github.com/mayaeh) * [nolanlawson](https://github.com/nolanlawson) * [ysksn](https://github.com/ysksn) * [sorin-davidoi](https://github.com/sorin-davidoi) +* [renchap](https://github.com/renchap) * [lynlynlynx](https://github.com/lynlynlynx) * [m4sk1n](mailto:me@m4sk.in) * [Marcin Mikołajczak](mailto:me@m4sk.in) +* [danielmbrasil](https://github.com/danielmbrasil) * [shleeable](https://github.com/shleeable) +* [c960657](https://github.com/c960657) * [renatolond](https://github.com/renatolond) * [zunda](https://github.com/zunda) +* [ineffyble](https://github.com/ineffyble) +* [takayamaki](https://github.com/takayamaki) * [alpaca-tc](https://github.com/alpaca-tc) * [nclm](https://github.com/nclm) -* [ineffyble](https://github.com/ineffyble) +* [trwnh](https://github.com/trwnh) * [ariasuni](https://github.com/ariasuni) * [Masoud Abkenar](mailto:ampbox@gmail.com) * [blackle](https://github.com/blackle) +* [ThisIsMissEm](https://github.com/ThisIsMissEm) * [Quent-in](https://github.com/Quent-in) -* [Brawaru](https://github.com/Brawaru) +* [brawaru](https://github.com/brawaru) * [JantsoP](https://github.com/JantsoP) -* [trwnh](https://github.com/trwnh) * [nullkal](https://github.com/nullkal) * [yookoala](https://github.com/yookoala) * [dunn](https://github.com/dunn) @@ -46,10 +53,8 @@ and provided thanks to the work of the following contributors: * [danhunsaker](https://github.com/danhunsaker) * [eramdam](https://github.com/eramdam) * [Jeroen](mailto:jeroenpraat@users.noreply.github.com) -* [takayamaki](https://github.com/takayamaki) * [masarakki](https://github.com/masarakki) * [ticky](https://github.com/ticky) -* [ThisIsMissEm](https://github.com/ThisIsMissEm) * [hinaloe](https://github.com/hinaloe) * [hcmiya](https://github.com/hcmiya) * [stephenburgess8](https://github.com/stephenburgess8) @@ -61,14 +66,14 @@ and provided thanks to the work of the following contributors: * [rkarabut](https://github.com/rkarabut) * [jeroenpraat](mailto:jeroenpraat@users.noreply.github.com) * [marek-lach](https://github.com/marek-lach) +* [krainboltgreene](https://github.com/krainboltgreene) * [Artoria2e5](https://github.com/Artoria2e5) * [rinsuki](https://github.com/rinsuki) * [marrus-sh](https://github.com/marrus-sh) -* [krainboltgreene](https://github.com/krainboltgreene) -* [pfigel](https://github.com/pfigel) -* [BoFFire](https://github.com/BoFFire) -* [Aldarone](https://github.com/Aldarone) * [deepy](https://github.com/deepy) +* [pfigel](https://github.com/pfigel) +* [Aldarone](https://github.com/Aldarone) +* [BoFFire](https://github.com/BoFFire) * [clworld](https://github.com/clworld) * [MasterGroosha](https://github.com/MasterGroosha) * [dracos](https://github.com/dracos) @@ -76,19 +81,25 @@ and provided thanks to the work of the following contributors: * [SerCom_KC](mailto:sercom-kc@users.noreply.github.com) * [Sylvhem](https://github.com/Sylvhem) * [koyuawsmbrtn](https://github.com/koyuawsmbrtn) +* [taichi221228](https://github.com/taichi221228) * [MitarashiDango](https://github.com/MitarashiDango) * [angristan](https://github.com/angristan) * [JeanGauthier](https://github.com/JeanGauthier) * [kschaper](https://github.com/kschaper) * [beatrix-bitrot](https://github.com/beatrix-bitrot) +* [github-actions[bot]](https://github.com/apps/github-actions) * [BenLubar](https://github.com/BenLubar) * [mkljczk](https://github.com/mkljczk) * [adbelle](https://github.com/adbelle) * [evanminto](https://github.com/evanminto) * [MightyPork](https://github.com/MightyPork) * [ashleyhull-versent](https://github.com/ashleyhull-versent) +* [gunchleoc](https://github.com/gunchleoc) +* [kedamaDQ](https://github.com/kedamaDQ) * [yhirano55](https://github.com/yhirano55) * [mashirozx](https://github.com/mashirozx) +* [dariusk](https://github.com/dariusk) +* [mgmn](https://github.com/mgmn) * [devkral](https://github.com/devkral) * [camponez](https://github.com/camponez) * [Hugo Gameiro](mailto:hmgameiro@gmail.com) @@ -96,12 +107,14 @@ and provided thanks to the work of the following contributors: * [SerCom_KC](mailto:szescxz@gmail.com) * [aschmitz](https://github.com/aschmitz) * [mfmfuyu](https://github.com/mfmfuyu) -* [kedamaDQ](https://github.com/kedamaDQ) +* [mistydemeo](https://github.com/mistydemeo) * [fpiesche](https://github.com/fpiesche) * [gandaro](https://github.com/gandaro) * [johnsudaar](https://github.com/johnsudaar) * [trebmuh](https://github.com/trebmuh) * [rmhasan](https://github.com/rmhasan) +* [Trevor Wolf](mailto:teeerevor@gmail.com) +* [jsgoldstein](https://github.com/jsgoldstein) * [lindwurm](https://github.com/lindwurm) * [victorhck](mailto:victorhck@geeko.site) * [voidsatisfaction](https://github.com/voidsatisfaction) @@ -109,49 +122,58 @@ and provided thanks to the work of the following contributors: * [seefood](https://github.com/seefood) * [jackjennings](https://github.com/jackjennings) * [sunny](https://github.com/sunny) +* [VyrCossont](https://github.com/VyrCossont) +* [Izorkin](https://github.com/Izorkin) * [puckipedia](https://github.com/puckipedia) * [splaGit](https://github.com/splaGit) * [tateisu](https://github.com/tateisu) * [walf443](https://github.com/walf443) +* [progval](https://github.com/progval) * [JoelQ](https://github.com/JoelQ) -* [mistydemeo](https://github.com/mistydemeo) * [Ashley](mailto:expenses@airmail.cc) * [xqus](https://github.com/xqus) +* [CSDUMMI](https://github.com/CSDUMMI) * [pfm-eyesightjp](https://github.com/pfm-eyesightjp) +* [S-H-GAMELINKS](https://github.com/S-H-GAMELINKS) * [fakenine](https://github.com/fakenine) +* [Signez](https://github.com/Signez) * [tsuwatch](https://github.com/tsuwatch) -* [progval](https://github.com/progval) * [victorhck](https://github.com/victorhck) -* [Izorkin](https://github.com/Izorkin) +* [luzpaz](https://github.com/luzpaz) * [manuelviens](mailto:manuelviens@users.noreply.github.com) * [fvh-P](https://github.com/fvh-P) * [lfuelling](https://github.com/lfuelling) * [rtucker](https://github.com/rtucker) * [Anna e só](mailto:contraexemplos@gmail.com) * [danieljakots](https://github.com/danieljakots) -* [dariusk](https://github.com/dariusk) * [Gomasy](https://github.com/Gomasy) -* [kazu9su](https://github.com/kazu9su) -* [komic](https://github.com/komic) +* [j-f1](https://github.com/j-f1) +* [kescherCode](https://github.com/kescherCode) +* [tooooooooomy](https://github.com/tooooooooomy) +* [Komic](mailto:contact@komic.eu) * [lmorchard](https://github.com/lmorchard) * [diomed](https://github.com/diomed) * [Neetshin](mailto:neetshin@neetsh.in) * [rainyday](https://github.com/rainyday) +* [rgroothuijsen](https://github.com/rgroothuijsen) +* [rrgeorge](https://github.com/rrgeorge) * [tcitworld](https://github.com/tcitworld) +* [timetinytim](https://github.com/timetinytim) * [valentin2105](https://github.com/valentin2105) * [yuntan](https://github.com/yuntan) * [goofy-bz](mailto:goofy@babelzilla.org) * [kadiix](https://github.com/kadiix) * [kodacs](https://github.com/kodacs) -* [luzpaz](https://github.com/luzpaz) * [marcin mikołajczak](mailto:me@m4sk.in) -* [berkes](https://github.com/berkes) +* [prplecake](https://github.com/prplecake) * [KScl](https://github.com/KScl) -* [sterdev](https://github.com/sterdev) +* [deprecated-acct](https://github.com/deprecated-acct) * [TheKinrar](https://github.com/TheKinrar) * [AA4ch1](https://github.com/AA4ch1) * [alexgleason](https://github.com/alexgleason) +* [berkes](https://github.com/berkes) * [cpytel](https://github.com/cpytel) +* [connorshea](https://github.com/connorshea) * [cutls](https://github.com/cutls) * [northerner](https://github.com/northerner) * [weex](https://github.com/weex) @@ -159,21 +181,22 @@ and provided thanks to the work of the following contributors: * [fhemberger](https://github.com/fhemberger) * [greysteil](https://github.com/greysteil) * [henrycatalinismith](https://github.com/henrycatalinismith) +* [hs4man21](https://github.com/hs4man21) * [HolgerHuo](https://github.com/HolgerHuo) * [d6rkaiz](https://github.com/d6rkaiz) * [ladyisatis](https://github.com/ladyisatis) * [JMendyk](https://github.com/JMendyk) -* [kescherCode](https://github.com/kescherCode) * [JohnD28](https://github.com/JohnD28) +* [casaper](https://github.com/casaper) * [znz](https://github.com/znz) * [saper](https://github.com/saper) * [Naouak](https://github.com/Naouak) * [pawelngei](https://github.com/pawelngei) -* [rgroothuijsen](https://github.com/rgroothuijsen) * [reneklacan](https://github.com/reneklacan) * [ekiru](https://github.com/ekiru) * [unasuke](https://github.com/unasuke) * [geta6](https://github.com/geta6) +* [gol-cha](https://github.com/gol-cha) * [happycoloredbanana](https://github.com/happycoloredbanana) * [joenepraat](https://github.com/joenepraat) * [leopku](https://github.com/leopku) @@ -184,6 +207,7 @@ and provided thanks to the work of the following contributors: * [aji-su](https://github.com/aji-su) * [ikuradon](https://github.com/ikuradon) * [nzws](https://github.com/nzws) +* [moritzheiber](https://github.com/moritzheiber) * [SuperSandro2000](https://github.com/SuperSandro2000) * [178inaba](https://github.com/178inaba) * [acid-chicken](https://github.com/acid-chicken) @@ -192,17 +216,24 @@ and provided thanks to the work of the following contributors: * [aablinov](https://github.com/aablinov) * [stalker314314](https://github.com/stalker314314) * [cohosh](https://github.com/cohosh) +* [muffinista](https://github.com/muffinista) * [huertanix](https://github.com/huertanix) +* [consideRatio](https://github.com/consideRatio) * [eleboucher](https://github.com/eleboucher) +* [FrancisMurillo](https://github.com/FrancisMurillo) * [halkeye](https://github.com/halkeye) * [Hanage999](https://github.com/Hanage999) * [treby](https://github.com/treby) +* [eltociear](https://github.com/eltociear) * [jpdevries](https://github.com/jpdevries) * [gdpelican](https://github.com/gdpelican) * [pbzweihander](https://github.com/pbzweihander) * [MonaLisaOverrdrive](https://github.com/MonaLisaOverrdrive) * [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name) +* [Tak](https://github.com/Tak) * [panarom](https://github.com/panarom) +* [MFTabriz](https://github.com/MFTabriz) +* [vmstan](https://github.com/vmstan) * [Dar13](https://github.com/Dar13) * [nevillepark](https://github.com/nevillepark) * [ornithocoder](https://github.com/ornithocoder) @@ -211,8 +242,10 @@ and provided thanks to the work of the following contributors: * [qguv](https://github.com/qguv) * [Ram Lmn](mailto:ramlmn@users.noreply.github.com) * [Sascha](mailto:sascha@serenitylabs.cloud) +* [SISheogorath](https://github.com/SISheogorath) * [harukasan](https://github.com/harukasan) * [stamak](https://github.com/stamak) +* [OmmyZhang](https://github.com/OmmyZhang) * [Technowix](https://github.com/Technowix) * [Zoeille](https://github.com/Zoeille) * [Thorwegian](https://github.com/Thorwegian) @@ -220,30 +253,31 @@ and provided thanks to the work of the following contributors: * [gled-rs](https://github.com/gled-rs) * [Valentin_NC](mailto:valentin.ouvrard@nautile.sarl) * [R0ckweb](https://github.com/R0ckweb) +* [alfe](https://github.com/alfe) * [caasi](https://github.com/caasi) * [chandrn7](https://github.com/chandrn7) * [chr-1x](https://github.com/chr-1x) * [esetomo](https://github.com/esetomo) * [foxiehkins](https://github.com/foxiehkins) -* [gol-cha](https://github.com/gol-cha) * [highemerly](https://github.com/highemerly) * [hoodie](mailto:hoodiekitten@outlook.com) * [kaiyou](https://github.com/kaiyou) * [007lva](https://github.com/007lva) * [luzi82](https://github.com/luzi82) -* [prplecake](https://github.com/prplecake) * [duxovni](https://github.com/duxovni) * [slice](https://github.com/slice) * [tmm576](https://github.com/tmm576) * [unsmell](mailto:unsmell@users.noreply.github.com) * [valerauko](https://github.com/valerauko) * [Grawl](https://github.com/Grawl) -* [chriswmartin](https://github.com/chriswmartin) +* [minacle](https://github.com/minacle) * [AndreLewin](https://github.com/AndreLewin) * [0xflotus](https://github.com/0xflotus) * [redtachyons](https://github.com/redtachyons) * [thurloat](https://github.com/thurloat) +* [Akkiesoft](https://github.com/Akkiesoft) * [aaribaud](https://github.com/aaribaud) +* [Saiv46](https://github.com/Saiv46) * [pointlessone](https://github.com/pointlessone) * [Andrew](mailto:andrewlchronister@gmail.com) * [arielrodrigues](https://github.com/arielrodrigues) @@ -254,29 +288,30 @@ and provided thanks to the work of the following contributors: * [dissolve](https://github.com/dissolve) * [PurpleBooth](https://github.com/PurpleBooth) * [bradurani](https://github.com/bradurani) -* [wavebeem](https://github.com/wavebeem) +* [bramus](https://github.com/bramus) +* [Brian Mock](mailto:brian@mockbrian.com) * [thermosflasche](https://github.com/thermosflasche) * [LottieVixen](https://github.com/LottieVixen) +* [chriswmartin](https://github.com/chriswmartin) * [wchristian](https://github.com/wchristian) -* [muffinista](https://github.com/muffinista) * [cdutson](https://github.com/cdutson) * [farlistener](https://github.com/farlistener) * [baby-gnu](https://github.com/baby-gnu) * [divergentdave](https://github.com/divergentdave) +* [lochiiconnectivity](https://github.com/lochiiconnectivity) * [DavidLibeau](https://github.com/DavidLibeau) * [dmerejkowsky](https://github.com/dmerejkowsky) * [ddevault](https://github.com/ddevault) * [emilyst](https://github.com/emilyst) -* [consideRatio](https://github.com/consideRatio) * [Fjoerfoks](https://github.com/Fjoerfoks) * [fmauNeko](https://github.com/fmauNeko) * [gloaec](https://github.com/gloaec) * [unstabler](https://github.com/unstabler) * [potato4d](https://github.com/potato4d) * [h-izumi](https://github.com/h-izumi) +* [HeitorMC](https://github.com/HeitorMC) * [ErikXXon](https://github.com/ErikXXon) * [ian-kelling](https://github.com/ian-kelling) -* [eltociear](https://github.com/eltociear) * [immae](https://github.com/immae) * [J0WI](https://github.com/J0WI) * [koboldunderlord](https://github.com/koboldunderlord) @@ -285,7 +320,9 @@ and provided thanks to the work of the following contributors: * [raggi](https://github.com/raggi) * [jasonrhodes](https://github.com/jasonrhodes) * [Jason Snell](mailto:jason@newrelic.com) +* [casperisfine](https://github.com/casperisfine) * [jviide](https://github.com/jviide) +* [joshuap](https://github.com/joshuap) * [YuleZ](https://github.com/YuleZ) * [jtracey](https://github.com/jtracey) * [crakaC](https://github.com/crakaC) @@ -294,14 +331,12 @@ and provided thanks to the work of the following contributors: * [Kazhnuz](https://github.com/Kazhnuz) * [mkody](https://github.com/mkody) * [connyduck](https://github.com/connyduck) -* [Tak](https://github.com/Tak) * [LindseyB](https://github.com/LindseyB) * [Lorenz Diener](mailto:halcyon@icosahedron.website) * [Markus Amalthea Magnuson](mailto:markus.magnuson@gmail.com) * [madmath03](https://github.com/madmath03) * [mig5](https://github.com/mig5) * [mohe2015](https://github.com/mohe2015) -* [moritzheiber](https://github.com/moritzheiber) * [Nathaniel Suchy](mailto:me@lunorian.is) * [ndarville](https://github.com/ndarville) * [NimaBoscarino](https://github.com/NimaBoscarino) @@ -312,21 +347,24 @@ and provided thanks to the work of the following contributors: * [xPaw](https://github.com/xPaw) * [petzah](https://github.com/petzah) * [PeterDaveHello](https://github.com/PeterDaveHello) +* [sidp](https://github.com/sidp) * [ignisf](https://github.com/ignisf) * [postmodern](https://github.com/postmodern) * [lumenwrites](https://github.com/lumenwrites) * [remram44](https://github.com/remram44) * [sts10](https://github.com/sts10) +* [arbolitoloco1](https://github.com/arbolitoloco1) * [u1-liquid](https://github.com/u1-liquid) -* [SISheogorath](https://github.com/SISheogorath) * [rosylilly](https://github.com/rosylilly) * [withshubh](https://github.com/withshubh) * [sim6](https://github.com/sim6) * [Sir-Boops](https://github.com/Sir-Boops) * [stemid](https://github.com/stemid) * [sumdog](https://github.com/sumdog) -* [OmmyZhang](https://github.com/OmmyZhang) +* [shuuji3](https://github.com/shuuji3) +* [edent](https://github.com/edent) * [ThomasLeister](https://github.com/ThomasLeister) +* [timothyjrogers](https://github.com/timothyjrogers) * [Tom McAtee](mailto:a1608768@student.adelaide.edu.au) * [tototoshi](https://github.com/tototoshi) * [TrashMacNugget](https://github.com/TrashMacNugget) @@ -343,11 +381,13 @@ and provided thanks to the work of the following contributors: * [aus-social](https://github.com/aus-social) * [bsky](mailto:git@bsky.moe) * [bsky](mailto:me@imbsky.net) +* [cadars](https://github.com/cadars) * [codl](https://github.com/codl) * [cpsdqs](https://github.com/cpsdqs) * [dogelover911](https://github.com/dogelover911) +* [emilweth](https://github.com/emilweth) * [barzamin](https://github.com/barzamin) -* [gunchleoc](https://github.com/gunchleoc) +* [forsamori](https://github.com/forsamori) * [fhalna](https://github.com/fhalna) * [haoyayoi](https://github.com/haoyayoi) * [helloworldstack](https://github.com/helloworldstack) @@ -358,6 +398,7 @@ and provided thanks to the work of the following contributors: * [mbajur](https://github.com/mbajur) * [matsurai25](https://github.com/matsurai25) * [mecab](https://github.com/mecab) +* [nametoolong](https://github.com/nametoolong) * [nicobz25](https://github.com/nicobz25) * [niwatori24](https://github.com/niwatori24) * [noiob](https://github.com/noiob) @@ -374,14 +415,15 @@ and provided thanks to the work of the following contributors: * [vjackson725](https://github.com/vjackson725) * [wxcafe](https://github.com/wxcafe) * [新都心(Neet Shin)](mailto:nucx@dio-vox.com) -* [clarfonthey](https://github.com/clarfonthey) -* [cygnan](https://github.com/cygnan) -* [Awea](https://github.com/Awea) +* [tenderlove](https://github.com/tenderlove) +* [raboof](https://github.com/raboof) * [single-right-quote](https://github.com/single-right-quote) * [8398a7](https://github.com/8398a7) * [857b](https://github.com/857b) +* [9p4](https://github.com/9p4) * [insom](https://github.com/insom) * [tachyons](https://github.com/tachyons) +* [AcesFullOfKings](https://github.com/AcesFullOfKings) * [Esteth](https://github.com/Esteth) * [unascribed](https://github.com/unascribed) * [Aguay-val](https://github.com/Aguay-val) @@ -389,13 +431,14 @@ and provided thanks to the work of the following contributors: * [h3poteto](https://github.com/h3poteto) * [unleashed](https://github.com/unleashed) * [alxrcs](https://github.com/alxrcs) +* [alexstine](https://github.com/alexstine) * [console-cowboy](https://github.com/console-cowboy) -* [Saiv46](https://github.com/Saiv46) * [Alkarex](https://github.com/Alkarex) * [a2](https://github.com/a2) * [Alfie John](mailto:33c6c91f3bb4a391082e8a29642cafaf@alfie.wtf) * [0xa](https://github.com/0xa) * [ashpieboop](https://github.com/ashpieboop) +* [alisonw](https://github.com/alisonw) * [virtualpain](https://github.com/virtualpain) * [sapphirus](https://github.com/sapphirus) * [amandavisconti](https://github.com/amandavisconti) @@ -406,88 +449,120 @@ and provided thanks to the work of the following contributors: * [schas002](https://github.com/schas002) * [contraexemplo](https://github.com/contraexemplo) * [abackstrom](https://github.com/abackstrom) +* [AntoninDelFabbro](https://github.com/AntoninDelFabbro) * [orlea](https://github.com/orlea) * [armandfardeau](https://github.com/armandfardeau) -* [raboof](https://github.com/raboof) * [v-aisac](https://github.com/v-aisac) -* [gi-yt](https://github.com/gi-yt) -* [boahc077](https://github.com/boahc077) -* [aldatsa](https://github.com/aldatsa) -* [jumbosushi](https://github.com/jumbosushi) -* [acuteaura](https://github.com/acuteaura) -* [ayumin](https://github.com/ayumin) -* [bzg](https://github.com/bzg) -* [BastienDurel](https://github.com/BastienDurel) -* [bearice](https://github.com/bearice) -* [li-bei](https://github.com/li-bei) -* [hardillb](https://github.com/hardillb) +* [Arya K](mailto:73596856+gi-yt@users.noreply.github.com) +* [Ashish Kurmi](mailto:100655670+boahc077@users.noreply.github.com) +* [Asier Iturralde Sarasola](mailto:asier.iturralde@gmail.com) +* [Atsushi Yamamoto](mailto:yamaatsushi927@gmail.com) +* [Aurelia](mailto:aurelia@schittler.dev) +* [Avdi Grimm](mailto:avdi@users.noreply.github.com) +* [Ayumu AIZAWA](mailto:ayumu.aizawa@gmail.com) +* [Bastien](mailto:bzg@users.noreply.github.com) +* [Bastien Durel](mailto:bastien@durel.org) +* [Bearice Ren](mailto:bearice@gmail.com) +* [Bei Li](mailto:kylinroc@gmail.com) +* [Ben Hardill](mailto:b.hardill@gmail.com) * [Benedikt Geißler](mailto:benedikt@g5r.eu) -* [BenisonSebastian](https://github.com/BenisonSebastian) +* [BenisonSebastian](mailto:33474422+benisonsebastian@users.noreply.github.com) * [Blake](mailto:blake.barnett@postmates.com) +* [Botao Wang](mailto:wxt2005@gmail.com) * [Brad Janke](mailto:brad.janke@gmail.com) -* [braydofficial](https://github.com/braydofficial) -* [bclindner](https://github.com/bclindner) -* [brycied00d](https://github.com/brycied00d) -* [carlosjs23](https://github.com/carlosjs23) -* [WyriHaximus](https://github.com/WyriHaximus) -* [cgxxx](https://github.com/cgxxx) -* [kibitan](https://github.com/kibitan) -* [cdzombak](https://github.com/cdzombak) -* [chrisheninger](https://github.com/chrisheninger) -* [chris-martin](https://github.com/chris-martin) -* [offbyone](https://github.com/offbyone) -* [cclauss](https://github.com/cclauss) -* [DoubleMalt](https://github.com/DoubleMalt) -* [Moosh-be](https://github.com/Moosh-be) -* [cchoi12](https://github.com/cchoi12) -* [Motoma](https://github.com/Motoma) +* [Brayd](mailto:byronfroehlich@proton.me) +* [Brian C. Lindner](mailto:cslindner@gmail.com) +* [Brian Campbell](mailto:unlambda@gmail.com) +* [Bryce Chidester](mailto:bryce@cobryce.com) +* [BtbN](mailto:btbn@btbn.de) +* [ButterflyOfFire](mailto:42316180+boffire@users.noreply.github.com) +* [Bèr Kessels](mailto:github@berk.es) +* [Carl Schwan](mailto:carl@carlschwan.eu) +* [Carlos A. Escobar](mailto:ingcarlosandresescobar@gmail.com) +* [Cees-Jan Kiewiet](mailto:ceesjank@gmail.com) +* [CgX](mailto:github@cgx.me) +* [Chikahiro Tokoro](mailto:uzukifirst@gmail.com) +* [Chike Nwaenie](mailto:chikenwaenie@gmail.com) +* [Chris](mailto:cmarti14@artic.edu) +* [Chris Dzombak](mailto:chris@chrisdzombak.net) +* [Chris Funderburg](mailto:chris@funderburg.me) +* [Chris Heninger](mailto:heninger@gmail.com) +* [Chris Johnson](mailto:49479599+workeffortwaste@users.noreply.github.com) +* [Chris Martin](mailto:ch.martin@gmail.com) +* [Chris Rose](mailto:offbyone@github.com) +* [Christian Clauss](mailto:cclauss@me.com) +* [Christoph Witzany](mailto:christoph@web.crofting.com) +* [Christophe Gesché](mailto:moosh@php.net) +* [Christopher Choi](mailto:cdddchris@gmail.com) +* [Christopher Gilbert](mailto:motoma@gmail.com) * [Christopher Kolstad](mailto:christopher.kolstad@finn.no) -* [csu](https://github.com/csu) -* [kklleemm](https://github.com/kklleemm) -* [colindean](https://github.com/colindean) -* [CommanderRoot](https://github.com/CommanderRoot) -* [connorshea](https://github.com/connorshea) -* [DeeUnderscore](https://github.com/DeeUnderscore) -* [dachinat](https://github.com/dachinat) +* [Christopher Nethercott](mailto:ccnethercott@gmail.com) +* [Christopher Su](mailto:christophersu9@gmail.com) +* [Clar Charr](mailto:clar@charr.xyz) +* [Clar Fon](mailto:them@lightdark.xyz) +* [Clément D](mailto:kklleemm@users.noreply.github.com) +* [Colette Kerr](mailto:colette.m.y.kerr@gmail.com) +* [Colin Dean](mailto:colindean@users.noreply.github.com) +* [CommanderRoot](mailto:commanderroot@users.noreply.github.com) +* [Cygnan](mailto:email@cygnan.com) +* [Cygnan](mailto:mail@cygnan.com) +* [D Anzorge](mailto:d.anzorge@gmail.com) +* [Dachi Natsvlishvili](mailto:dachinat@gmail.com) * [Daggertooth](mailto:dev@monsterpit.net) -* [watilde](https://github.com/watilde) -* [dalehenries](https://github.com/dalehenries) -* [daprice](https://github.com/daprice) -* [da2x](https://github.com/da2x) -* [codesections](https://github.com/codesections) -* [dar5hak](https://github.com/dar5hak) -* [kant](https://github.com/kant) -* [maxolasersquad](https://github.com/maxolasersquad) +* [Daijiro Wachi](mailto:daijiro.wachi@gmail.com) +* [Dale Henries](mailto:dalehenries@gmail.com) +* [Dale Price](mailto:daprice@users.noreply.github.com) +* [Dan Peterson](mailto:danp@danp.net) +* [Daniel Aleksandersen](mailto:code@daniel.priv.no) +* [Daniel Axtens](mailto:daniel@axtens.net) +* [Daniel Sockwell](mailto:dsockwell@gmail.com) +* [Darshak Parikh](mailto:dar5hak@users.noreply.github.com) +* [Darío Hereñú](mailto:magallania@gmail.com) +* [David Authier](mailto:aweaoftheworld@gmail.com) +* [David Baucum](mailto:maxolasersquad@gmail.com) * [David Baumgold](mailto:david@davidbaumgold.com) * [David Caldwell](mailto:david+github@porkrind.org) * [David Celis](mailto:me@davidcel.is) * [David Hewitt](mailto:davidmhewitt@users.noreply.github.com) +* [David Leadbeater](mailto:dgl@dgl.cx) * [David Underwood](mailto:davefp@gmail.com) +* [David Vega](mailto:david-vega@users.noreply.github.com) * [David Yip](mailto:yipdw@member.fsf.org) +* [Dean Bassett](mailto:dean@dbassett.dev) * [Debanshu Kundu](mailto:debanshu.kundu@joshtechnologygroup.com) * [Denis Teyssier](mailto:admin@mascali.ovh) * [Derek Lewis](mailto:derekcecillewis@gmail.com) * [Devon Blandin](mailto:dblandin@gmail.com) +* [Douglas Blank](mailto:doug.blank@gmail.com) * [Drew Gates](mailto:aranaur@users.noreply.github.com) * [Drew Schuster](mailto:dtschust@gmail.com) * [Dryusdan](mailto:dryusdan@dryusdan.fr) * [Eai](mailto:eai@mizle.net) +* [Eashwar Ranganathan](mailto:eranganathan@lyft.com) * [Ed Knutson](mailto:knutsoned@gmail.com) -* [Effy Elden](mailto:effy@effy.space) +* [Elizabeth Martín Campos](mailto:me@elizabeth.sh) * [Elizabeth Myers](mailto:elizabeth@interlinked.me) +* [Ell Bradshaw](mailto:cincodenada@gmail.com) * [Eric](mailto:enewhuis@gmail.com) * [Eric Blade](mailto:blade.eric@gmail.com) * [Eshin Kunishima](mailto:mikoim@users.noreply.github.com) * [Espen Rønnevik](mailto:espen@ronnevik.net) +* [Essem](mailto:smswessem@gmail.com) +* [Evan](mailto:35814742+evanphilip@users.noreply.github.com) * [Expenses](mailto:expenses@airmail.cc) * [Fabian Schlenz](mailto:mail@fabianonline.de) * [Faye Duxovni](mailto:duxovni@duxovni.org) * [Filipe Rodrigues](mailto:shello@shello.org) * [Finariel](mailto:finariel@gmail.com) +* [Florin](mailto:csflorin@users.noreply.github.com) +* [Foritus](mailto:rich@aornis.com) * [Francis Chong](mailto:francis@ignition.hk) * [Franck Zoccolo](mailto:franck@zoccolo.com) +* [Frankie Roberto](mailto:frankie@frankieroberto.com) * [Fred Wenzel](mailto:fwenzel@users.noreply.github.com) +* [Fries](mailto:40834252+ayefries@users.noreply.github.com) * [Gabriel Rubens](mailto:gabrielrumiranda@gmail.com) +* [Gabriel Simmer](mailto:github@gmem.ca) * [Gaelan Steele](mailto:gbs@canishe.com) * [Genbu Hase](mailto:hasegenbu@gmail.com) * [Georg Gadinger](mailto:nilsding@nilsding.org) @@ -509,48 +584,65 @@ and provided thanks to the work of the following contributors: * [Hiroe Jun](mailto:jun.hiroe@gmail.com) * [Hiromi Kai](mailto:pie05041008@gmail.com) * [Hisham Muhammad](mailto:hisham@gobolinux.org) +* [HonkingGoose](mailto:34918129+honkinggoose@users.noreply.github.com) * [Hugo "Slaynash" Flores](mailto:hugoflores@hotmail.fr) * [INAGAKI Hiroshi](mailto:musashino205@users.noreply.github.com) * [IWAI, Masaharu](mailto:iwaim.sub@gmail.com) +* [Ian](mailto:ian@devolute.net) * [Ian McCowan](mailto:imccowan@gmail.com) * [Ian McDowell](mailto:me@ianmcdowell.net) * [Iijima Yasushi](mailto:kurage.cc@gmail.com) * [Ingo Blechschmidt](mailto:iblech@web.de) * [Irie Aoi](mailto:eai@mizle.net) +* [Ivan Rodriguez](mailto:104603218+irod22@users.noreply.github.com) * [J Yeary](mailto:usbsnowcrash@users.noreply.github.com) +* [JT Olio](mailto:hello@jtolio.com) * [Jack Michaud](mailto:jack-michaud@users.noreply.github.com) +* [Jaehong Kang](mailto:sinoru@me.com) * [Jakub Mendyk](mailto:jakubmendyk.szkola@gmail.com) * [James](mailto:james.allen.vaughan@gmail.com) +* [James Adney](mailto:jfadney@gmail.com) * [James Smith](mailto:james@floppy.org.uk) +* [Jamie Hoyle](mailto:j@jamiehoyle.com) * [Jarek Lipski](mailto:pub@loomchild.net) +* [Jay Prakash Kalia](mailto:jaykalia047@gmail.com) * [Jennifer Glauche](mailto:=^.^=@github19.jglauche.de) * [Jennifer Kruse](mailto:jenkr55@gmail.com) * [Jeremy Rose](mailto:nornagon@nornagon.net) * [Jessica](mailto:46502909+hyenagirl64@users.noreply.github.com) * [Jessica K. Litwin](mailto:jessica@litw.in) +* [Jim Myhrberg](mailto:contact@jimeh.me) * [Jo Decker](mailto:trolldecker@users.noreply.github.com) * [Joan Montané](mailto:jmontane@users.noreply.github.com) * [Joe](mailto:401283+htmlbyjoe@users.noreply.github.com) * [Joe Friedl](mailto:stuff@joefriedl.net) +* [Jonathan Hawkes](mailto:jonathan@thoughtbuilt.com) * [Jonathan Klee](mailto:klee.jonathan@gmail.com) * [Jordan Guerder](mailto:jguerder@fr.pulseheberg.net) * [Joseph Mingrone](mailto:jehops@users.noreply.github.com) * [Josh Leeb-du Toit](mailto:mail@joshleeb.com) +* [Josh McKinney](mailto:joshka@users.noreply.github.com) * [Josh Soref](mailto:2119212+jsoref@users.noreply.github.com) -* [Joshua Wood](mailto:josh@joshuawood.net) +* [João Pedro Marques](mailto:64037198+thedevjoao@users.noreply.github.com) +* [Juan Xavier Gomez](mailto:jgomez@codecademy.com) * [Julien](mailto:tiwy57@users.noreply.github.com) * [Julien Deswaef](mailto:juego@requiem4tv.com) +* [Jullan-M](mailto:42940512+jullan-m@users.noreply.github.com) * [June Sallou](mailto:jnsll@users.noreply.github.com) +* [Justin Hutchings](mailto:jhutchings1@users.noreply.github.com) * [Justin Thomas](mailto:justin@jdt.io) * [Jérémy Benoist](mailto:j0k3r@users.noreply.github.com) * [KEINOS](mailto:github@keinos.com) +* [Kai](mailto:2644614+schweinepriester@users.noreply.github.com) * [Kairui Song | 宋恺睿](mailto:ryncsn@gmail.com) * [Keiji Matsuzaki](mailto:futoase@gmail.com) * [Kevin Liu](mailto:kevin@potatofrom.space) * [Kit Redgrave](mailto:qwertyitis@gmail.com) * [Knut Erik](mailto:abjectio@users.noreply.github.com) +* [Kohei Ota (inductor)](mailto:kela@inductor.me) * [Kota Ouchi](mailto:k0ta0uchi@gmail.com) * [Krzysztof Jurewicz](mailto:krzysztof.jurewicz@gmail.com) +* [Kuba Suder](mailto:mackuba@users.noreply.github.com) * [Leo Wzukw](mailto:leowzukw@users.noreply.github.com) * [Leonie](mailto:62470640+bubblineyuri@users.noreply.github.com) * [Lex Alexander](mailto:l.alexander10@gmail.com) @@ -558,12 +650,17 @@ and provided thanks to the work of the following contributors: * [Lorenz Diener](mailto:lorenzd@gmail.com) * [Luc Didry](mailto:ldidry@users.noreply.github.com) * [Lukas Burk](mailto:jemus42@users.noreply.github.com) +* [Lukas Martini](mailto:lutoma@ohai.su) +* [Luxiaba](mailto:5391976+luxiaba@users.noreply.github.com) * [Manato Kameya](mailto:grabacr07+github@gmail.com) * [Mantas](mailto:mistermantas@users.noreply.github.com) * [Mareena Kunjachan](mailto:mareenakunjachan@gmail.com) * [Marek Lach](mailto:marek.brohatwack.lach@gmail.com) +* [Mark Roszko](mailto:mark.roszko@gmail.com) * [Markus Petzsch](mailto:markus@petzsch.eu) * [Markus R](mailto:wirehack7@users.noreply.github.com) +* [Markus Unterwaditzer](mailto:markus-honeypot@unterwaditzer.net) +* [Markus Unterwaditzer](mailto:markus@unterwaditzer.net) * [Marty McGuire](mailto:schmartissimo@gmail.com) * [Marvin Kopf](mailto:marvinkopf@posteo.de) * [Masafumi Otsune](mailto:info@otsune.com) @@ -571,21 +668,24 @@ and provided thanks to the work of the following contributors: * [Mateusz Bugowski](mailto:23140767+mbugowski@users.noreply.github.com) * [Mathias B](mailto:10813340+mathias-b@users.noreply.github.com) * [Mathieu Brunot](mailto:mb.mathieu.brunot@gmail.com) +* [Matias Lago Evia](mailto:matiaslagoevia@gmail.com) * [Matt](mailto:matt-auckland@users.noreply.github.com) * [Matt Corallo](mailto:649246+thebluematt@users.noreply.github.com) +* [Matt Hodges](mailto:hodgesmr1@gmail.com) * [Matt Sweetman](mailto:webroo@gmail.com) +* [Matt Williams](mailto:matt@makeable.co.uk) * [Matthias Bethke](mailto:matthias@towiski.de) * [Matthias Beyer](mailto:mail@beyermatthias.de) * [Matthias Jouan](mailto:matthias.jouan@gmail.com) * [Matthieu Paret](mailto:matthieuparet69@gmail.com) +* [Matthías Páll Gissurarson](mailto:mpg@mpg.is) * [Maxime BORGES](mailto:maxime.borges@gmail.com) -* [Mayu Laierlence](mailto:minacle@live.com) -* [Meisam](mailto:39205857+mftabriz@users.noreply.github.com) * [Michael Deeb](mailto:michaeldeeb@me.com) * [Michael Vieira](mailto:dtox94@gmail.com) * [Michel](mailto:michel@cyweo.com) * [Midgard](mailto:m1dgard@users.noreply.github.com) * [Mike Burns](mailto:mburns@thoughtbot.com) +* [Mikhail Paulyshka](mailto:me@mixaill.net) * [Milan](mailto:me@petabyteboy.de) * [Milan*](mailto:tchncs@vivaldi.net) * [Milton Mazzarri](mailto:milmazz@gmail.com) @@ -602,9 +702,12 @@ and provided thanks to the work of the following contributors: * [Nanamachi](mailto:town7.haruki@gmail.com) * [Nathaniel Ekoniak](mailto:nekoniak@ennate.tech) * [NecroTechno](mailto:necrotechno@riseup.net) +* [Neil Matatall](mailto:448516+oreoshake@users.noreply.github.com) * [Nicholas La Roux](mailto:larouxn@gmail.com) * [Nick Gerakines](mailto:nick@gerakines.net) +* [Nicolai Søborg](mailto:nicolaisoeborg@users.noreply.github.com) * [Nicolai von Neudeck](mailto:nicolai@vonneudeck.com) +* [Nikita Karamov](mailto:me@kytta.dev) * [Ninetailed](mailto:ninetailed@gmail.com) * [Nishi, Keisuke](mailto:k24@users.noreply.github.com) * [Noiob](mailto:noiob@users.noreply.github.com) @@ -616,34 +719,49 @@ and provided thanks to the work of the following contributors: * [Oskari Noppa](mailto:noppa@users.noreply.github.com) * [Otakan](mailto:otakan951@gmail.com) * [Padraig Fahy](mailto:tech@padraigfahy.com) +* [Partho Ghosh](mailto:partho.ghosh24@gmail.com) * [Patrice Ferlet](mailto:metal3d@gmail.com) * [PatrickRWells](mailto:32802366+patrickrwells@users.noreply.github.com) * [Paul](mailto:naydex.mc+github@gmail.com) +* [PauloVilarinho](mailto:33267902+paulovilarinho@users.noreply.github.com) * [Pete Keen](mailto:pete@petekeen.net) * [Pierre Bourdon](mailto:delroth@gmail.com) * [Pierre-Morgan Gate](mailto:pgate@users.noreply.github.com) +* [Plastikmensch](mailto:plastikmensch@users.noreply.github.com) +* [Pleclown](mailto:pleclown+github@gmail.com) +* [Ramūns Usovs](mailto:ramuuns@enkurs.org) * [Ratmir Karabut](mailto:rkarabut@sfmodern.ru) * [Reto Kromer](mailto:retokromer@users.noreply.github.com) +* [Riedler](mailto:github@riedler.wien) +* [Rin](mailto:36845451+ateliersnek@users.noreply.github.com) * [Rob Petti](mailto:rob.petti@gmail.com) +* [Rob Thomas](mailto:xrobau@gmail.com) * [Rob Watson](mailto:rfwatson@users.noreply.github.com) * [Robert Laurenz](mailto:8169746+laurenzcodes@users.noreply.github.com) +* [Rodion Borisov](mailto:vintprox@gmail.com) * [Rohan Sharma](mailto:i.am.lone.survivor@protonmail.com) * [Roni Laukkarinen](mailto:roni@laukkarinen.info) +* [Rose](mailto:83477269+ataridreams@users.noreply.github.com) +* [Rubicon Rowe](mailto:thislight@users.noreply.github.com) * [Ryan Freebern](mailto:ryan@freebern.org) * [Ryan Wade](mailto:ryan.wade@protonmail.com) * [Ryo Kajiwara](mailto:kfe-fecn6.prussian@s01.info) -* [S.H](mailto:gamelinks007@gmail.com) * [SJang1](mailto:git@sjang.dev) * [Sadiq Saif](mailto:staticsafe@users.noreply.github.com) +* [Sai](mailto:github@saizai.com) * [Sam Hewitt](mailto:hewittsamuel@gmail.com) +* [Samruddhi Khandale](mailto:samruddhikhandale@github.com) * [Samuel Kaiser](mailto:sk22@mailbox.org) +* [Santiago Kozak](mailto:santikzk1406@gmail.com) * [Sara Aimée Smiseth](mailto:51710585+sarasmiseth@users.noreply.github.com) * [Sara Golemon](mailto:pollita@php.net) * [Satoshi KOJIMA](mailto:skoji@mac.com) * [ScienJus](mailto:i@scienjus.com) * [Scott Larkin](mailto:scott@codeclimate.com) * [Scott Sweeny](mailto:scott@ssweeny.net) +* [Sean](mailto:64788907+seano-vs@users.noreply.github.com) * [Sean](mailto:sean@sean.taipei) +* [Sean Whalen](mailto:44679+seanthegeek@users.noreply.github.com) * [Sebastian Hübner](mailto:imolein@users.noreply.github.com) * [Sebastian Morr](mailto:sebastian@morr.cc) * [Sergei Č](mailto:noiwex1911@gmail.com) @@ -652,14 +770,19 @@ and provided thanks to the work of the following contributors: * [Shin Adachi](mailto:shn@glucose.jp) * [Shin Kojima](mailto:shin@kojima.org) * [Shouko Yu](mailto:imshouko@gmail.com) +* [Simon Elvery](mailto:simon@elvery.net) * [Sina Mashek](mailto:sina@mashek.xyz) +* [Skyler Hawthorne](mailto:skyler@dead10ck.com) * [Soft. Dev](mailto:24978+nileshkumar@users.noreply.github.com) * [Sophie Parker](mailto:dev@cortices.me) * [Soshi Kato](mailto:mail@sossii.com) * [Spanky](mailto:2788886+spankyworks@users.noreply.github.com) +* [Stan Hu](mailto:stanhu@gmail.com) * [Stanislas](mailto:stanislas.lange@pm.me) +* [Stanislav Dobrovolschii](mailto:uusername@protonmail.ch) * [StefOfficiel](mailto:pichard.stephane@free.fr) * [Stefano Pigozzi](mailto:ste.pigozzi@gmail.com) +* [Steven Munn](mailto:stevenjlm@users.noreply.github.com) * [Steven Tappert](mailto:admin@dark-it.net) * [Stéphane Guillou](mailto:stephane.guillou@member.fsf.org) * [Su Yang](mailto:soulteary@users.noreply.github.com) @@ -672,11 +795,14 @@ and provided thanks to the work of the following contributors: * [TakesxiSximada](mailto:takesxi.sximada@gmail.com) * [Tao Bror Bojlén](mailto:brortao@users.noreply.github.com) * [Taras Gogol](mailto:taras2358@gmail.com) +* [Terry Garcia](mailto:10190993+terrygarcia@users.noreply.github.com) * [The Stranjer](mailto:791672+thestranjer@users.noreply.github.com) * [TheInventrix](mailto:theinventrix@users.noreply.github.com) * [TheMainOne](mailto:50847364+theevilskeleton@users.noreply.github.com) +* [Thijs Kinkhorst](mailto:thijs@kinkhorst.com) * [Thomas Alberola](mailto:thomas@needacoffee.fr) * [Thomas Citharel](mailto:github@tcit.fr) +* [Tim Lucas](mailto:t@toolmantim.com) * [Toby Deshane](mailto:fortyseven@users.noreply.github.com) * [Toby Pinder](mailto:gigitrix@gmail.com) * [Tomonori Murakami](mailto:crosslife777@gmail.com) @@ -684,13 +810,14 @@ and provided thanks to the work of the following contributors: * [Tony Jiang](mailto:yujiang99@gmail.com) * [Treyssat-Vincent Nino](mailto:treyssatvincent@users.noreply.github.com) * [Truong Nguyen](mailto:truongnmt.dev@gmail.com) +* [Tyler Deitz](mailto:tylerdeitz@gmail.com) * [Udo Kramer](mailto:optik@fluffel.io) * [Una](mailto:una@unascribed.com) * [Ushitora Anqou](mailto:ushitora@anqou.net) * [Ushitora Anqou](mailto:ushitora_anqou@yahoo.co.jp) * [Valentin Lorentz](mailto:progval+git@progval.net) +* [Varun Sharma](mailto:varun999sharma@gmail.com) * [Vladimir Mincev](mailto:vladimir@canicinteractive.com) -* [Vyr Cossont](mailto:vyrcossont@users.noreply.github.com) * [Waldir Pimenta](mailto:waldyrious@gmail.com) * [Wenceslao Páez Chávez](mailto:wcpaez@gmail.com) * [Wesley Ellis](mailto:tahnok@gmail.com) @@ -714,8 +841,11 @@ and provided thanks to the work of the following contributors: * [Zach Neill](mailto:neillz@berea.edu) * [Zachary Spector](mailto:logicaldash@gmail.com) * [ZiiX](mailto:ziix@users.noreply.github.com) -* [asria-jp](mailto:is@alicematic.com) +* [aaaaalbert](mailto:aaaaalbert@users.noreply.github.com) +* [afontenot](mailto:adam.m.fontenot@gmail.com) +* [alfe](mailto:alfe10251+github@gmail.com) * [ava](mailto:vladooku@users.noreply.github.com) +* [awea](mailto:aweaoftheworld@gmail.com) * [benklop](mailto:benklop@gmail.com) * [bobbyd0g](mailto:93697464+bobbyd0g@users.noreply.github.com) * [bsky](mailto:git@imbsky.net) @@ -727,10 +857,10 @@ and provided thanks to the work of the following contributors: * [d0p1](mailto:dopi-sama@hush.com) * [dxwc](mailto:dxwc@users.noreply.github.com) * [eai04191](mailto:eai@mizle.net) +* [eggplants](mailto:w10776e8w@yahoo.co.jp) * [evilny0](mailto:evilny0@moomoocamp.net) * [febrezo](mailto:felixbrezo@gmail.com) * [fsubal](mailto:fsubal@users.noreply.github.com) -* [fusagiko / takayamaki](mailto:24884114+takayamaki@users.noreply.github.com) * [fusshi-](mailto:dikky1218@users.noreply.github.com) * [gentaro](mailto:gentaroooo@gmail.com) * [guigeekz](mailto:pattusg@gmail.com) @@ -753,20 +883,27 @@ and provided thanks to the work of the following contributors: * [kedama](mailto:32974885+kedamadq@users.noreply.github.com) * [keiya](mailto:keiya_21@yahoo.co.jp) * [kuro5hin](mailto:rusty@kuro5hin.org) +* [kyori19](mailto:kyori@accelf.net) +* [lenore gilbert](mailto:lenore@lenoregilbert.net) * [leo60228](mailto:leo@60228.dev) * [matildepark](mailto:matilde.park@pm.me) * [maxypy](mailto:maxime@mpigou.fr) * [mhe](mailto:mail@marcus-herrmann.com) +* [mhkhung](mailto:mhkhung@gmail.com) * [mickkael](mailto:19755421+mickkael@users.noreply.github.com) * [mike castleman](mailto:m@mlcastle.net) * [mimikun](mailto:dzdzble_effort_311@outlook.jp) * [mohemohe](mailto:mohemohe@users.noreply.github.com) +* [mon1kasenpai](mailto:ballaticseal@gmail.com) * [mshrtkch](mailto:mshrtkch@users.noreply.github.com) * [muan](mailto:muan@github.com) +* [n0toose](mailto:git@n0toose.net) * [namelessGonbai](mailto:43787036+namelessgonbai@users.noreply.github.com) * [neetshin](mailto:neetshin@neetsh.in) +* [nemobis](mailto:federicoleva@tiscali.it) * [notozeki](mailto:notozeki@users.noreply.github.com) * [ntl-purism](mailto:57806346+ntl-purism@users.noreply.github.com) +* [nyura123dev](mailto:58617294+nyura123dev@users.noreply.github.com) * [nzws](mailto:git-yuzu@svk.jp) * [pea-sys](mailto:49807271+pea-sys@users.noreply.github.com) * [potpro](mailto:pptppctt@gmail.com) @@ -775,6 +912,7 @@ and provided thanks to the work of the following contributors: * [rcombs](mailto:rcombs@rcombs.me) * [roikale](mailto:roikale@users.noreply.github.com) * [rysiekpl](mailto:rysiek@hackerspace.pl) +* [s0](mailto:s0@s0.is) * [sasanquaneuf](mailto:sasanquaneuf@gmail.com) * [saturday06](mailto:dyob@lunaport.net) * [scd31](mailto:57571338+scd31@users.noreply.github.com) @@ -794,9 +932,13 @@ and provided thanks to the work of the following contributors: * [vpzomtrrfrt](mailto:vpzomtrrfrt@gmail.com) * [walfie](mailto:walfington@gmail.com) * [y-temp4](mailto:y.temp4@gmail.com) +* [y.takahashi](mailto:eai@mizle.net) +* [ymmtmdk](mailto:ymmtmdk@gmail.com) * [ymmtmdk](mailto:ymmtmdk@gmail.com) * [yoshipc](mailto:yoooo@yoshipc.net) +* [yufushiro](mailto:62991447+yufushiro@users.noreply.github.com) * [Özcan Zafer AYAN](mailto:ozcanzaferayan@gmail.com) +* [наб](mailto:nabijaczleweli@nabijaczleweli.xyz) * [ばん](mailto:detteiu0321@gmail.com) * [ふるふる](mailto:frfs@users.noreply.github.com) * [りんすき](mailto:6533808+rinsuki@users.noreply.github.com) @@ -815,951 +957,933 @@ This document is provided for informational purposes only. Since it is only upda Following people have contributed to translation of Mastodon: - GunChleoc (*Scottish Gaelic*) -- ケインツロ ⚧️👾🛸 (KNTRO) (*Spanish, Argentina*) -- Hồ Nhất Duy (honhatduy) (*Vietnamese*) -- Sveinn í Felli (sveinki) (*Icelandic*) -- Kristaps (Kristaps_M) (*Latvian*) +- KNTRO (*Spanish, Argentina*) +- honhatduy (*Vietnamese*) +- sveinki (*Icelandic*) +- Kristaps_M (*Latvian*) - NCAA (*Danish, French*) -- Zoltán Gera (gerazo) (*Hungarian*) -- ghose (XoseM) (*Galician, Spanish*) -- Jeong Arm (Kjwon15) (*Korean, Esperanto, Japanese, Spanish*) -- Emanuel Pina (emanuelpina) (*Portuguese*) +- gerazo (*Hungarian*) +- ghose (*Galician, Spanish*) +- Kjwon15 (*Esperanto, Japanese, Korean, Spanish*) +- emanuelpina (*Portuguese*) - Reyzadren (*Ido, Malay*) -- Thai Localization (thl10n) (*Thai*) +- thl10n (*Thai*) - Besnik_b (*Albanian*) -- Joene (joenepraat) (*Dutch*) -- Cyax (Cyaxares) (*Kurmanji (Kurdish)*) +- joenepraat (*Dutch*) +- Cyaxares (*Kurmanji (Kurdish)*) - adrmzz (*Sardinian*) -- Ramdziana F Y (rafeyu) (*Indonesian*) +- rafeyu (*Indonesian*) - xatier (*Chinese Traditional, Chinese Traditional, Hong Kong*) -- qezwan (*Sorani (Kurdish), Persian*) +- qezwan (*Persian, Sorani (Kurdish)*) - spla (*Catalan, Spanish*) -- ButterflyOfFire (BoFFire) (*Arabic, French, Kabyle*) -- Martin (miles) (*Slovenian*) -- නාමල් ජයසිංහ (nimnaya) (*Sinhala*) -- Asier Iturralde Sarasola (aldatsa) (*Basque*) -- Ondřej Pokorný (unextro) (*Czech*) +- BoFFire (*Arabic, French, Kabyle*) +- miles (*Slovenian*) +- nimnaya (*Sinhala*) +- aldatsa (*Basque*) +- unextro (*Czech*) - Roboron (*Spanish*) - taicv (*Vietnamese*) - koyu (*German*) -- Daniele Lira Mereb (danilmereb) (*Portuguese, Brazilian*) -- T. E. Kalaycı (tekrei) (*Turkish*) -- Evert Prants (IcyDiamond) (*Estonian*) -- Yair Mahalalel (yairm) (*Hebrew*) -- Ihor Hordiichuk (ihor_ck) (*Ukrainian*) -- Alessandro Levati (Oct326) (*Italian*) -- Kimmo Kujansuu (mrkujansuu) (*Finnish*) -- Alix Rossi (palindromordnilap) (*Corsican, Esperanto, French*) -- Danial Behzadi (danialbehzadi) (*Persian*) -- stan ionut (stanionut12) (*Romanian*) -- Mastodon 中文译者 (mastodon-linguist) (*Chinese Simplified*) -- Kristijan Tkalec (lapor) (*Slovenian*) -Alexander Sorokin (Brawaru) (*Russian, Vietnamese, Swedish, Portuguese, Tamil, Kabyle, Polish, Italian, Catalan, Armenian, Hungarian, Albanian, Greek, Galician, Korean, Ukrainian, German, Danish, French*) +- danilmereb (*Portuguese, Brazilian*) +- tekrei (*Turkish*) +- IcyDiamond (*Estonian*) +- yairm (*Hebrew*) +- ihor_ck (*Ukrainian*) +- Oct326 (*Italian*) +- mrkujansuu (*Finnish*) +- palindromordnilap (*Corsican, Esperanto, French*) +- danialbehzadi (*Persian*) +- stanionut12 (*Romanian*) +- mastodon-linguist (*Chinese Simplified*) +- lapor (*Slovenian*) +- Brawaru (*Albanian, Armenian, Catalan, Danish, French, Galician, German, Greek, Hungarian, Italian, Kabyle, Korean, Polish, Portuguese, Russian, Swedish, Tamil, Ukrainian, Vietnamese*) - ManeraKai (*Arabic*) -- мачко (ma4ko) (*Bulgarian*) +- ma4ko (*Bulgarian*) +- Rhoslyn (*Welsh*) - kamee (*Armenian*) -- Yamagishi Kazutoshi (ykzts) (*Japanese, Icelandic, Sorani (Kurdish), Albanian, Vietnamese, Chinese Simplified*) -- Takeçi (polygoat) (*French, Italian*) +- ykzts (*Albanian, Chinese Simplified, Icelandic, Japanese, Sorani (Kurdish), Vietnamese*) +- polygoat (*French, Italian*) - REMOVED_USER (*Czech*) - borys_sh (*Ukrainian*) -- Imre Kristoffer Eilertsen (DandelionSprout) (*Norwegian*) -- Marek Ľach (mareklach) (*Slovak, Polish*) -- yeft (*Chinese Traditional, Hong Kong, Chinese Traditional*) -- D. Cederberg (cederberget) (*Swedish*) -- Miguel Mayol (mitcoes) (*Spanish, Catalan*) +- DandelionSprout (*Norwegian*) +- mareklach (*Polish, Slovak*) +- yeft (*Chinese Traditional, Chinese Traditional, Hong Kong*) +- cederberget (*Swedish*) +- mitcoes (*Catalan, Spanish*) - enolp (*Asturian*) -- Manuel Viens (manuelviens) (*French*) +- manuelviens (*French*) - cybergene (*Japanese*) - REMOVED_USER (*Turkish*) - xpil (*Polish, Scottish Gaelic*) -- Balázs Meskó (mesko.balazs) (*Hungarian, Czech*) -- Koala Yeung (yookoala) (*Chinese Traditional, Hong Kong*) +- mesko.balazs (*Czech, Hungarian*) +- yookoala (*Chinese Traditional, Hong Kong*) - Osoitz (*Basque*) -- Amir Rubinstein - TAU (AmirrTAU) (*Hebrew, Indonesian*) -- Maya Minatsuki (mayaeh) (*Japanese*) -- Peterandre (*Norwegian Nynorsk, Norwegian*) -Mélanie Chauvel (ariasuni) (*French, Esperanto, Norwegian Nynorsk, Persian, Kabyle, Sardinian, Corsican, Breton, Portuguese, Brazilian, Arabic, Chinese Simplified, Ukrainian, Slovenian, Greek, German, Czech, Hungarian*) +- AmirrTAU (*Hebrew, Indonesian*) +- mayaeh (*Japanese*) +- Peterandre (*Norwegian, Norwegian Nynorsk*) +- ariasuni (*Arabic, Breton, Chinese Simplified, Corsican, Czech, Esperanto, French, German, Greek, Hungarian, Kabyle, Norwegian Nynorsk, Persian, Portuguese, Brazilian, Sardinian, Slovenian, Ukrainian*) - tzium (*Sardinian*) - Diluns (*Occitan*) -- Galician Translator (Galician_translator) (*Galician*) -- Marcin Mikołajczak (mkljczkk) (*Polish, Czech, Russian*) -- Jeff Huang (s8321414) (*Chinese Traditional*) -- Pixelcode (realpixelcode) (*German*) -- Allen Zhong (AstroProfundis) (*Chinese Simplified*) +- REMOVED_USER (*Galician*) +- mkljczkk (*Czech, Polish, Russian*) +- s8321414 (*Chinese Traditional*) +- realpixelcode (*German*) - lamnatos (*Greek*) -- Sean Young (assanges) (*Chinese Traditional*) +- AstroProfundis (*Chinese Simplified*) +- assanges (*Chinese Traditional*) - retiolus (*Catalan, French, Spanish*) - tolstoevsky (*Russian*) -- Ali Demirtaş (alidemirtas) (*Turkish*) -- J. Cam Andrever-Wright (gourmas) (*Cornish*) +- alidemirtas (*Turkish*) +- gourmas (*Cornish*) - coxde (*Chinese Simplified*) - Dremski (*Bulgarian*) - gagik_ (*Armenian*) -- Masoud Abkenar (mabkenar) (*Persian*) +- mabkenar (*Persian*) - arshat (*Kazakh*) -- Ira (seefood) (*Hebrew*) +- seefood (*Hebrew*) - Linerly (*Indonesian*) -- Blak Ouille (BlakOuille16) (*French*) -- e (diveedd) (*Kurmanji (Kurdish)*) -- Em St Cenydd (cancennau) (*Welsh*) -- Tigran (tigransimonyan) (*Armenian*) +- BlakOuille16 (*French*) +- diveedd (*Kurmanji (Kurdish)*) +- cancennau (*Welsh*) +- lisawe (*Norwegian*) +- tigransimonyan (*Armenian*) - Draacoun (*Portuguese, Brazilian*) -- REMOVED_USER (*Turkish*) -- Nurul Azeera Hidayah @ Muhammad Nur Hidayat Yasuyoshi (MNH48.moe) (mnh48) (*Malay*) -- Tagomago (tagomago) (*Spanish, French*) -- Ashun (ashune) (*Croatian*) +- mnh48 (*Malay*) +- tagomago (*French, Spanish*) +- ashune (*Croatian*) - Aditoo17 (*Czech*) - vishnuvaratharajan (*Tamil*) - pulmonarycosignerkindness (*Swedish*) - calypsoopenmail (*French*) - REMOVED_USER (*Kabyle*) - snerk (*Norwegian Nynorsk*) -- Sebastian (SebastianBerlin) (*German*) -- lisawe (*Norwegian*) +- TranslatorDE (*German*) - serratrad (*Catalan*) - Bran_Ruz (*Breton*) -- ViktorOn (*Russian, Danish*) +- ViktorOn (*Danish, Russian*) - Gearguy (*Finnish*) -- Andi Chandler (andibing) (*English, United Kingdom*) -- Tor Egil Hoftun Kvæstad (Taloran) (*Norwegian Nynorsk*) +- andibing (*English, United Kingdom*) +- Taloran (*Norwegian Nynorsk*) - GiorgioHerbie (*Italian*) -- හෙළබස සමූහය (HelaBasa) (*Sinhala*) -- kat (katktv) (*Ukrainian, Russian*) -- Yi-Jyun Pan (pan93412) (*Chinese Traditional*) -- Fjoerfoks (fryskefirefox) (*Frisian, Dutch*) -- Eshagh (eshagh79) (*Persian*) +- HelaBasa (*Sinhala*) +- philip-khor (*English, United Kingdom, Malay*) +- katktv (*Russian, Ukrainian*) +- pan93412 (*Chinese Traditional*) +- fryskefirefox (*Dutch, Frisian*) +- eshagh79 (*Persian*) - regulartranslator (*Portuguese, Brazilian*) - Saederup92 (*Danish*) -- ozzii (Serbian (Cyrillic), French) -- Irfan (Irfan_Radz) (*Malay*) +- ozzii (*French, Serbian (Cyrillic)*) +- Irfan_Radz (*Malay*) - ClearlyClaire (*French, Icelandic*) -- Sokratis Alichanidis (alichani) (*Greek*) -- Jiří Podhorecký (trendspotter) (*Czech*) -- Akarshan Biswas (biswasab) (*Bengali, Sanskrit*) -- Robert Wolniak (Szkodnix) (*Polish*) -- Jan Lindblom (janlindblom) (*Swedish*) -- Dewi (Unkorneg) (*Breton, French*) -- Kristoffer Grundström (Umeaboy) (*Swedish*) -- Rafael H L Moretti (Moretti) (*Portuguese, Brazilian*) +- alichani (*Greek*) +- trendspotter (*Czech*) +- biswasab (*Bengali, Sanskrit*) +- Szkodnix (*Polish*) +- janlindblom (*Swedish*) +- Unkorneg (*Breton, French*) +- Umeaboy (*Swedish*) +- Moretti (*Portuguese, Brazilian*) - d5Ziif3K (*Ukrainian*) -- Nemu (Dormemulo) (*Esperanto, French, Italian, Ido, Afrikaans*) -- Johan Mynhardt (johanmynhardt) (*Afrikaans*) -- Rojdayek (Kurmanji (Kurdish)) +- Dormemulo (*Afrikaans, Esperanto, French, Ido, Italian*) +- johanmynhardt (*Afrikaans*) +- Rojdayek (*Kurmanji (Kurdish)*) - REMOVED_USER (*Portuguese, Brazilian*) - GCardo (*Portuguese, Brazilian*) - christalleras (*Norwegian Nynorsk*) - diorama (*Italian*) -- Jaz-Michael King (jazmichaelking) (*Welsh*) -- Catalina (catalina.st) (*Romanian*) -- Ryo (DrRyo) (*Korean*) -- otrapersona (*Spanish, Mexico, Spanish*) -- Frontier Translation Ltd. (frontier-translation) (*Chinese Simplified*) -- Mauzi (*Swedish, German*) +- jazmichaelking (*Welsh*) +- catalina.st (*Romanian*) +- DrRyo (*Korean*) +- otrapersona (*Spanish, Spanish, Mexico*) +- frontier-translation (*Chinese Simplified*) +- Mauzi (*German, Swedish*) - Clopsy87 (*Italian*) - atarashiako (*Chinese Simplified*) +- 101010pl (*Polish*) - erictapen (*German*) -- zhen liao (az0189re) (*Chinese Simplified*) -- 101010 (101010pl) (*Polish*) +- az0189re (*Chinese Simplified*) - REMOVED_USER (*Norwegian*) - axi (*Finnish*) - silkevicious (*Italian*) -- Floxu (fredrikdim1) (*Norwegian Nynorsk*) -- Nic Dafis (nicdafis) (*Welsh*) -- NadieAishi (*Spanish, Mexico, Spanish*) -- 戸渡生野 (aomyouza2543) (*Thai*) -- Tjipke van der Heide (vancha) (*Frisian*) -- Erik Mogensen (mogsie) (*Norwegian*) +- nicdafis (*Welsh*) +- fredrikdim1 (*Norwegian Nynorsk*) +- NadieAishi (*Spanish, Spanish, Mexico*) +- aomyouza2543 (*Thai*) +- vancha (*Frisian*) +- mogsie (*Norwegian*) - pomoch (*Chinese Traditional, Hong Kong*) -- Alexandre Brito (alexbrito) (*Portuguese, Brazilian*) -- Bertil Hedkvist (Berrahed) (*Swedish*) -- William(ѕ)ⁿ (wmlgr) (*Spanish*) +- alexbrito (*Portuguese, Brazilian*) +- Berrahed (*Swedish*) +- wmlgr (*Spanish*) - LNDDYL (*Chinese Traditional*) - tanketom (*Norwegian Nynorsk*) - norayr (*Armenian*) -- l3ycle (*German*) - strubbl (*German*) -- Satnam S Virdi (pika10singh) (*Punjabi*) -- Tiago Epifânio (tfve) (*Portuguese*) -- Mentor Gashi (mentorgashi.com) (*Albanian*) -- Sid (autinerd1) (*Dutch, German*) +- l3ycle (*German*) +- pika10singh (*Punjabi*) +- tfve (*Portuguese*) +- mentorgashi.com (*Albanian*) +- autinerd1 (*Dutch, German*) - carolinagiorno (*Portuguese, Brazilian*) -- Em_i (emiliencoss) (*French*) -- Liam O (liamoshan) (*Irish*) -- Hayk Khachatryan (brutusromanus123) (*Armenian*) -- Roby Thomas (roby.thomas) (*Malayalam*) +- emiliencoss (*French*) +- liamoshan (*Irish*) +- Gim_Garam (*Korean*) +- brutusromanus123 (*Armenian*) +- roby.thomas (*Malayalam*) - ThonyVezbe (*Breton*) -- Percy (kecrily) (*Chinese Simplified*) -- Bharat Kumar (Marwari) (*Hindi*) -- Austra Muizniece (aus_m) (*Latvian*) -- Urubu Lageano (urubulageano) (*Portuguese, Brazilian*) -- Just Spanish (7_7) (*Spanish, Mexico*) +- kecrily (*Chinese Simplified*) +- Marwari (*Hindi*) +- aus_m (*Latvian*) +- urubulageano (*Portuguese, Brazilian*) +- 7_7 (*Spanish, Mexico*) - v4vachan (*Malayalam*) - bilfri (*Danish*) -- IamHappy (mrmx2013) (*Ukrainian*) +- mrmx2013 (*Ukrainian*) - dkdarshan760 (*Sanskrit*) -- Timur Seber (seber) (*Tatar*) -- Slimane Selyan AMIRI (SelyanKab) (*Kabyle*) +- seber (*Tatar*) +- SelyanKab (*Kabyle*) - VaiTon (*Italian*) - tykayn (*French*) -- Abdulaziz Aljaber (kuwaitna) (*Arabic*) +- kuwaitna (*Arabic*) - taoxvx (*Danish*) -- Hrach Mkrtchyan (hrachmk) (*Armenian*) -- sabri (thetomatoisavegetable) (*Spanish, Spanish, Argentina*) -- CoelacanthusHex (*Chinese Simplified*) -- Rhys Harrison (rhedders) (*Esperanto*) -- syncopams (*Chinese Traditional, Hong Kong, Chinese Traditional, Chinese Simplified*) +- hrachmk (*Armenian*) +- thetomatoisavegetable (*Spanish, Spanish, Argentina*) +- Coelacanthus (*Chinese Simplified*) +- rhedders (*Esperanto*) +- syncopams (*Chinese Simplified, Chinese Traditional, Chinese Traditional, Hong Kong*) - SteinarK (*Norwegian Nynorsk*) +- vagnes (*Norwegian, Norwegian Nynorsk*) - REMOVED_USER (*Standard Moroccan Tamazight*) -- Maxine B. Vågnes (vagnes) (*Norwegian, Norwegian Nynorsk*) -- Rikard Linde (rikardlinde) (*Swedish*) +- rikardlinde (*Swedish*) - ahangarha (*Persian*) -- Lalo Tafolla (lalotafo) (*Spanish, Spanish, Mexico*) -- Larissa Cruz (larissacruz) (*Portuguese, Brazilian*) -- dashersyed (Urdu (Pakistan)) +- lalotafo (*Spanish, Spanish, Mexico*) +- larissacruz (*Portuguese, Brazilian*) +- dashersyed (*Urdu (Pakistan)*) - camerongreer21 (*English, United Kingdom*) - REMOVED_USER (*Ukrainian*) -- Conight Wang (xfddwhh) (*Chinese Simplified*) +- xfddwhh (*Chinese Simplified*) - liffon (*Swedish*) -- Damjan Dimitrioski (gnud) (*Macedonian*) +- gnud (*Macedonian*) - rondnunes (*Portuguese, Brazilian*) - PPNplus (*Thai*) -- Steven Ritchie (Steaph38) (*Scottish Gaelic*) -- 游荡 (MamaShip) (*Chinese Simplified*) -- Edward Navarro (EdwardNavarro) (*Spanish*) +- Steaph38 (*Scottish Gaelic*) +- MamaShip (*Chinese Simplified*) +- EdwardNavarro (*Spanish*) - shioko (*Chinese Simplified*) - gnu-ewm (*Polish*) -- Kahina Mess (K_hina) (*Kabyle*) -- Hexandcube (hexandcube) (*Polish*) -- Scott Starkey (yekrats) (*Esperanto*) +- K_hina (*Kabyle*) +- hexandcube (*Polish*) +- yekrats (*Esperanto*) - ZiriSut (*Kabyle*) - FreddyG (*Esperanto*) -- mynameismonkey (*Welsh*) -- Groosha (groosha) (*Russian*) -- Gwenn (Belvar) (*Breton*) +- jmking (*Welsh*) +- groosha (*Russian*) +- toba (*German*) +- Belvar (*Breton*) - StanleyFrew (*French*) - cathalgarvey (*Irish*) -- Nikita Epifanov (Nikets) (*Russian*) +- Nikets (*Russian*) - REMOVED_USER (*Finnish*) - jaranta (*Finnish*) -- Slobodan Simić (Слободан Симић) (slsimic) (*Serbian (Cyrillic)*) -- iVampireSP (*Chinese Traditional, Chinese Simplified*) -- Felicia Jongleur (midsommar) (*Swedish*) -- Denys (dector) (*Ukrainian*) -- Mo_der Steven (SakuraPuare) (*Chinese Simplified*) +- slsimic (*Serbian (Cyrillic)*) +- iVampireSP (*Chinese Simplified, Chinese Traditional*) +- midsommar (*Swedish*) +- dector (*Ukrainian*) +- SakuraPuare (*Chinese Simplified*) - REMOVED_USER (*German*) -- Kishin Sagume (kishinsagi) (*Chinese Simplified*) +- kishinsagi (*Chinese Simplified*) - bennepharaoh (*Chinese Simplified*) - Vanege (*Esperanto*) -- hibiya inemuri (hibiya) (*Korean*) -- Jess Rafn (therealyez) (*Danish*) -- Stasiek Michalski (hellcp) (*Polish*) +- hibiya (*Korean*) +- therealyez (*Danish*) +- hellcp (*Polish*) - dxwc (*Bengali*) -- Heran Membingung (heranmembingung) (*Indonesian*) - Parodper (*Galician*) +- filbert (*Indonesian*) - rbnval (*Catalan*) +- jmontane (*Catalan*) - Liboide (*Spanish*) - hemnaren (*Norwegian Nynorsk*) -- jmontane (*Catalan*) -- Andy Kleinert (AndyKl) (*German*) -- Chris Kay (chriskarasoulis) (*Greek*) +- AndyKl (*German*) +- Acursen (*German*) +- chriskarasoulis (*Greek*) - CrowdinBRUH (*Vietnamese*) -- Rhoslyn Prys (Rhoslyn) (*Welsh*) -- abidin toumi (Zet24) (*Arabic*) -- Johan Schiff (schyffel) (*Swedish*) -- Rex_sa (rex07) (*Arabic*) +- Zet24 (*Arabic*) +- schyffel (*Swedish*) +- rex07 (*Arabic*) - amedcj (*Kurmanji (Kurdish)*) -- Arunmozhi (tecoholic) (*Tamil*) -- zer0-x (ZER0-X) (*Arabic*) +- tecoholic (*Tamil*) +- zer0-x (*Arabic*) - staticnoisexyz (*Czech*) -- Lauren Liberda (selfisekai) (*Polish*) -- Michael Zeevi (maze88) (*Hebrew*) +- cuu508 (*Latvian*) +- selfisekai (*Polish*) +- maze88 (*Hebrew*) - oti4500 (*Hungarian, Ukrainian*) -- Delta (Delta-Time) (*Japanese*) -- Marc Antoine Thevenet (MATsxm) (*French*) -- AlexKoala (alexkoala) (*Korean*) +- Delta-Time (*Japanese*) +- MATsxm (*French*) +- alexkoala (*Korean*) - SarfarazAhmed (*Urdu (Pakistan)*) -- Ahmad Dakhlallah (MIUIArabia) (*Arabic*) -- Mats Gunnar Ahlqvist (goqbi) (*Swedish*) +- ahmadafef (*Arabic*) +- goqbi (*Swedish*) - diazepan (*Spanish, Spanish, Argentina*) -- Tiger:blank (tsagaanbar) (*Chinese Simplified*) -- REMOVED_USER (*Chinese Simplified*) +- tsagaanbar (*Chinese Simplified*) - marzuquccen (*Kabyle*) +- REMOVED_USER (*Chinese Simplified*) - atriix (*Swedish*) -- Laur (melaur) (*Romanian*) -- VictorCorreia (victorcorreia1984) (*Afrikaans*) -- Remito (remitocat) (*Japanese*) -- Juan José Salvador Piedra (JuanjoSalvador) (*Spanish*) -- REMOVED_USER (*Norwegian*) -- 森の子リスのミーコの大冒険 (Phroneris) (*Japanese*) -- Gim_Garam (*Korean*) +- melaur (*Romanian*) +- victorcorreia1984 (*Afrikaans*) +- remitocat (*Japanese*) +- JuanjoSalvador (*Spanish*) +- Phroneris (*Japanese*) - BurekzFinezt (*Serbian (Cyrillic)*) -- Pēteris Caune (cuu508) (*Latvian*) +- lancet (*Irish*) - asnomgtu (*Hungarian*) - bendigeidfran (*Welsh*) - SHeija (*Finnish*) -- Врабац (Slovorad) (*Serbian (Cyrillic)*) -- Dženan (Dzenan) (*Swedish*) -- Gabriel Beecham (lancet) (*Irish*) +- Dzenan (*Swedish*) +- Slovorad (*Serbian (Cyrillic)*) +- isaac.97_WT (*Spanish*) - antonyho (*Chinese Traditional, Hong Kong*) -- Jack R (isaac.97_WT) (*Spanish*) -- Henrik Mattsson-Mårn (rchk) (*Swedish*) -- Oguzhan Aydin (aoguzhan) (*Turkish*) -- Soran730 (*Chinese Simplified*) -- andruhov (*Ukrainian, Russian*) -- 北䑓如法 (Nyoho) (*Japanese*) +- rchk (*Swedish*) +- aoguzhan (*Turkish*) +- andruhov (*Russian, Ukrainian*) +- Nyoho (*Japanese*) - phena109 (*Chinese Traditional, Hong Kong*) -- Aryamik Sharma (Aryamik) (*Hindi, Swedish*) +- Aryamik (*Hindi, Swedish*) - Unmual (*Spanish*) -- Tobias Bannert (toba) (*German*) -- Adrián Graña (alaris83) (*Spanish*) +- agrana (*Spanish*) - vpei (*Chinese Simplified*) - cruz2020 (*Portuguese*) -- papapep (h9f2ycHh-ktOd6_Y) (*Catalan*) -- Roj (roj1512) (*Sorani (Kurdish), Kurmanji (Kurdish)*) -- るいーね (ruine) (*Japanese*) -- aujawindar (*Norwegian Nynorsk*) +- h9f2ycHh-ktOd6_Y (*Catalan*) +- roj1512 (*Kurmanji (Kurdish), Sorani (Kurdish)*) +- ruine (*Japanese*) - irithys (*Chinese Simplified*) -- Sam Tux (imahbub) (*Bengali*) +- aujawindar (*Norwegian Nynorsk*) +- imahbub (*Bengali*) - igordrozniak (*Polish*) -- Johannes Nilsson (nlssn) (*Swedish*) -- Michał Sidor (michcioperz) (*Polish*) -- Isaac Huang (caasih) (*Chinese Traditional*) -- AW Unad (awcodify) (*Indonesian*) +- nlssn (*Swedish*) +- michcioperz (*Polish*) +- caasih (*Chinese Traditional*) +- stromholm (*Swedish*) +- awcodify (*Indonesian*) - 1Alino (*Slovak*) -- Cutls (cutls) (*Japanese*) -- Goudarz Jafari (GoudarzJafari) (*Persian*) -- Daniel Strömholm (stromholm) (*Swedish*) -- 1 (Ipsumry) (*Spanish*) -- Falling Snowdin (tghgg) (*Vietnamese*) -- Paulino Michelazzo (pmichelazzo) (*Portuguese, Brazilian*) -- Y.Yamashiro (uist1idrju3i) (*Japanese*) -- Rasmus Lindroth (RasmusLindroth) (*Swedish*) -- Gianfranco Fronteddu (gianfro.gianfro) (*Sardinian*) -- Andrea Lo Iacono (niels0n) (*Italian*) +- cutls (*Japanese*) +- GoudarzJafari (*Persian*) +- Ipsumry (*Spanish*) +- tghgg (*Vietnamese*) +- pmichelazzo (*Portuguese, Brazilian*) +- uist1idrju3i (*Japanese*) +- RasmusLindroth (*Swedish*) +- gianfro.gianfro (*Sardinian*) +- niels0n (*Italian*) - fucsia (*Italian*) -- Vedran Serbu (SerenoXGen) (*Croatian*) -- Raphael Das Gupta (das-g) (*Esperanto, German*) +- SerenoXGen (*Croatian*) +- das-g (*Esperanto, German*) - yanchan09 (*Estonian*) - ainmeolai (*Irish*) -- REMOVED_USER (*Norwegian*) +- kinshuksunil (*Hindi*) - mian42 (*Bulgarian*) -- Kinshuk Sunil (kinshuksunil) (*Hindi*) +- ullasjoseph (*Malayalam*) - al_._ (*German, Russian*) -- Ullas Joseph (ullasjoseph) (*Malayalam*) - sanoth (*Swedish*) -- Aftab Alam (iaftabalam) (*Hindi*) +- iaftabalam (*Hindi*) - frumble (*German*) -- juanda097 (juanda-097) (*Spanish*) -- Matthías Páll Gissurarson (icetritlo) (*Icelandic*) -- Russian Retro (retrograde) (*Russian*) +- juanda-097 (*Spanish*) +- icetritlo (*Icelandic*) +- retrograde (*Russian*) +- tedliou (*Chinese Traditional*) - KcKcZi (*Chinese Simplified*) -- Yu-Pai Liu (tedliou) (*Chinese Traditional*) -- Amarin Cemthong (acitmaster) (*Thai*) +- acitmaster (*Thai*) - Etinew (*Hebrew*) - xsml (*Chinese Simplified*) -- S.J. L. (samijuhanilii) (*Finnish*) +- samijuhanilii (*Finnish*) - Anunnakey (*Macedonian*) - erikkemp (*Dutch*) -- Tsl (muun) (*Chinese Simplified*) -- Renato "Lond" Cerqueira (renatolond) (*Portuguese, Brazilian*) -- Úna-Minh Kavanagh (yunitex) (*Irish*) +- renatolond (*Portuguese, Brazilian*) +- muun (*Chinese Simplified*) +- yunitex (*Irish*) - kongk (*Norwegian Nynorsk*) - erikstl (*Esperanto*) - twpenguin (*Chinese Traditional*) +- bobchao (*Chinese Traditional*) - JeremyStarTM (*German*) -- Po-chiang Chao (bobchao) (*Chinese Traditional*) -- Marcus Myge (mygg-priv) (*Norwegian*) -- Esther (esthermations) (*Portuguese*) -- Jiri Grönroos (spammemoreplease) (*Finnish*) +- IetsMooi (*Norwegian*) - MadeInSteak (*Finnish*) +- esthermations (*Portuguese*) +- spammemoreplease (*Finnish*) - witoharmuth (*Swedish*) -- MESHAL45 (*Arabic*) - mcdutchie (*Dutch*) -- Michal Špondr (michalspondr) (*Czech*) +- MESHAL45 (*Arabic*) +- michalspondr (*Czech*) - t_aus_m (*German*) -- kaki7777 (*Japanese, Chinese Traditional*) -- Heimen Stoffels (Vistaus) (*Dutch*) -- serapolis (*Chinese Traditional, Hong Kong, Chinese Traditional, Japanese, Chinese Simplified*) -- Rajarshi Guha (rajarshiguha) (*Bengali*) -- Amir Reza (ElAmir) (*Persian*) -- REMOVED_USER (*Norwegian*) -- MohammadSaleh Kamyab (mskf1383) (*Persian*) +- Vistaus (*Dutch*) +- serapolis (*Chinese Simplified, Chinese Traditional, Chinese Traditional, Hong Kong, Japanese*) +- kaki7777 (*Chinese Traditional, Japanese*) +- rajarshiguha (*Bengali*) +- ElAmir (*Persian*) - REMOVED_USER (*Romanian*) -- Gopal Sharma (gopalvirat) (*Hindi*) -- Вероніка Някшу (pampushkaveronica) (*Russian, Romanian*) -- Linnéa (lesbian_subnet) (*Swedish*) -- Valentin (HDValentin) (*German*) +- mskf1383 (*Persian*) +- gopalvirat (*Hindi*) +- lesbian_subnet (*Swedish*) +- pampushkaveronica (*Romanian, Russian*) +- HDValentin (*German*) - dragnucs2 (*Arabic*) -- Carlos Solís (csolisr) (*Esperanto*) -- Tofiq Abdula (Xwla) (*Sorani (Kurdish)*) +- csolisr (*Esperanto*) +- Xwla (*Sorani (Kurdish)*) - halcek (*Slovak*) -- Tobias Kunze (rixxian) (*German*) -- Parthan S Ramanujam (parthan) (*Tamil*) -- Kasper Nymand (KasperNymand) (*Danish*) -- TS (morte) (*Finnish*) -- REMOVED_USER (*German*) +- parthan (*Tamil*) +- rixxian (*German*) +- KasperNymand (*Danish*) - REMOVED_USER (*Basque*) +- morte (*Finnish*) - subram (*Turkish*) -- Gudwin (*Spanish, Mexico, Spanish*) -- Ptrcmd (ptrcmd) (*Chinese Traditional*) -- shmuelHal (*Hebrew*) +- Gudwin (*Spanish, Spanish, Mexico*) - SensDeViata (*Ukrainian*) +- ptrcmd (*Chinese Traditional*) +- shmuelHal (*Hebrew*) - megaleo (*Portuguese, Brazilian*) -- Acursen (*German*) -- NurKai Kai (nurkaiyttv) (*German*) -- Guttorm (ghveem) (*Norwegian Nynorsk*) +- nurkaiyttv (*German*) - SergioFMiranda (*Portuguese, Brazilian*) -- Danni Lundgren (dannilundgren) (*Danish*) -- Vivek K J (Vivekkj) (*Malayalam*) +- ghveem (*Norwegian Nynorsk*) +- dannilundgren (*Danish*) - hiroTS (*Chinese Traditional*) -- teadesu (*Portuguese, Brazilian*) +- Vivekkj (*Malayalam*) +- fnogcps (*Portuguese, Brazilian*) - petartrajkov (*Macedonian*) -- Ariel Costas (arielcostas3) (*Galician*) -- Ch. (sftblw) (*Korean*) +- arielcostas (*Galician*) +- sftblw (*Korean*) - Rintan (*Japanese*) -- Jair Henrique (jairhenrique) (*Portuguese, Brazilian*) - sorcun (*Turkish*) +- jairhenrique (*Portuguese, Brazilian*) - filippodb (*Italian*) - johne32rus23 (*Russian*) -- OctolinGamer (octolingamer) (*Portuguese, Brazilian*) +- octolingamer (*Portuguese, Brazilian*) - AzureNya (*Chinese Simplified*) -- Ram varma (ram4varma) (*Tamil*) -- REMOVED_USER (Sorani (Kurdish)) -- REMOVED_USER (*Portuguese, Brazilian*) +- ram4varma (*Tamil*) +- REMOVED_USER (*Sorani (Kurdish)*) - seanmhade (*Irish*) - sanser (*Russian*) -- Vijay (vijayatmin) (*Tamil*) +- vijayatmin (*Tamil*) - Anomalion (*German*) -- Pukima (Pukimaa) (*German*) -- Curtis Lee (CansCurtis) (*Chinese Traditional*) -- โบโลน่าไวรัส (nullxyz_) (*Thai*) -- ふぁーらんど (farland1717) (*Japanese*) +- Pukimaa (*German*) +- nullxyz_ (*Thai*) +- CansCurtis (*Chinese Traditional*) +- farland1717 (*Japanese*) - 3wen (*Breton*) +- rahmatullinailzira53 (*Tatar*) - rlafuente (*Portuguese*) -- Ильзира Рахматуллина (rahmatullinailzira53) (*Tatar*) -- Code Man (codemansrc) (*Russian*) -- Philip Gillißen (guerda) (*German*) -- Daniel Dimitrov (daniel.dimitrov) (*Bulgarian*) -- Anton (atjn) (*Danish*) +- codemansrc (*Russian*) +- guerda (*German*) +- daniel.dimitrov (*Bulgarian*) +- atjn (*Danish*) - kekkepikkuni (*Tamil*) - MODcraft (*Chinese Simplified*) - oorsutri (*Tamil*) +- NeoChen1024 (*Chinese Traditional*) - wortfeld (*German*) -- Neo_Chen (NeoChen1024) (*Chinese Traditional*) -- Stereopolex (*Polish*) - NxOne14 (*Bulgarian*) -- Juan Ortiz (Kloido) (*Spanish, Catalan*) -- Nithin V (Nithin896) (*Tamil*) +- Stereopolex (*Polish*) +- Kloido (*Catalan, Spanish*) +- Nithin896 (*Tamil*) - strikeCunny2245 (*Icelandic*) -- Miro Rauhala (mirorauhala) (*Finnish*) -- nicoduesing (duconi) (*German, Esperanto*) -- Gnonthgol (*Norwegian Nynorsk*) -- WKobes (*Dutch*) +- mirorauhala (*Finnish*) +- duconi (*Esperanto, German*) - Oymate (*Bengali*) +- WKobes (*Dutch*) +- Gnonthgol (*Norwegian Nynorsk*) +- EzigboOmenana (*Cornish, Igbo*) - mikwee (*Hebrew*) -- EzigboOmenana (*Igbo, Cornish*) -- yan Wato (janWato) (*Hindi*) +- janWato (*Hindi*) - Papuass (*Latvian*) -- Vincent Orback (vincentorback) (*Swedish*) +- vincentorback (*Swedish*) +- nineteen (*Chinese Simplified*) - chettoy (*Chinese Simplified*) -- 19 (nineteen) (*Chinese Simplified*) -- ಚಿರಾಗ್ ನಟರಾಜ್ (chiraag-nataraj) (*Kannada*) -- Layik Hama (layik) (*Sorani (Kurdish)*) -- Guillaume Turchini (orion78fr) (*French*) -- Andri Yngvason (andryng) (*Icelandic*) -- Aswin C (officialcjunior) (*Malayalam*) -- Yuval Nehemia (yuvalne) (*Hebrew*) -- mawoka-myblock (mawoka) (*German*) -- Ganesh D (auntgd) (*Marathi*) -- Lens0021 (lens0021) (*Korean*) -- An Gafraíoch (angafraioch) (*Irish*) -- Michael Smith (michaelshmitty) (*Dutch*) -- Ryan Ho (koungho) (*Chinese Traditional*) +- chiraag-nataraj (*Kannada*) +- layik (*Sorani (Kurdish)*) +- orion78fr (*French*) +- officialcjunior (*Malayalam*) +- andryng (*Icelandic*) +- auntgd (*Marathi*) +- mawoka (*German*) +- yuvalne (*Hebrew*) +- lens0021 (*Korean*) +- angafraioch (*Irish*) +- koungho (*Chinese Traditional*) +- michaelshmitty (*Dutch*) - tunisiano187 (*French*) -- Peter van Mever (SpacemanSpiff) (*Dutch*) -- Pedro Henrique (exploronauta) (*Portuguese, Brazilian*) +- h_tejas (*Marathi*) +- meskobalazs (*Hungarian*) +- exploronauta (*Portuguese, Brazilian*) - REMOVED_USER (*Esperanto, Italian, Japanese*) -- Tejas Harad (h_tejas) (*Marathi*) -- Balázs Meskó (meskobalazs) (*Hungarian*) -- Vasanthan (vasanthan) (*Tamil*) -- Tatsuto "Laminne" Yamamoto (laminne) (*Japanese*) -- slbtty (shenlebantongying) (*Chinese Simplified*) -- 硫酸鶏 (acid_chicken) (*Japanese*) +- SpacemanSpiff (*Dutch*) +- vasanthan (*Tamil*) +- laminne (*Japanese*) +- shenlebantongying (*Chinese Simplified*) +- acid_chicken (*Japanese*) +- clarminb8 (*Sorani (Kurdish)*) - programizer (*German*) - guessimmaterialgrl (*Chinese Simplified*) -- clarmin b8 (clarminb8) (*Sorani (Kurdish)*) -- Maria Riegler (riegler3m) (*German*) - manukp (*Malayalam*) -- earth dweller (sanethoughtyt) (*Marathi*) +- riegler3m (*German*) +- sanethoughtyt (*Marathi*) - psymyn (*Hebrew*) -- Aaraon Thomas (aaraon) (*Portuguese, Brazilian*) -- Rafael Viana (rafacnec) (*Portuguese, Brazilian*) -- Marek Ľach (marek-lach) (*Slovak*) -- meijerivoi (toilet) (*Finnish*) +- aaraon (*Portuguese, Brazilian*) +- toilet (*Finnish*) +- marek-lach (*Slovak*) +- rafacnec (*Portuguese, Brazilian*) +- GenialMeg (*Spanish*) - essaar (*Tamil*) - serubeena (*Swedish*) - RqndomHax (*French*) - REMOVED_USER (*Polish*) -- ギャラ (gyara) (*Chinese Simplified, Japanese*) -- Khó͘ Tiatlêng (khotiatleng) (*Chinese Traditional, Taigi*) -- revarioba (*Spanish*) -- friedbeans (*Croatian*) -- An (AnTheMaker) (*German*) -- kuchengrab (*German*) -- Hernik (hernik27) (*Czech*) +- gyara (*Chinese Simplified, Japanese*) - valarivan (*Tamil*) -- אדם לוין (adamlevin) (*Hebrew*) -- Vít Horčička (legvita123) (*Czech*) -- Abi Turi (abi123) (*Georgian*) -- Thomas Munkholt (munkholt) (*Danish*) +- khotiatleng (*Chinese Traditional, Taigi*) +- hernik27 (*Czech*) +- kuchengrab (*German*) +- friedbeans (*Croatian*) +- revarioba (*Spanish*) +- AnTheMaker (*German*) +- adamlevin (*Hebrew*) +- abi123 (*Georgian*) +- munkholt (*Danish*) - pparescasellas (*Catalan*) -- Hinaloe (hinaloe) (*Japanese*) +- hinaloe (*Japanese*) +- Selrond (*Slovak*) - Ifnuth (*German*) -- Sebastián Andil (Selrond) (*Slovak*) -- boni777 (*Chinese Simplified*) +- ddgulledge (*Esperanto*) - KEINOS (*Japanese*) -- Asbjørn Olling (a2) (*Danish*) +- a2 (*Danish*) +- boni777 (*Chinese Simplified*) - REMOVED_USER (*Chinese Traditional, Hong Kong*) -- DarkShy Community (ponyfrost.mc) (*Russian*) -- Dennis Reimund (reimunddennis7) (*German*) +- reimunddennis7 (*German*) +- ponyfrost.mc (*Russian*) - jocafeli (*Spanish, Mexico*) -- Wrya ali (John12) (*Sorani (Kurdish)*) -- Bottle (suryasalem2010) (*Tamil*) -- Algustionesa Yoshi (algustionesa) (*Indonesian*) - JzshAC (*Chinese Simplified*) -- Artem Mikhalitsin (artemmikhalitsin) (*Russian*) -- siamano (*Thai, Esperanto*) -- KARARTI44 (kararti44) (*Turkish*) +- suryasalem2010 (*Tamil*) +- John12 (*Sorani (Kurdish)*) +- algustionesa (*Indonesian*) +- artemmikhalitsin (*Russian*) +- mbootsman (*Dutch*) +- siamano (*Esperanto, Thai*) +- kararti44 (*Turkish*) - c0c (*Irish*) -- Stefano S. (Sting1_JP) (*Italian*) +- Sting1_JP (*Italian*) +- sammy8806 (*German*) +- antillion99 (*Spanish*) +- ilis (*Galician*) - tommil (*Finnish*) -- Ignacio Lis (ilis) (*Galician*) -- Steven Tappert (sammy8806) (*German*) -- Antillion (antillion99) (*Spanish*) -- K.B.Dharun Krishna (kbdharun) (*Tamil*) -- Wassim EL BOUHAMIDI (elbouhamidiw) (*Arabic*) - Reg3xp (*Persian*) +- elbouhamidiw (*Arabic*) +- kbdharun (*Tamil*) +- mble (*Polish*) +- Exbu (*Dutch*) - florentVgn (*French*) -- Matt (Exbu) (*Dutch*) -- Maciej Błędkowski (mble) (*Polish*) -- gowthamanb (*Tamil*) - hiphipvargas (*Portuguese*) +- gowthamanb (*Tamil*) - GabuVictor (*Portuguese, Brazilian*) +- REMOVED_USER (*Spanish*) - Pverte (*French*) -- REMOVED_USER (*Spanish*) - Surindaku (*Chinese Simplified*) -- Arttu Ylhävuori (arttu.ylhavuori) (*Finnish*) -- Pabllo Soares (pabllosoarez) (*Portuguese, Brazilian*) -- Jona (88wcJoWl) (*Spanish*) -- Ka2n (kaanmetu) (*Turkish*) +- arttu.ylhavuori (*Finnish*) +- samiti3d (*Thai*) - tctovsli (*Norwegian Nynorsk*) -- Timo Tijhof (Krinkle) (*Dutch*) -- SamitiMed (samiti3d) (*Thai*) -- Mikkel B. Goldschmidt (mikkelbjoern) (*Danish*) -- Odyssey346 (alexader612) (*Norwegian*) -- mecqor labi (mecqorlabi) (*Persian*) -- Cù Huy Phúc Khang (taamee) (*Vietnamese*) -- Oskari Lavinto (olavinto) (*Finnish*) -- Philippe Lemaire (philippe-lemaire) (*Esperanto*) +- Krinkle (*Dutch*) +- mikkelbjoern (*Danish*) +- kaanmetu (*Turkish*) +- pabllosoarez (*Portuguese, Brazilian*) +- mecqorlabi (*Persian*) - vjasiegd (*Polish*) -- Eban (ebanDev) (*Esperanto, French*) -- Nícolas Lavinicki (nclavinicki) (*Portuguese, Brazilian*) -- REMOVED_USER (*Portuguese, Brazilian*) -- Rekan Adl (rekan-adl1) (*Sorani (Kurdish)*) -- VSx86 (*Russian*) +- ebanDev (*Esperanto, French*) +- philippe-lemaire (*Esperanto*) +- olavinto (*Finnish*) +- taamee (*Vietnamese*) +- nclavinicki (*Portuguese, Brazilian*) +- rekan-adl1 (*Sorani (Kurdish)*) - umelard (*Hebrew*) -- Antara2Cinta (Se7enTime) (*Indonesian*) +- Se7enTime (*Indonesian*) +- VSx86 (*Russian*) +- yaitelmouden (*Standard Moroccan Tamazight*) - Lucas_NL (*Dutch*) -- Yassine Aït-El-Mouden (yaitelmouden) (*Standard Moroccan Tamazight*) -- Mathieu Marquer (slasherfun) (*French*) -- Haerul Fuad (Dokuwiki) (*Indonesian*) +- Dokuwiki (*Indonesian*) +- slasherfun (*French*) - parnikkapore (*Thai*) -- Michelle M (MichelleMMM) (*Dutch*) +- MichelleMMM (*Dutch*) +- sherwanothman11 (*Sorani (Kurdish)*) +- lagash (*Esperanto*) - malbona (*Esperanto*) -- Sherwan Othman (sherwanothman11) (*Sorani (Kurdish)*) -- Lagash (lagash) (*Esperanto*) -- Chine Sebastien (chine.sebastien) (*French*) -- bgme (*Chinese Simplified*) -- Rafael V. (Rafaeeel) (*Portuguese, Brazilian*) - SKELET (*Danish*) -- A A (sebastien.chine) (*French*) -- Project Z (projectz.1338) (*German*) -- Fei Yang (Fei1Yang) (*Chinese Traditional*) -- Ğani (freegnu) (*Tatar*) -- musix (*Persian*) -- REMOVED_USER (*German*) -- ALEM FARID (faridatcemlulaqbayli) (*Kabyle*) -- Jean-Pierre MÉRESSE (Jipem) (*French*) +- chine.sebastien (*French*) +- bgme (*Chinese Simplified*) +- Rafaael (*Portuguese, Brazilian*) +- Fei1Yang (*Chinese Traditional*) +- freegnu (*Tatar*) +- sebastien.chine (*French*) +- projectz.1338 (*German*) - enipra (*Armenian*) -- Serhiy Dmytryshyn (dies) (*Ukrainian*) -- Eric Brulatout (ebrulato) (*Esperanto*) -- Hougo (hougo) (*French*) +- faridatcemlulaqbayli (*Kabyle*) +- musix (*Persian*) +- Jipem (*French*) +- hougo (*French*) +- dies (*Ukrainian*) +- djprmf (*Portuguese*) - Sonstwer (*German*) -- Pedro Fernandes (djprmf) (*Portuguese*) -- REMOVED_USER (*Norwegian*) -- Tigran's Tips (tigrank08) (*Armenian*) -- 亜緯丹穂 (ayiniho) (*Japanese*) -- maisui (*Chinese Simplified*) -- Trinsec (*Dutch*) -- Adrián Lattes (haztecaso) (*Spanish*) -- webkinzfrog (*Polish*) +- ebrulato (*Esperanto*) +- haztecaso (*Spanish*) - ybardapurkar (*Marathi*) -- Mordi Sacks (MordiSacks) (*Hebrew*) -- Manuel Tassi (Mannivu) (*Italian*) -- Szabolcs Gál (galszabolcs810624) (*Hungarian*) -- rikrise (*Swedish*) -- when_hurts (*German*) -- Wojciech Bigosinski (wbigos2) (*Polish*) -- Vladislav S (vladislavs) (*Romanian*) -- mikslatvis (*Latvian*) -- MartinAlstad (*Norwegian*) +- MordiSacks (*Hebrew*) +- ayiniho (*Japanese*) +- tigrank08 (*Armenian*) +- Trinsec (*Dutch*) +- webkinzfrog (*Polish*) +- Mannivu (*Italian*) +- maisui (*Chinese Simplified*) - TracyJacks (*Chinese Simplified*) +- galszabolcs810624 (*Hungarian*) +- vladislavs (*Romanian*) +- rikrise (*Swedish*) +- MartinAlstad (*Norwegian*) +- when_hurts (*German*) +- wbigos2 (*Polish*) +- mikslatvis (*Latvian*) - rasheedgm (*Kannada*) -- Cirelli (cirelli94) (*Italian*) - danreznik (*Hebrew*) +- cirelli94 (*Italian*) - iraline (*Portuguese, Brazilian*) -- Seán Mór (seanmor3) (*Irish*) +- seanmor3 (*Irish*) +- sidharastro (*Spanish, Mexico*) - vianaweb (*Portuguese, Brazilian*) -- Siddharastro Doraku (sidharastro) (*Spanish, Mexico*) -- REMOVED_USER (*Spanish*) +- nspeaks (*Hindi*) +- belkacem77 (*Kabyle*) - omquylzu (*Latvian*) -- Arthegor (*French*) -- Navjot Singh (nspeaks) (*Hindi*) - mkljczk (*Polish*) -- Belkacem Mohammed (belkacem77) (*Kabyle*) +- c6ristian (*German*) +- lexxai (*Ukrainian*) - Showfom (*Chinese Simplified*) - xemyst (*Catalan*) -- lexxai (*Ukrainian*) -- c6ristian (*German*) -- svetlozaurus (*Bulgarian*) +- Arthegor (*French*) +- petrosyan (*Armenian*) - Ozai (*German*) +- MetehanOzyurek (*Turkish*) - damascene (*Arabic*) -- Jan Ainali (Ainali) (*Swedish*) -- Sahak Petrosyan (petrosyan) (*Armenian*) -- Metehan Özyürek (MetehanOzyurek) (*Turkish*) -- Сау Рэмсон (sawrams) (*Russian*) +- svetlozaurus (*Bulgarian*) +- Ainali (*Swedish*) +- rapiteanu (*Romanian*) +- sawrams (*Russian*) +- kscanne (*Irish*) +- sebastienserre (*French*) - metehan-arslan (*Turkish*) -- Viorel-Cătălin Răpițeanu (rapiteanu) (*Romanian*) -- Sébastien SERRE (sebastienserre) (*French*) -- Eugen Caruntu (eugencaruntu) (*Romanian*) -- Kevin Scannell (kscanne) (*Irish*) -- Pachara Chantawong (pachara2202) (*Thai*) -- bensch.dev (*German*) +- eugencaruntu (*Romanian*) +- quinoa_biryani (*Bengali*) +- pachara2202 (*Thai*) - LIZH (*French*) -- Siddhartha Sarathi Basu (quinoa_biryani) (*Bengali*) -- Overflow Cat (OverflowCat) (*Chinese Traditional, Chinese Simplified*) -- Stephan Voeth (svoeth) (*German*) -- Zijian Zhao (jobs2512821228) (*Chinese Simplified*) -- bugboy-20 (*Esperanto, Italian*) -- SouthFox (*Chinese Simplified*) -- Noan (SkewRam) (*French*) +- bensch.dev (*German*) +- SkewRam (*French*) +- jobs2512821228 (*Chinese Simplified*) - dbeaver (*German*) +- OverflowCat (*Chinese Simplified, Chinese Traditional*) +- svoeth (*German*) +- SouthFox (*Chinese Simplified*) +- bugboy-20 (*Esperanto, Italian*) +- guruprasath (*Tamil*) - turtle836 (*German*) -- Guru Prasath Anandapadmanaban (guruprasath) (*Tamil*) - zordsdavini (*Lithuanian*) -- Susanna Ånäs (susanna.anas) (*Finnish*) -- Alessandro (alephoto85) (*Italian*) -- Marcepanek_ (thekingmarcepan) (*Polish*) -- Choi Younsoo (usagicore) (*Korean*) -- Yann Aguettaz (yann-a) (*French*) -- zylosophe (*French*) -- Celso Fernandes (Celsof) (*Portuguese, Brazilian*) -- Feruz Oripov (FeruzOripov) (*Russian*) +- susanna.anas (*Finnish*) +- thekingmarcepan (*Polish*) +- alephoto85 (*Italian*) +- FeruzOripov (*Russian*) +- yann-a (*French*) +- usagicore (*Korean*) +- Celsof (*Portuguese, Brazilian*) - REMOVED_USER (*French*) -- Bui Huy Quang (bhuyquang1) (*Vietnamese*) +- zylosophe (*French*) +- bhuyquang1 (*Vietnamese*) - bogomilshopov (*Bulgarian*) +- kaedech (*Japanese*) +- xgc.redes (*Asturian*) - REMOVED_USER (*Burmese*) -- Kaede (kaedech) (*Japanese*) -- Mick Onio (xgc.redes) (*Asturian*) -- Malik Mann (dermalikmann) (*German*) +- dermalikmann (*German*) +- hg6 (*Hindi*) - padulafacundo (*Spanish*) +- tina.zhang040609 (*Chinese Simplified*) - r3dsp1 (*Chinese Traditional, Hong Kong*) - dadosch (*German*) -- Tianqi Zhang (tina.zhang040609) (*Chinese Simplified*) -- HybridGlucose (*Chinese Traditional*) - vmichalak (*French*) -- hg6 (*Hindi*) +- HybridGlucose (*Chinese Traditional*) - marivisales (*Portuguese, Brazilian*) -- Orlando Murcio (Atos20) (*Spanish, Mexico*) +- Atos20 (*Spanish, Mexico*) +- J0hsHH (*Norwegian*) - maa123 (*Japanese*) -- Julian Doser (julian21) (*English, United Kingdom, German*) -- johannes hove-henriksen (J0hsHH) (*Norwegian*) -- Alexander Ivanov (Saiv46) (*Russian*) -- unstable.icu (*Chinese Simplified*) -- Padraic Calpin (padraic-padraic) (*Slovenian*) -- Youngeon Lee (YoungeonLee) (*Korean*) -- LeJun (le-jun) (*French*) -- shdy (*German*) -- REMOVED_USER (*French*) -- Yonjae Lee (yonjlee) (*Korean*) +- julian21 (*English, United Kingdom, German*) - cenegd (*Chinese Simplified*) +- padraic-padraic (*Slovenian*) - piupiupiudiu (*Chinese Simplified*) -- Umi (mtrumi) (*Chinese Traditional, Hong Kong, Chinese Simplified*) -- Yogesh K S (yogi) (*Kannada*) +- shdy (*German*) +- mtrumi (*Chinese Simplified, Chinese Traditional, Hong Kong*) +- YoungeonLee (*Korean*) +- unstable.icu (*Chinese Simplified*) +- yonjlee (*Korean*) +- le-jun (*French*) +- Saiv46 (*Russian*) +- youloveonlymeh (*Chinese Simplified*) +- yogi (*Kannada*) +- adithyak04 (*Malayalam*) +- daijie (*Chinese Simplified*) +- milli.pretili (*Croatian*) - Ulong32 (*Japanese*) -- Adithya K (adithyak04) (*Malayalam*) -- DAI JIE (daijie) (*Chinese Simplified*) -- Mihael Budeč (milli.pretili) (*Croatian*) -- Hugh Liu (youloveonlymeh) (*Chinese Simplified*) +- rakino (*Chinese Simplified*) - ZQYD (*Chinese Simplified*) -- X.M (kimonoki) (*Chinese Simplified*) -- Rakino (rakino) (*Chinese Simplified*) -- paziy Georgi (paziygeorgi4) (*Dutch*) -- Komeil Parseh (mmdbalkhi) (*Persian*) -- Jothipazhani Nagarajan (jothipazhani.n) (*Tamil*) -- tikky9 (*Portuguese, Brazilian*) -- horsm (*Finnish*) -- BenJule (*German*) -- Stanisław Jelnicki (JelNiSlaw) (*Polish*) -- Yananas (wangyanyan.hy) (*Chinese Simplified*) -- Vivamus (elaaksu) (*Turkish*) -- ihealyou (*Italian*) +- kimonoki (*Chinese Simplified*) +- jothipazhani.n (*Tamil*) - AmazighNM (*Kabyle*) -- Miquel Sabaté Solà (mssola) (*Catalan*) +- mssola (*Catalan*) +- JelNiSlaw (*Polish*) +- BenJule (*German*) +- wangyanyan.hy (*Chinese Simplified*) - residuum (*German*) -- nua_kr (*Korean*) -- Andrea Mazzilli (andreamazzilli) (*Italian*) -- Paula SIMON (EncoreEutIlFalluQueJeLeSusse) (*French*) +- mmdbalkhi (*Persian*) +- paziygeorgi4 (*Dutch*) +- tikky9 (*Portuguese, Brazilian*) +- ihealyou (*Italian*) +- elaaksu (*Turkish*) +- horsm (*Finnish*) - hallomaurits (*Dutch*) -- Erfan Kheyrollahi Qaroğlu (ekm507) (*Persian*) - REMOVED_USER (*Galician, Spanish*) -- alnd hezh (alndhezh) (*Sorani (Kurdish)*) -- Clash Clans (KURD12345) (*Sorani (Kurdish)*) +- SolidRhino (*Dutch*) +- KURD12345 (*Sorani (Kurdish)*) +- alndhezh (*Sorani (Kurdish)*) +- nua_kr (*Korean*) +- EncoreEutIlFalluQueJeLeSusse (*French*) +- CloudSet (*Chinese Simplified*) - ruok (*Chinese Simplified*) - Frederik-FJ (*German*) -- CloudSet (*Chinese Simplified*) -- Solid Rhino (SolidRhino) (*Dutch*) +- andreamazzilli (*Italian*) +- ekm507 (*Persian*) +- noellabo (*Japanese*) - hussama (*Portuguese, Brazilian*) -- jazzynico (*French*) -- k_taka (peaceroad) (*Japanese*) -- 林水溶 (shuiRong) (*Chinese Simplified*) -- Peter Lutz (theellutzo) (*German*) -- Sébastien Feugère (smonff) (*French*) -- AnalGoddess770 (*Hebrew*) -- Sven Goller (svengoller) (*German*) -- Ahmet (ahmetlii) (*Turkish*) +- shuiRong (*Chinese Simplified*) +- smonff (*French*) +- peaceroad (*Japanese*) +- hallo_hamza12 (*Sorani (Kurdish)*) +- ahmetlii (*Turkish*) +- theellutzo (*German*) - hosted22 (*German*) -- Hallo Abdullah (hallo_hamza12) (*Sorani (Kurdish)*) -- Karam Hamada (TheKaram) (*Arabic*) -- Takeshi Umeda (noellabo) (*Japanese*) +- svengoller (*German*) +- TheKaram (*Arabic*) +- jazzynico (*French*) +- AnalGoddess770 (*Hebrew*) - SnDer (*Dutch*) -- Robert Yano (throwcalmbobaway) (*Spanish, Mexico*) -- Gustav Lindqvist (Reedyn) (*Swedish*) -- Dagur Ammendrup (dagurp) (*Icelandic*) -- shafouz (*Portuguese, Brazilian*) -- Miguel Branco (mglbranco) (*Galician*) -- Sergey Panteleev (saundefined) (*Russian*) -- Tom_ (*Czech*) -- Zlr- (cZeler) (*French*) -- Ashok314 (ashok314) (*Hindi*) -- PifyZ (*French*) -- Zeyi Fan (fanzeyi) (*Chinese Simplified*) -- OminousCry (*Russian, Ukrainian*) -- Adam Sapiński (Adamos9898) (*Polish*) - eichkat3r (*German*) -- Yasin İsa YILDIRIM (redsfyre) (*Turkish*) -- Tagada (Tagadda) (*French*) +- PifyZ (*French*) +- OminousCry (*Russian, Ukrainian*) +- shafouz (*Portuguese, Brazilian*) +- Tom_ (*Czech*) +- Tagadda (*French*) +- ashok314 (*Hindi*) +- cZeler (*French*) +- Iriep (*Breton*) +- throwcalmbobaway (*Spanish, Mexico*) +- redsfyre (*Turkish*) +- Adamos9898 (*Polish*) +- Reedyn (*Swedish*) +- saundefined (*Russian*) +- fanzeyi (*Chinese Simplified*) +- mglbranco (*Galician*) +- dagurp (*Icelandic*) - gasrios (*Portuguese, Brazilian*) -- 夜楓Yoka (Yoka2627) (*Chinese Simplified*) -- AniCommieDDR (*Russian*) -- Nathaël Noguès (NatNgs) (*French*) -- Daniel M. (daniconil) (*Catalan*) -- César Daniel Cavanzo Quintero (LeinadCQ) (*Esperanto*) -- Noam Tamim (noamtm) (*Hebrew*) +- saccharin23 (*Japanese*) +- tshrinivasan (*Tamil*) +- REMOVED_USER (*Urdu (Pakistan)*) +- kishorkumara3 (*Kannada*) +- swatisani (*Urdu (Pakistan)*) +- daniconil (*Catalan*) +- NatNgs (*French*) +- Yoka2627 (*Chinese Simplified*) - papayaisnotafood (*Chinese Traditional*) -- さっかりんにーさん (saccharin23) (*Japanese*) -- Marcin Wolski (martinwolski) (*Polish*) -- REMOVED_USER (*Chinese Simplified*) -- Kk (kishorkumara3) (*Kannada*) -- Shrinivasan T (tshrinivasan) (*Tamil*) -- REMOVED_USER (Urdu (Pakistan)) -- Kakarico Bra (kakarico20) (*Portuguese, Brazilian*) -- Swati Sani (swatisani) (*Urdu (Pakistan)*) -- 快乐的老鼠宝宝 (LaoShuBaby) (*Chinese Simplified, Chinese Traditional*) -- Mt Front (mtfront) (*Chinese Simplified*) -- SusVersiva (*Catalan*) -- REMOVED_USER (*Portuguese, Brazilian*) -- Avinash Mg (hatman290) (*Malayalam*) -- kruijs (*Dutch*) -- Artem (Artem4ik) (*Russian*) +- LeinadCQ (*Esperanto*) +- kakarico20 (*Portuguese, Brazilian*) +- AniCommieDDR (*Russian*) +- martinwolski (*Polish*) +- noamtm (*Hebrew*) +- tradjincal (*French*) - Zinkokooo (*Basque*) -- 劉昌賢 (twcctz500) (*Chinese Traditional*) - Vikatakavi (*Kannada*) -- Tradjincal (tradjincal) (*French*) -- Robin van der Vliet (RobinvanderVliet) (*Esperanto*) -- Marvin (magicmarvman) (*German*) +- SusVersiva (*Catalan*) +- RobinvanderVliet (*Esperanto*) +- Artem4ik (*Russian*) - pullopen (*Chinese Simplified*) -- Tealk (*German*) -- tibequadorian (*German*) -- Henk Bulder (henkbulder) (*Dutch*) -- Edison Lee (edisonlee55) (*Chinese Traditional*) -- mpdude (*German*) -- Rijk van Geijtenbeek (rvangeijtenbeek) (*Dutch*) -- Entelekheia-ousia (*Chinese Simplified*) -- REMOVED_USER (*Spanish*) -- sergioaraujo1 (*Portuguese, Brazilian*) -- Livingston Samuel (livingston) (*Tamil*) +- magicmarvman (*German*) +- mtfront (*Chinese Simplified*) +- twcctz500 (*Chinese Traditional*) +- LaoShuBaby (*Chinese Simplified, Chinese Traditional*) +- kruijs (*Dutch*) +- hatman290 (*Malayalam*) - mmokhi (*Persian*) +- sergioaraujo1 (*Portuguese, Brazilian*) - tsundoker (*Malayalam*) -- CyberAmoeba (pseudoobscura) (*Chinese Simplified*) - prabhjot (*Hindi*) -- Ikka Putri (ikka240290) (*Indonesian, Danish, English, United Kingdom*) -- Paz Galindo (paz.almendra.g) (*Spanish*) -- Ricardo Colin (rysard) (*Spanish*) -- Pierre Morvan (Iriep) (*Breton*) -- oscfd (*Spanish*) -- Thies Mueller (thies00) (*German*) -- Lyra (teromene) (*French*) -- Kedr (lava20121991) (*Esperanto*) -- mkljczk (mykylyjczyk) (*Polish*) +- livingston (*Tamil*) +- pseudoobscura (*Chinese Simplified*) +- Entelekheia-ousia (*Chinese Simplified*) +- tibequadorian (*German*) +- edisonlee55 (*Chinese Traditional*) +- Tealk (*German*) +- rvangeijtenbeek (*Dutch*) +- henkbulder (*Dutch*) +- mpdude (*German*) - fedot (*Russian*) -- Philipp Fischbeck (PFischbeck) (*German*) -- Hasan Berkay Çağır (berkaycagir) (*Turkish*) -- Silvestri Nicola (nick99silver) (*Italian*) - skaaarrr (*German*) -- Mo Rijndael (mo_rijndael) (*Russian*) -- tsesunnaallun (orezraey) (*Portuguese, Brazilian*) -- Lukas Fülling (lfuelling) (*German*) -- Algo (algovigura) (*Indonesian*) -- REMOVED_USER (*Spanish*) -- setthemfree (*Ukrainian*) -- i fly (ifly3years) (*Chinese Simplified*) -- ralozkolya (*Georgian*) -- Zoé Bőle (zoe1337) (*German*) -- Ville Rantanen (vrntnn) (*Finnish*) +- rysard (*Spanish*) +- paz.almendra.g (*Spanish*) +- mykylyjczyk (*Polish*) +- PFischbeck (*German*) +- berkaycagir (*Turkish*) +- thies00 (*German*) +- lava20121991 (*Esperanto*) +- nick99silver (*Italian*) +- teromene (*French*) +- ikka240290 (*Danish, English, United Kingdom, Indonesian*) +- Merman-Jack (*Chinese Simplified*) +- zoe1337 (*German*) +- lfuelling (*German*) +- REMOVED_USER (*Georgian*) - GaggiX (*Italian*) -- JackXu (Merman-Jack) (*Chinese Simplified*) -- ceonia (*Chinese Traditional, Hong Kong*) -- Emirhan Yavuz (takomlii) (*Turkish*) +- orezraey (*Portuguese, Brazilian*) - teezeh (*German*) -- MevLyshkin (Leinnan) (*Polish*) -- Apple (blackteaovo) (*Chinese Simplified*) -- qwerty287 (*German*) -- Tangcuyu (*Chinese Simplified*) +- takomlii (*Turkish*) +- ceonia (*Chinese Traditional, Hong Kong*) +- mo_rijndael (*Russian*) +- vrntnn (*Finnish*) +- ifly3years (*Chinese Simplified*) +- Leinnan (*Polish*) +- algovigura (*Indonesian*) +- setthemfree (*Ukrainian*) +- anoopp (*Malayalam*) +- samir_t7 (*Kabyle*) +- AymBroussier (*French*) +- albjeremias (*Portuguese*) - Nocta (*French*) -- ru_mactunnag (*Scottish Gaelic*) -- Lilian Nabati (Lilounab49) (*French*) -- lokalisoija (*Finnish*) -- Dennis Reimund (reimund_dennis) (*German*) -- ronee (*Kurmanji (Kurdish)*) -- EricVogt_ (*Spanish*) -- yu miao (metaxx.dev) (*Chinese Simplified*) -- Anoop (anoopp) (*Malayalam*) -- Samir Tighzert (samir_t7) (*Kabyle*) -- sn02 (*German*) -- Yui Karasuma (yui87) (*Japanese*) -- asala4544 (*Basque*) -- Thibaut Rousseau (thiht44) (*French*) -- Jason Gibson (barberpike606) (*Slovenian, Chinese Simplified*) -- Sugar NO (g1024116707) (*Chinese Simplified*) -- Aymeric (AymBroussier) (*French*) - pezcurrel (*Italian*) -- Xurxo Guerra (xguerrap) (*Galician*) -- nicosomb (*French*) -- Albatroz Jeremias (albjeremias) (*Portuguese*) -- María José Vera (mjverap) (*Spanish*) - mashirozx (*Chinese Simplified*) +- blackteaovo (*Chinese Simplified*) +- xguerrap (*Galician*) +- reimund_dennis (*German*) +- asala4544 (*Basque*) +- qwerty287 (*German*) +- ru_mactunnag (*Scottish Gaelic*) +- Lilounab49 (*French*) +- ronee (*Kurmanji (Kurdish)*) +- barberpike606 (*Chinese Simplified, Slovenian*) +- lokalisoija (*Finnish*) +- Tangcuyu (*Chinese Simplified*) - codl (*French*) -- Doug (douglasalvespe) (*Portuguese, Brazilian*) -- Matias Lavik (matiaslavik) (*Norwegian Nynorsk*) -- random_person (*Spanish*) -- whoeta (wh0eta) (*Russian*) -- xpac1985 (xpac) (*German*) -- thisdudeisvegan (braydofficial) (*German*) -- Fleva (*Sardinian*) -- Anonymous (Anonymous666) (*Russian*) -- Mohammad Adnan Mahmood (adnanmig) (*Arabic*) -- ÀŘǾŚ PÀŚĦÀÍ (arospashai) (*Sorani (Kurdish)*) -- mikel (mikelalas) (*Spanish*) -- Trond Boksasp (boksasp) (*Norwegian*) -- asretro (*Chinese Traditional, Hong Kong*) -- Holger Huo (holgerhuo) (*Chinese Simplified*) -- Aman Alam (aalam) (*Punjabi*) -- smedvedev (*Russian*) -- Jay Lonnquist (crowkeep) (*Japanese*) -- mimikun (*Japanese*) -- Mohd Bilal (mdb571) (*Malayalam*) -- veer66 (*Thai*) -- OpenAlgeria (*Arabic*) -- Rave (nayumi-464812844) (*Vietnamese*) -- ReavedNetwork (*German*) -- Michael (Discostu36) (*German*) +- mjverap (*Spanish*) +- metaxx.dev (*Chinese Simplified*) +- g1024116707 (*Chinese Simplified*) +- EricVogt_ (*Spanish*) +- yui87 (*Japanese*) +- sn02 (*German*) +- nicosomb (*French*) +- thiht44 (*French*) - tamaina (*Japanese*) +- OpenAlgeria (*Arabic*) +- Saislakshmanan (*Tamil*) +- amithraj1989 (*Kannada*) +- adnanmig (*Arabic*) +- smedvedev (*Russian*) +- boksasp (*Norwegian*) +- mikelalas (*Spanish*) +- random_person (*Spanish*) +- matiaslavik (*Norwegian Nynorsk*) +- douglasalvespe (*Portuguese, Brazilian*) +- Fleva (*Sardinian*) +- arospashai (*Sorani (Kurdish)*) +- xpac (*German*) +- asretro (*Chinese Traditional, Hong Kong*) +- aalam (*Punjabi*) +- mimikun (*Japanese*) +- holgerhuo (*Chinese Simplified*) +- mdb571 (*Malayalam*) +- braydofficial (*German*) +- rmegg1933 (*Latvian*) +- nayumi-464812844 (*Vietnamese*) +- ReavedNetwork (*German*) +- Discostu36 (*German*) +- veer66 (*Thai*) - sk22 (*German*) -- Ragnars Eggerts (rmegg1933) (*Latvian*) -- Sais Lakshmanan (Saislakshmanan) (*Tamil*) -- Amith Raj Shetty (amithraj1989) (*Kannada*) -- Bartek Fijałkowski (brateq) (*Polish*) -- Asbeltrion (*Spanish*) -- Michael Horstmann (mhrstmnn) (*German*) -- Joffrey Abeilard (Abeilard14) (*French*) -- capiscuas (*Spanish*) +- crowkeep (*Japanese*) +- wh0eta (*Russian*) +- Anonymous666 (*Russian*) - djoerd (*Dutch*) -- REMOVED_USER (*Spanish*) -- NeverMine17 (*Russian*) -- songxianj (songxian_jiang) (*Chinese Simplified*) -- Ács Zoltán (zoli111) (*Hungarian*) -- haaninjo (*Swedish*) - REMOVED_USER (*Esperanto*) -- Philip Molares (DerMolly) (*German*) -- ChalkPE (amato0617) (*Korean*) -- ebrezhoneg (*Breton*) -- 디떱 (diddub) (*Korean*) -- Hans (hansj) (*German*) -- Nithya Mary (nithyamary25) (*Tamil*) -- kavitha129 (*Tamil*) +- Abijeet (*Basque*) +- benjamincobb (*German*) - waweic (*German*) -- Aries (orlea) (*Japanese*) -- おさ (osapon) (*Japanese*) -- Abijeet Patro (Abijeet) (*Basque*) -- centumix (*Japanese*) -- Martin Müller (muellermartin) (*German*) +- kavitha129 (*Tamil*) +- nithyamary25 (*Tamil*) +- ebrezhoneg (*Breton*) +- argxentakato (*Japanese*) - tateisu (*Japanese*) -- Arĝentakato (argxentakato) (*Japanese*) -- Benjamin Cobb (benjamincobb) (*German*) -- deanerschnitzel (*German*) -- Jill H. (kokakiwi) (*French*) -- maksutheam (*Finnish*) -- d0p1 (d0p1s4m4) (*French*) +- osapon (*Japanese*) +- centumix (*Japanese*) +- orlea (*Japanese*) +- NeverMine17 (*Russian*) +- capiscuas (*Spanish*) +- brateq (*Polish*) +- zoli111 (*Hungarian*) +- Jiniux (*Italian*) +- Aniqueper1 (*Chinese Simplified*) +- SamOak (*Portuguese, Brazilian*) +- dobrado (*Portuguese, Brazilian*) +- dcapillae (*Spanish*) +- xissshawww (*Chinese Simplified*) +- kuraking202 (*Sorani (Kurdish)*) +- RanjAhmed (*Sorani (Kurdish)*) +- Salh_haji6 (*Sorani (Kurdish)*) +- dashty (*Sorani (Kurdish)*) +- Kurdish.boy (*Sorani (Kurdish)*) +- herrero.maty (*Spanish*) +- umonaca (*Chinese Simplified*) +- ronchaine (*Finnish*) +- atomicmind (*Slovenian*) +- futchitwo (*Japanese*) +- brodi1 (*Dutch*) +- soheilkhanalipur (*Persian*) +- hud5634j (*Spanish*) +- kvdbve34 (*Russian*) +- jiangshanghan (*Chinese Simplified*) +- patriceboivin58 (*French*) - majorblazr (*Danish*) -- Patrice Boivin (patriceboivin58) (*French*) -- 江尚寒 (jiangshanghan) (*Chinese Simplified*) -- HSD Channel (kvdbve34) (*Russian*) -- alwyn joe (iomedivh200) (*Chinese Simplified*) -- ZHY (sheepzh) (*Chinese Simplified*) -- Bei Li (libei) (*Chinese Simplified*) -- Aluo (Aluo_rbszd) (*Chinese Simplified*) -- clarkzjw (*Chinese Simplified*) -- Noah Luppe (noahlup) (*German*) +- maksutheam (*Finnish*) +- kokakiwi (*French*) - araghunde (*Galician*) +- noahlup (*German*) +- clarkzjw (*Chinese Simplified*) +- Aluo_rbszd (*Chinese Simplified*) +- libei (*Chinese Simplified*) +- sheepzh (*Chinese Simplified*) +- iomedivh200 (*Chinese Simplified*) +- fyuodchiodmoiidiiduh86 (*Chinese Simplified*) - BratishkaErik (*Russian*) - Bunny9568 (*Chinese Simplified*) -- SamOak (*Portuguese, Brazilian*) -- Ranj A Abdulqadir (RanjAhmed) (*Sorani (Kurdish)*) -- Amir Kurdo (kuraking202) (*Sorani (Kurdish)*) -- 于晚霞 (xissshawww) (*Chinese Simplified*) -- Fyuoxyjidyho Moiodyyiodyhi (fyuodchiodmoiidiiduh86) (*Chinese Simplified*) -- RPD0911 (*Hungarian*) -- dcapillae (*Spanish*) -- dobrado (*Portuguese, Brazilian*) -- Hannah (Aniqueper1) (*Chinese Simplified*) -- Azad ahmad (dashty) (*Sorani (Kurdish)*) -- Uri Chachick (urich.404) (*Hebrew*) -- Bnoru (*Portuguese, Brazilian*) -- Jiniux (*Italian*) -- REMOVED_USER (*German*) -- Salh_haji6 (Sorani (Kurdish)) -- Kurdish Translator (*Kurdish.boy) (Sorani (Kurdish)*) -- Beagle (beagleworks) (*Japanese*) -- hud5634j (*Spanish*) -- Kisaragi Hiu (flyingfeather1501) (*Chinese Traditional*) -- Dominik Ziegler (dodomedia) (*German*) -- soheilkhanalipur (*Persian*) -- Brodi (brodi1) (*Dutch*) -- Savarín Electrográfico Marmota Intergalactica (herrero.maty) (*Spanish*) -- Ni Futchi (futchitwo) (*Japanese*) -- Zois Lee (gcnwm) (*Chinese Simplified*) -- Arnold Marko (atomicmind) (*Slovenian*) +- d0p1s4m4 (*French*) +- flyingfeather1501 (*Chinese Traditional*) +- dodomedia (*German*) +- beagleworks (*Japanese*) +- gcnwm (*Chinese Simplified*) - scholzco (*German*) -- Jari Ronkainen (ronchaine) (*Finnish*) -- umonaca (*Chinese Simplified*) +- RPD0911 (*Hungarian*) +- urich.404 (*Hebrew*) +- Bnoru (*Portuguese, Brazilian*) +- deanerschnitzel (*German*) +- haaninjo (*Swedish*) +- Asbeltrion (*Spanish*) +- songxian_jiang (*Chinese Simplified*) +- hansj (*German*) +- amato0617 (*Korean*) +- diddub (*Korean*) +- muellermartin (*German*) +- DerMolly (*German*) +- Abeilard14 (*French*) +- mhrstmnn (*German*) \ No newline at end of file diff --git a/Aptfile b/Aptfile index a52eef4e1..5e033f136 100644 --- a/Aptfile +++ b/Aptfile @@ -1,26 +1,5 @@ ffmpeg -libicu[0-9][0-9] -libicu-dev -libidn12 -libidn-dev +libopenblas0-pthread libpq-dev libxdamage1 libxfixes3 -zlib1g-dev -libcairo2 -libcroco3 -libdatrie1 -libgdk-pixbuf2.0-0 -libgraphite2-3 -libharfbuzz0b -libpango-1.0-0 -libpangocairo-1.0-0 -libpangoft2-1.0-0 -libpixman-1-0 -librsvg2-2 -libthai-data -libthai0 -libvpx[5-9] -libxcb-render0 -libxcb-shm0 -libxrender1 diff --git a/CHANGELOG.md b/CHANGELOG.md index b3cad7a90..8e579e148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,24 +1,74 @@ -Changelog -========= +# Changelog All notable changes to this project will be documented in this file. -## End of life notice +## [4.2.9] - 2024-05-30 -**The 4.0.x branch has reached its end of life and will not receive any further update.** -This means that no security fix will be made available for this branch after this date, and you will need to update to a more recent version (such as the 4.2.x branch) to receive security fixes. +### Security -## [4.0.15] - 2024-02-16 +- Update dependencies +- Fix private mention filtering ([GHSA-5fq7-3p3j-9vrf](https://github.com/mastodon/mastodon/security/advisories/GHSA-5fq7-3p3j-9vrf)) +- Fix password change endpoint not being rate-limited ([GHSA-q3rg-xx5v-4mxh](https://github.com/mastodon/mastodon/security/advisories/GHSA-q3rg-xx5v-4mxh)) +- Add hardening around rate-limit bypass ([GHSA-c2r5-cfqr-c553](https://github.com/mastodon/mastodon/security/advisories/GHSA-c2r5-cfqr-c553)) + +### Added + +- Add rate-limit on OAuth application registration ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30316)) +- Add fallback redirection when getting a webfinger query `WEB_DOMAIN@WEB_DOMAIN` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28592)) +- Add `digest` attribute to `Admin::DomainBlock` entity in REST API ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/29092)) + +### Removed + +- Remove superfluous application-level caching in some controllers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29862)) +- Remove aggressive OAuth application vacuuming ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30316)) + +### Fixed + +- Fix leaking Elasticsearch connections in Sidekiq processes ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30450)) +- Fix language of remote posts not being recognized when using unusual casing ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30403)) +- Fix off-by-one in `tootctl media` commands ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30306)) +- Fix removal of allowed domains (in `LIMITED_FEDERATION_MODE`) not being recorded in the audit log ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30125)) +- Fix not being able to block a subdomain of an already-blocked domain through the API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30119)) +- Fix `Idempotency-Key` being ignored when scheduling a post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30084)) +- Fix crash when supplying the `FFMPEG_BINARY` environment variable ([timothyjrogers](https://github.com/mastodon/mastodon/pull/30022)) +- Fix improper email address validation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29838)) +- Fix results/query in `api/v1/featured_tags/suggestions` ([mjankowski](https://github.com/mastodon/mastodon/pull/29597)) +- Fix unblocking internationalized domain names under certain conditions ([tribela](https://github.com/mastodon/mastodon/pull/29530)) +- Fix admin account created by `mastodon:setup` not being auto-approved ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29379)) +- Fix reference to non-existent var in CLI maintenance command ([mjankowski](https://github.com/mastodon/mastodon/pull/28363)) + +## [4.2.8] - 2024-02-23 + +### Added + +- Add hourly task to automatically require approval for new registrations in the absence of moderators ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29318), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/29355)) + In order to prevent future abandoned Mastodon servers from being used for spam, harassment and other malicious activity, Mastodon will now automatically switch new user registrations to require moderator approval whenever they are left open and no activity (including non-moderation actions from apps) from any logged-in user with permission to access moderation reports has been detected in a full week. + When this happens, users with the permission to change server settings will receive an email notification. + This feature is disabled when `EMAIL_DOMAIN_ALLOWLIST` is used, and can also be disabled with `DISABLE_AUTOMATIC_SWITCHING_TO_APPROVED_REGISTRATIONS=true`. + +### Changed + +- Change registrations to be closed by default on new installations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29280)) + If you are running a server and never changed your registrations mode from the default, updating will automatically close your registrations. + Simply re-enable them through the administration interface or using `tootctl settings registrations open` if you want to enable them again. + +### Fixed + +- Fix processing of remote ActivityPub actors making use of `Link` objects as `Image` `url` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29335)) +- Fix link verifications when page size exceeds 1MB ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29358)) + +## [4.2.7] - 2024-02-16 ### Fixed - Fix OmniAuth tests and edge cases in error handling ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29201), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/29207)) +- Fix new installs by upgrading to the latest release of the `nsa` gem, instead of a no longer existing commit ([mjankowski](https://github.com/mastodon/mastodon/pull/29065)) ### Security - Fix insufficient checking of remote posts ([GHSA-jhrq-qvrm-qr36](https://github.com/mastodon/mastodon/security/advisories/GHSA-jhrq-qvrm-qr36)) -## [4.0.14] - 2024-02-14 +## [4.2.6] - 2024-02-14 ### Security @@ -37,13 +87,77 @@ This means that no security fix will be made available for this branch after thi For these reasons, this behavior is now locked under the `ALLOW_UNSAFE_AUTH_PROVIDER_REATTACH` environment variable. In addition, regardless of this environment variable, Mastodon will refuse to attach two identities from the same authentication provider to the same account. -## [4.0.13] - 2024-02-01 +## [4.2.5] - 2024-02-01 ### Security - Fix insufficient origin validation (CVE-2024-23832, [GHSA-3fjr-858r-92rw](https://github.com/mastodon/mastodon/security/advisories/GHSA-3fjr-858r-92rw)) -## [4.0.12] - 2023-10-10 +## [4.2.4] - 2024-01-24 + +### Fixed + +- Fix error when processing remote files with unusually long names ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28823)) +- Fix processing of compacted single-item JSON-LD collections ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28816)) +- Retry 401 errors on replies fetching ([ShadowJonathan](https://github.com/mastodon/mastodon/pull/28788)) +- Fix `RecordNotUnique` errors in LinkCrawlWorker ([tribela](https://github.com/mastodon/mastodon/pull/28748)) +- Fix Mastodon not correctly processing HTTP Signatures with query strings ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28443), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/28476)) +- Fix potential redirection loop of streaming endpoint ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28665)) +- Fix streaming API redirection ignoring the port of `streaming_api_base_url` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28558)) +- Fix error when processing link preview with an array as `inLanguage` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28252)) +- Fix unsupported time zone or locale preventing sign-up ([Gargron](https://github.com/mastodon/mastodon/pull/28035)) +- Fix "Hide these posts from home" list setting not refreshing when switching lists ([brianholley](https://github.com/mastodon/mastodon/pull/27763)) +- Fix missing background behind dismissable banner in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/27479)) +- Fix line wrapping of language selection button with long locale codes ([gunchleoc](https://github.com/mastodon/mastodon/pull/27100), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27127)) +- Fix `Undo Announce` activity not being sent to non-follower authors ([MitarashiDango](https://github.com/mastodon/mastodon/pull/18482)) +- Fix N+1s because of association preloaders not actually getting called ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28339)) +- Fix empty column explainer getting cropped under certain conditions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28337)) +- Fix `LinkCrawlWorker` error when encountering empty OEmbed response ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28268)) +- Fix call to inefficient `delete_matched` cache method in domain blocks ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28367)) + +### Security + +- Add rate-limit of TOTP authentication attempts at controller level ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28801)) + +## [4.2.3] - 2023-12-05 + +### Fixed + +- Fix dependency on `json-canonicalization` version that has been made unavailable since last release + +## [4.2.2] - 2023-12-04 + +### Changed + +- Change dismissed banners to be stored server-side ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27055)) +- Change GIF max matrix size error to explicitly mention GIF files ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27927)) +- Change `Follow` activities delivery to bypass availability check ([ShadowJonathan](https://github.com/mastodon/mastodon/pull/27586)) +- Change single-column navigation notice to be displayed outside of the logo container ([renchap](https://github.com/mastodon/mastodon/pull/27462), [renchap](https://github.com/mastodon/mastodon/pull/27476)) +- Change Content-Security-Policy to be tighter on media paths ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26889)) +- Change post language code to include country code when relevant ([gunchleoc](https://github.com/mastodon/mastodon/pull/27099), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27207)) + +### Fixed + +- Fix upper border radius of onboarding columns ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27890)) +- Fix incoming status creation date not being restricted to standard ISO8601 ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27655), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/28081)) +- Fix some posts from threads received out-of-order sometimes not being inserted into timelines ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27653)) +- Fix posts from force-sensitized accounts being able to trend ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27620)) +- Fix error when trying to delete already-deleted file with OpenStack Swift ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27569)) +- Fix batch attachment deletion when using OpenStack Swift ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27554)) +- Fix processing LDSigned activities from actors with unknown public keys ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27474)) +- Fix error and incorrect URLs in `/api/v1/accounts/:id/featured_tags` for remote accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27459)) +- Fix report processing notice not mentioning the report number when performing a custom action ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27442)) +- Fix handling of `inLanguage` attribute in preview card processing ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27423)) +- Fix own posts being removed from home timeline when unfollowing a used hashtag ([kmycode](https://github.com/mastodon/mastodon/pull/27391)) +- Fix some link anchors being recognized as hashtags ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27271), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27584)) +- Fix format-dependent redirects being cached regardless of requested format ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27634)) + +## [4.2.1] - 2023-10-10 + +### Added + +- Add redirection on `/deck` URLs for logged-out users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27128)) +- Add support for v4.2.0 migrations to `tootctl maintenance fix-duplicates` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27147)) ### Changed @@ -52,23 +166,329 @@ This means that no security fix will be made available for this branch after thi ### Fixed +- Fix duplicate reports being sent when reporting some remote posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27355)) +- Fix clicking on already-opened thread post scrolling to the top of the thread ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27331), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27338), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27350)) +- Fix some remote posts getting truncated ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27307)) +- Fix some cases of infinite scroll code trying to fetch inaccessible posts in a loop ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27286)) +- Fix `Vary` headers not being set on some redirects ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27272)) - Fix mentions being matched in some URL query strings ([mjankowski](https://github.com/mastodon/mastodon/pull/25656)) +- Fix unexpected linebreak in version string in the Web UI ([vmstan](https://github.com/mastodon/mastodon/pull/26986)) +- Fix double scroll bars in some columns in advanced interface ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27187)) +- Fix boosts of local users being filtered in account timelines ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27204)) - Fix multiple instances of the trend refresh scheduler sometimes running at once ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27253)) - Fix importer returning negative row estimates ([jgillich](https://github.com/mastodon/mastodon/pull/27258)) +- Fix incorrectly keeping outdated update notices absent from the API endpoint ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27021)) +- Fix import progress not updating on certain failures ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27247)) +- Fix websocket connections being incorrectly decremented twice on errors ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/27238)) +- Fix explore prompt appearing because of posts being received out of order ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27211)) +- Fix explore prompt sometimes showing up when the home TL is loading ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27062)) +- Fix link handling of mentions in user profiles when logged out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27185)) - Fix filtering audit log for entries about disabling 2FA ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27186)) +- Fix notification toasts not respecting reduce-motion ([c960657](https://github.com/mastodon/mastodon/pull/27178)) +- Fix retention dashboard not displaying correct month ([vmstan](https://github.com/mastodon/mastodon/pull/27180)) - Fix tIME chunk not being properly removed from PNG uploads ([TheEssem](https://github.com/mastodon/mastodon/pull/27111)) +- Fix division by zero in video in bitrate computation code ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27129)) - Fix inefficient queries in “Follows and followers” as well as several admin pages ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27116), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27306)) +- Fix ActiveRecord using two connection pools when no replica is defined ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27061)) +- Fix the search documentation URL in system checks ([renchap](https://github.com/mastodon/mastodon/pull/27036)) -## [4.0.11] - 2023-09-20 +## [4.2.0] - 2023-09-21 + +The following changelog entries focus on changes visible to users, administrators, client developers or federated software developers, but there has also been a lot of code modernization, refactoring, and tooling work, in particular by [@danielmbrasil](https://github.com/danielmbrasil), [@mjankowski](https://github.com/mjankowski), [@nschonni](https://github.com/nschonni), [@renchap](https://github.com/renchap), and [@takayamaki](https://github.com/takayamaki). + +### Added + +- **Add full-text search of opted-in public posts and rework search operators** ([Gargron](https://github.com/mastodon/mastodon/pull/26485), [jsgoldstein](https://github.com/mastodon/mastodon/pull/26344), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26657), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26650), [jsgoldstein](https://github.com/mastodon/mastodon/pull/26659), [Gargron](https://github.com/mastodon/mastodon/pull/26660), [Gargron](https://github.com/mastodon/mastodon/pull/26663), [Gargron](https://github.com/mastodon/mastodon/pull/26688), [Gargron](https://github.com/mastodon/mastodon/pull/26689), [Gargron](https://github.com/mastodon/mastodon/pull/26686), [Gargron](https://github.com/mastodon/mastodon/pull/26687), [Gargron](https://github.com/mastodon/mastodon/pull/26692), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26697), [Gargron](https://github.com/mastodon/mastodon/pull/26699), [Gargron](https://github.com/mastodon/mastodon/pull/26701), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26710), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26739), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26754), [Gargron](https://github.com/mastodon/mastodon/pull/26662), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26755), [Gargron](https://github.com/mastodon/mastodon/pull/26781), [Gargron](https://github.com/mastodon/mastodon/pull/26782), [Gargron](https://github.com/mastodon/mastodon/pull/26760), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26756), [Gargron](https://github.com/mastodon/mastodon/pull/26784), [Gargron](https://github.com/mastodon/mastodon/pull/26807), [Gargron](https://github.com/mastodon/mastodon/pull/26835), [Gargron](https://github.com/mastodon/mastodon/pull/26847), [Gargron](https://github.com/mastodon/mastodon/pull/26834), [arbolitoloco1](https://github.com/mastodon/mastodon/pull/26893), [tribela](https://github.com/mastodon/mastodon/pull/26896), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26927), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26959), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27014)) + This introduces a new `public_statuses` Elasticsearch index for public posts by users who have opted in to their posts being searchable (`toot#indexable` flag). + This also revisits the other indexes to provide more useful indexing, and adds new search operators such as `from:me`, `before:2022-11-01`, `after:2022-11-01`, `during:2022-11-01`, `language:fr`, `has:poll`, or `in:library` (for searching only in posts you have written or interacted with). + Results are now ordered chronologically. +- **Add admin notifications for new Mastodon versions** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26582)) + This is done by querying `https://api.joinmastodon.org/update-check` every 30 minutes in a background job. + That URL can be changed using the `UPDATE_CHECK_URL` environment variable, and the feature outright disabled by setting that variable to an empty string (`UPDATE_CHECK_URL=`). +- **Add “Privacy and reach” tab in profile settings** ([Gargron](https://github.com/mastodon/mastodon/pull/26484), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26508)) + This reorganized scattered privacy and reach settings to a single place, as well as improve their wording. +- **Add display of out-of-band hashtags in the web interface** ([Gargron](https://github.com/mastodon/mastodon/pull/26492), [arbolitoloco1](https://github.com/mastodon/mastodon/pull/26497), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26506), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26525), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26606), [Gargron](https://github.com/mastodon/mastodon/pull/26666), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26960)) +- **Add role badges to the web interface** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25649), [Gargron](https://github.com/mastodon/mastodon/pull/26281)) +- **Add ability to pick domains to forward reports to using the `forward_to_domains` parameter in `POST /api/v1/reports`** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25866), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26636)) + The `forward_to_domains` REST API parameter is a list of strings. If it is empty or omitted, the previous behavior is maintained. + The `forward` parameter still needs to be set for `forward_to_domains` to be taken into account. + The forwarded-to domains can only include that of the original author and people being replied to. +- **Add forwarding of reported replies to servers being replied to** ([Gargron](https://github.com/mastodon/mastodon/pull/25341), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26189)) +- Add `ONE_CLICK_SSO_LOGIN` environment variable to directly link to the Single-Sign On provider if there is only one sign up method available ([CSDUMMI](https://github.com/mastodon/mastodon/pull/26083), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26368), [CSDUMMI](https://github.com/mastodon/mastodon/pull/26857), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26901)) +- **Add webhook templating** ([Gargron](https://github.com/mastodon/mastodon/pull/23289)) +- **Add webhooks for local `status.created`, `status.updated`, `account.updated` and `report.updated`** ([VyrCossont](https://github.com/mastodon/mastodon/pull/24133), [VyrCossont](https://github.com/mastodon/mastodon/pull/24243), [VyrCossont](https://github.com/mastodon/mastodon/pull/24211)) +- **Add exclusive lists** ([dariusk, necropolina](https://github.com/mastodon/mastodon/pull/22048), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25324)) +- **Add a confirmation screen when suspending a domain** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25144), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25603)) +- **Add support for importing lists** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25203), [mgmn](https://github.com/mastodon/mastodon/pull/26120), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26372)) +- **Add optional hCaptcha support** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25019), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25057), [Gargron](https://github.com/mastodon/mastodon/pull/25395), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26388)) +- **Add lines to threads in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24549), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24677), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24696), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24711), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24714), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24713), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24715), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24800), [teeerevor](https://github.com/mastodon/mastodon/pull/25706), [renchap](https://github.com/mastodon/mastodon/pull/25807)) +- **Add new onboarding flow to web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24619), [Gargron](https://github.com/mastodon/mastodon/pull/24646), [Gargron](https://github.com/mastodon/mastodon/pull/24705), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24872), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/24883), [Gargron](https://github.com/mastodon/mastodon/pull/24954), [stevenjlm](https://github.com/mastodon/mastodon/pull/24959), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25010), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25275), [Gargron](https://github.com/mastodon/mastodon/pull/25559), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25561)) +- **Add auto-refresh of accounts we get new messages/edits of** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26510)) +- **Add Elasticsearch cluster health check and indexes mismatch check to dashboard** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26448), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26605), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26658)) +- Add `hide_collections`, `discoverable` and `indexable` attributes to credentials API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26998)) +- Add `S3_ENABLE_CHECKSUM_MODE` environment variable to enable checksum verification on compatible S3-providers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26435)) +- Add admin API for managing tags ([rrgeorge](https://github.com/mastodon/mastodon/pull/26872)) +- Add a link to hashtag timelines from the Trending hashtags moderation interface ([gunchleoc](https://github.com/mastodon/mastodon/pull/26724)) +- Add timezone to datetimes in e-mails ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26822)) +- Add `authorized_fetch` server setting in addition to env var ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25798), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26958)) +- Add avatar image to webfinger responses ([tvler](https://github.com/mastodon/mastodon/pull/26558)) +- Add debug logging on signature verification failure ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26637), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26812)) +- Add explicit error messages when DeepL quota is exceeded ([lutoma](https://github.com/mastodon/mastodon/pull/26704)) +- Add Elasticsearch/OpenSearch version to “Software” in admin dashboard ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26652)) +- Add `data-nosnippet` attribute to remote posts and local posts with `noindex` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26648)) +- Add support for federating `memorial` attribute ([rrgeorge](https://github.com/mastodon/mastodon/pull/26583)) +- Add Cherokee and Kalmyk to languages dropdown ([gunchleoc](https://github.com/mastodon/mastodon/pull/26012), [gunchleoc](https://github.com/mastodon/mastodon/pull/26013)) +- Add `DELETE /api/v1/profile/avatar` and `DELETE /api/v1/profile/header` to the REST API ([danielmbrasil](https://github.com/mastodon/mastodon/pull/25124), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26573)) +- Add `ES_PRESET` option to customize numbers of shards and replicas ([Gargron](https://github.com/mastodon/mastodon/pull/26483), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26489)) + This can have a value of `single_node_cluster` (default), `small_cluster` (uses one replica) or `large_cluster` (uses one replica and a higher number of shards). +- Add `CACHE_BUSTER_HTTP_METHOD` environment variable ([renchap](https://github.com/mastodon/mastodon/pull/26528), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26542)) +- Add support for `DB_PASS` when using `DATABASE_URL` ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26295)) +- Add `GET /api/v1/instance/languages` to REST API ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24443)) +- Add primary key to `preview_cards_statuses` join table ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25243), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26384), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26447), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26737), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26979)) +- Add client-side timeout on resend confirmation button ([Gargron](https://github.com/mastodon/mastodon/pull/26300)) +- Add published date and author to news on the explore screen in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26155)) +- Add `lang` attribute to various UI components ([c960657](https://github.com/mastodon/mastodon/pull/23869), [c960657](https://github.com/mastodon/mastodon/pull/23891), [c960657](https://github.com/mastodon/mastodon/pull/26111), [c960657](https://github.com/mastodon/mastodon/pull/26149)) +- Add stricter protocol fields validation for accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25937)) +- Add support for Azure blob storage ([mistydemeo](https://github.com/mastodon/mastodon/pull/23607), [mistydemeo](https://github.com/mastodon/mastodon/pull/26080)) +- Add toast with option to open post after publishing in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25564), [Signez](https://github.com/mastodon/mastodon/pull/25919), [Gargron](https://github.com/mastodon/mastodon/pull/26664)) +- Add canonical link tags in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25715)) +- Add button to see results for polls in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25726)) +- Add at-symbol prepended to mention span title ([forsamori](https://github.com/mastodon/mastodon/pull/25684)) +- Add users index on `unconfirmed_email` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25672), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25702)) +- Add superapp index on `oauth_applications` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25670)) +- Add index to backups on `user_id` column ([mjankowski](https://github.com/mastodon/mastodon/pull/25647)) +- Add onboarding prompt when home feed too slow in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25267), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25556), [Gargron](https://github.com/mastodon/mastodon/pull/25579), [renchap](https://github.com/mastodon/mastodon/pull/25580), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25581), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25617), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25917), [Gargron](https://github.com/mastodon/mastodon/pull/26829), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26935)) +- Add `POST /api/v1/conversations/:id/unread` API endpoint to mark a conversation as unread ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25509)) +- Add `translate="no"` to outgoing mentions and links ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25524)) +- Add unsubscribe link and headers to e-mails ([Gargron](https://github.com/mastodon/mastodon/pull/25378), [c960657](https://github.com/mastodon/mastodon/pull/26085)) +- Add logging of websocket send errors ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25280)) +- Add time zone preference ([Gargron](https://github.com/mastodon/mastodon/pull/25342), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26025)) +- Add `legal` as report category ([Gargron](https://github.com/mastodon/mastodon/pull/23941), [renchap](https://github.com/mastodon/mastodon/pull/25400), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26509)) +- Add `data-nosnippet` so Google doesn't use trending posts in snippets for `/` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25279)) +- Add card with who invited you to join when displaying rules on sign-up ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23475)) +- Add missing primary keys to `accounts_tags` and `statuses_tags` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25210)) +- Add support for custom sign-up URLs ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25014), [renchap](https://github.com/mastodon/mastodon/pull/25108), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25190), [mgmn](https://github.com/mastodon/mastodon/pull/25531)) + This is set using `SSO_ACCOUNT_SIGN_UP` and reflected in the REST API by adding `registrations.sign_up_url` to the `/api/v2/instance` endpoint. +- Add polling and automatic redirection to `/start` on email confirmation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25013)) +- Add ability to block sign-ups from IP using the CLI ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24870)) +- Add ALT badges to media that has alternative text in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24782), [c960657](https://github.com/mastodon/mastodon/pull/26166) +- Add ability to include accounts with pending follow requests in lists ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19727), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24810)) +- Add trend management to admin API ([rrgeorge](https://github.com/mastodon/mastodon/pull/24257)) + - `POST /api/v1/admin/trends/statuses/:id/approve` + - `POST /api/v1/admin/trends/statuses/:id/reject` + - `POST /api/v1/admin/trends/links/:id/approve` + - `POST /api/v1/admin/trends/links/:id/reject` + - `POST /api/v1/admin/trends/tags/:id/approve` + - `POST /api/v1/admin/trends/tags/:id/reject` + - `GET /api/v1/admin/trends/links/publishers` + - `POST /api/v1/admin/trends/links/publishers/:id/approve` + - `POST /api/v1/admin/trends/links/publishers/:id/reject` +- Add user handle to notification mail recipient address ([HeitorMC](https://github.com/mastodon/mastodon/pull/24240)) +- Add progress indicator to sign-up flow ([Gargron](https://github.com/mastodon/mastodon/pull/24545)) +- Add client-side validation for taken username in sign-up form ([Gargron](https://github.com/mastodon/mastodon/pull/24546)) +- Add `--approve` option to `tootctl accounts create` ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24533)) +- Add “In Memoriam” banner back to profiles ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23591), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23614)) + This adds the `memorial` attribute to the `Account` REST API entity. +- Add colour to follow button when hashtag is being followed ([c960657](https://github.com/mastodon/mastodon/pull/24361)) +- Add further explanations to the profile link verification instructions ([drzax](https://github.com/mastodon/mastodon/pull/19723)) +- Add a link to Identity provider's account settings from the account settings ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24100), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24628)) +- Add support for streaming server to connect to postgres with self-signed certs through the `sslmode` URL parameter ([ramuuns](https://github.com/mastodon/mastodon/pull/21431)) +- Add support for specifying S3 storage classes through the `S3_STORAGE_CLASS` environment variable ([hyl](https://github.com/mastodon/mastodon/pull/22480)) +- Add support for incoming rich text ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23913)) +- Add support for Ruby 3.2 ([tenderlove](https://github.com/mastodon/mastodon/pull/22928), [casperisfine](https://github.com/mastodon/mastodon/pull/24142), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24202), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26934)) +- Add API parameter to safeguard unexpected mentions in new posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18350)) + +### Changed + +- **Change hashtags to be displayed separately when they are the last line of a post** ([renchap](https://github.com/mastodon/mastodon/pull/26499), [renchap](https://github.com/mastodon/mastodon/pull/26614), [renchap](https://github.com/mastodon/mastodon/pull/26615)) +- **Change reblogs to be excluded from "Posts and replies" tab in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/26302)) +- **Change interaction modal in web interface** ([Gargron, ClearlyClaire](https://github.com/mastodon/mastodon/pull/26075), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26269), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26268), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26267), [mgmn](https://github.com/mastodon/mastodon/pull/26459), [tribela](https://github.com/mastodon/mastodon/pull/26461), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26593), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26795)) +- **Change design of link previews in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/26136), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26151), [Gargron](https://github.com/mastodon/mastodon/pull/26153), [Gargron](https://github.com/mastodon/mastodon/pull/26250), [Gargron](https://github.com/mastodon/mastodon/pull/26287), [Gargron](https://github.com/mastodon/mastodon/pull/26286), [c960657](https://github.com/mastodon/mastodon/pull/26184)) +- **Change "direct message" nomenclature to "private mention" in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24248)) +- **Change translation feature to cover Content Warnings, poll options and media descriptions** ([c960657](https://github.com/mastodon/mastodon/pull/24175), [S-H-GAMELINKS](https://github.com/mastodon/mastodon/pull/25251), [c960657](https://github.com/mastodon/mastodon/pull/26168), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26452)) +- **Change account search to match by text when opted-in** ([jsgoldstein](https://github.com/mastodon/mastodon/pull/25599), [Gargron](https://github.com/mastodon/mastodon/pull/26378)) +- **Change import feature to be clearer, less error-prone and more reliable** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21054), [mgmn](https://github.com/mastodon/mastodon/pull/24874)) +- **Change local and federated timelines to be tabs of a single “Live feeds” column** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25641), [Gargron](https://github.com/mastodon/mastodon/pull/25683), [mgmn](https://github.com/mastodon/mastodon/pull/25694), [Plastikmensch](https://github.com/mastodon/mastodon/pull/26247), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26633)) +- **Change user archive export to be faster and more reliable, and export `.zip` archives instead of `.tar.gz` ones** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23360), [TheEssem](https://github.com/mastodon/mastodon/pull/25034)) +- **Change `mastodon-streaming` systemd unit files to be templated** ([e-nomem](https://github.com/mastodon/mastodon/pull/24751)) +- **Change `statsd` integration to disable sidekiq metrics by default** ([mjankowski](https://github.com/mastodon/mastodon/pull/25265), [mjankowski](https://github.com/mastodon/mastodon/pull/25336), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26310)) + This deprecates `statsd` support and disables the sidekiq integration unless `STATSD_SIDEKIQ` is set to `true`. + This is because the `nsa` gem is unmaintained, and its sidekiq integration is known to add very significant overhead. + Later versions of Mastodon will have other ways to get the same metrics. +- **Change replica support to native Rails adapter** ([krainboltgreene](https://github.com/mastodon/mastodon/pull/25693), [Gargron](https://github.com/mastodon/mastodon/pull/25849), [Gargron](https://github.com/mastodon/mastodon/pull/25874), [Gargron](https://github.com/mastodon/mastodon/pull/25851), [Gargron](https://github.com/mastodon/mastodon/pull/25977), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26074), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26326), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26386), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26856)) + This is a breaking change, dropping `makara` support, and requiring you to update your database configuration if you are using replicas. + To tell Mastodon to use a read replica, you can either set the `REPLICA_DB_NAME` environment variable (along with `REPLICA_DB_USER`, `REPLICA_DB_PASS`, `REPLICA_DB_HOST`, and `REPLICA_DB_PORT`, if they differ from the primary database), or the `REPLICA_DATABASE_URL` environment variable if your configuration is based on `DATABASE_URL`. +- Change DCT method used for JPEG encoding to float ([electroCutie](https://github.com/mastodon/mastodon/pull/26675)) +- Change from `node-redis` to `ioredis` for streaming ([gmemstr](https://github.com/mastodon/mastodon/pull/26581)) +- Change private statuses index to index without crutches ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26713)) +- Change video compression parameters ([Gargron](https://github.com/mastodon/mastodon/pull/26631), [Gargron](https://github.com/mastodon/mastodon/pull/26745), [Gargron](https://github.com/mastodon/mastodon/pull/26766), [Gargron](https://github.com/mastodon/mastodon/pull/26970)) +- Change admin e-mail notification settings to be their own settings group ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26596)) +- Change opacity of the delete icon in the search field to be more visible ([AntoninDelFabbro](https://github.com/mastodon/mastodon/pull/26449)) +- Change Account Search to prioritize username over display name ([jsgoldstein](https://github.com/mastodon/mastodon/pull/26623)) +- Change follow recommendation materialized view to be faster in most cases ([renchap, ClearlyClaire](https://github.com/mastodon/mastodon/pull/26545)) +- Change `robots.txt` to block GPTBot ([Foritus](https://github.com/mastodon/mastodon/pull/26396)) +- Change header of hashtag timelines in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26362), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26416)) +- Change streaming `/metrics` to include additional metrics ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26299), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26945)) +- Change indexing frequency from 5 minutes to 1 minute, add locks to schedulers ([Gargron](https://github.com/mastodon/mastodon/pull/26304)) +- Change column link to add a better keyboard focus indicator ([teeerevor](https://github.com/mastodon/mastodon/pull/26278)) +- Change poll form element colors to fit with the rest of the ui ([teeerevor](https://github.com/mastodon/mastodon/pull/26139), [teeerevor](https://github.com/mastodon/mastodon/pull/26162), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26164)) +- Change 'favourite' to 'favorite' for American English ([marekr](https://github.com/mastodon/mastodon/pull/24667), [gunchleoc](https://github.com/mastodon/mastodon/pull/26009), [nabijaczleweli](https://github.com/mastodon/mastodon/pull/26109)) +- Change ActivityStreams representation of suspended accounts to not use a blank `name` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25276)) +- Change focus UI for keyboard only input ([teeerevor](https://github.com/mastodon/mastodon/pull/25935), [Gargron](https://github.com/mastodon/mastodon/pull/26125), [Gargron](https://github.com/mastodon/mastodon/pull/26767)) +- Change thread view to scroll to the selected post rather than the post being replied to ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24685)) +- Change links in multi-column mode so tabs are open in single-column mode ([Signez](https://github.com/mastodon/mastodon/pull/25893), [Signez](https://github.com/mastodon/mastodon/pull/26070), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25973), [Signez](https://github.com/mastodon/mastodon/pull/26019), [Signez](https://github.com/mastodon/mastodon/pull/26759)) +- Change searching with `#` to include account index ([jsgoldstein](https://github.com/mastodon/mastodon/pull/25638)) +- Change label and design of sensitive and unavailable media in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25712), [Gargron](https://github.com/mastodon/mastodon/pull/26135), [Gargron](https://github.com/mastodon/mastodon/pull/26330)) +- Change button colors to increase hover/focus contrast and consistency ([teeerevor](https://github.com/mastodon/mastodon/pull/25677), [Gargron](https://github.com/mastodon/mastodon/pull/25679)) +- Change dropdown icon above compose form from ellipsis to bars in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25661)) +- Change header backgrounds to use fewer different colors in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25577)) +- Change files to be deleted in batches instead of one-by-one ([Gargron](https://github.com/mastodon/mastodon/pull/23302), [S-H-GAMELINKS](https://github.com/mastodon/mastodon/pull/25586), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25587)) +- Change emoji picker icon ([iparr](https://github.com/mastodon/mastodon/pull/25479)) +- Change edit profile page ([Gargron](https://github.com/mastodon/mastodon/pull/25413), [c960657](https://github.com/mastodon/mastodon/pull/26538)) +- Change "bot" label to "automated" ([Gargron](https://github.com/mastodon/mastodon/pull/25356)) +- Change design of dropdowns in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25107)) +- Change wording of “Content cache retention period” setting to highlight destructive implications ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23261)) +- Change autolinking to allow carets in URL search params ([renchap](https://github.com/mastodon/mastodon/pull/25216)) +- Change share action from being in action bar to being in dropdown in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25105)) +- Change sessions to be ordered from most-recent to least-recently updated ([frankieroberto](https://github.com/mastodon/mastodon/pull/25005)) +- Change vacuum scheduler to also delete expired tokens and unused application records ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24868), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24871)) +- Change "Sign in" to "Login" ([Gargron](https://github.com/mastodon/mastodon/pull/24942)) +- Change domain suspensions to also be checked before trying to fetch unknown remote resources ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24535)) +- Change media components to use aspect-ratio rather than compute height themselves ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24686), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24943), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26801)) +- Change logo version in header based on screen size in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24707)) +- Change label from "For you" to "People" on explore screen in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24706)) +- Change logged-out WebUI HTML pages to be cached for a few seconds ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24708)) +- Change unauthenticated responses to be cached in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/24348), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24662), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24665)) +- Change HTTP caching logic ([Gargron](https://github.com/mastodon/mastodon/pull/24347), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24604)) +- Change hashtags and mentions in bios to open in-app in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24643)) +- Change styling of the recommended accounts to allow bio to be more visible ([chike00](https://github.com/mastodon/mastodon/pull/24480)) +- Change account search in moderation interface to allow searching by username including the leading `@` ([HeitorMC](https://github.com/mastodon/mastodon/pull/24242)) +- Change all components to use the same error page in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24512)) +- Change search pop-out in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24305)) +- Change user settings to be stored in a more optimal way ([Gargron](https://github.com/mastodon/mastodon/pull/23630), [c960657](https://github.com/mastodon/mastodon/pull/24321), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24453), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24460), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24558), [Gargron](https://github.com/mastodon/mastodon/pull/24761), [Gargron](https://github.com/mastodon/mastodon/pull/24783), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25508), [jsgoldstein](https://github.com/mastodon/mastodon/pull/25340), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26884), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27012)) +- Change media upload limits and remove client-side resizing ([Gargron](https://github.com/mastodon/mastodon/pull/23726)) +- Change design of account rows in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24247), [Gargron](https://github.com/mastodon/mastodon/pull/24343), [Gargron](https://github.com/mastodon/mastodon/pull/24956), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25131)) +- Change log-out to use Single Logout when using external log-in through OIDC ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24020)) +- Change sidekiq-bulk's batch size from 10,000 to 1,000 jobs in one Redis call ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24034)) +- Change translation to only be offered for supported languages ([c960657](https://github.com/mastodon/mastodon/pull/23879), [c960657](https://github.com/mastodon/mastodon/pull/24037)) + This adds the `/api/v1/instance/translation_languages` REST API endpoint that returns an object with the supported translation language pairs in the form: + ```json + { + "fr": ["en", "de"] + } + ``` + (where `fr` is a supported source language and `en` and `de` or supported output language when translating a `fr` string) +- Change compose form checkbox to native input with `appearance: none` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22949)) +- Change posts' clickable area to be larger ([c960657](https://github.com/mastodon/mastodon/pull/23621)) +- Change `followed_by` link to `location=all` if account is local on /admin/accounts/:id page ([tribela](https://github.com/mastodon/mastodon/pull/23467)) + +### Removed + +- **Remove support for Node.js 14** ([renchap](https://github.com/mastodon/mastodon/pull/25198)) +- **Remove support for Ruby 2.7** ([nschonni](https://github.com/mastodon/mastodon/pull/24237)) +- **Remove clustering from streaming API** ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/24655)) +- **Remove anonymous access to the streaming API** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23989)) +- Remove obfuscation of reply count in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26768)) +- Remove `kmr` from language selection, as it was a duplicate for `ku` ([gunchleoc](https://github.com/mastodon/mastodon/pull/26014), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26787)) +- Remove 16:9 cropping from web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26132)) +- Remove back button from bookmarks, favourites and lists screens in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26126)) +- Remove display name input from sign-up form ([Gargron](https://github.com/mastodon/mastodon/pull/24704)) +- Remove `tai` locale ([c960657](https://github.com/mastodon/mastodon/pull/23880)) +- Remove empty Kushubian (csb) local files ([nschonni](https://github.com/mastodon/mastodon/pull/24151)) +- Remove `Permissions-Policy` header from all responses ([Gargron](https://github.com/mastodon/mastodon/pull/24124)) ### Fixed -- Fix post translation erroring out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26990)) +- **Fix filters not being applying in the explore page** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25887)) +- **Fix being unable to load past a full page of filtered posts in Home timeline** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24930)) +- **Fix log-in flow when involving both OAuth and external authentication** ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24073)) +- **Fix broken links in account gallery** ([c960657](https://github.com/mastodon/mastodon/pull/24218)) +- **Fix migration handler not updating lists** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24808)) +- Fix crash when viewing a moderation appeal and the moderator account has been deleted ([xrobau](https://github.com/mastodon/mastodon/pull/25900)) +- Fix error in Web UI when server rules cannot be fetched ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26957)) +- Fix paragraph margins resulting in irregular read-more cut-off in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26828)) +- Fix notification permissions being requested immediately after login ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26472)) +- Fix performances of profile directory ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26840), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26842)) +- Fix mute button and volume slider feeling disconnected in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26827), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26860)) +- Fix “Scoped order is ignored, it's forced to be batch order.” warnings ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26793)) +- Fix blocked domain appearing in account feeds ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26823)) +- Fix invalid `Content-Type` header for WebP images ([c960657](https://github.com/mastodon/mastodon/pull/26773)) +- Fix minor inefficiencies in `tootctl search deploy` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26721)) +- Fix filter form in profiles directory overflowing instead of wrapping ([arbolitoloco1](https://github.com/mastodon/mastodon/pull/26682)) +- Fix sign up steps progress layout in right-to-left locales ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26728)) +- Fix bug with “favorited by” and “reblogged by“ view on posts only showing up to 40 items ([timothyjrogers](https://github.com/mastodon/mastodon/pull/26577), [timothyjrogers](https://github.com/mastodon/mastodon/pull/26574)) +- Fix bad search type heuristic ([Gargron](https://github.com/mastodon/mastodon/pull/26673)) +- Fix not being able to negate prefix clauses in search ([Gargron](https://github.com/mastodon/mastodon/pull/26672)) +- Fix timeout on invalid set of exclusionary parameters in `/api/v1/timelines/public` ([danielmbrasil](https://github.com/mastodon/mastodon/pull/26239)) +- Fix adding column with default value taking longer on Postgres >= 11 ([Gargron](https://github.com/mastodon/mastodon/pull/26375)) +- Fix light theme select option for hashtags ([teeerevor](https://github.com/mastodon/mastodon/pull/26311)) +- Fix AVIF attachments ([c960657](https://github.com/mastodon/mastodon/pull/26264)) +- Fix incorrect URL normalization when fetching remote resources ([c960657](https://github.com/mastodon/mastodon/pull/26219), [c960657](https://github.com/mastodon/mastodon/pull/26285)) +- Fix being unable to filter posts for individual Chinese languages ([gunchleoc](https://github.com/mastodon/mastodon/pull/26066)) +- Fix preview card sometimes linking to 4xx error pages ([c960657](https://github.com/mastodon/mastodon/pull/26200)) +- Fix emoji picker button scrolling with textarea content in single-column view ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25304)) +- Fix missing border on error screen in light theme in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26152)) +- Fix UI overlap with the loupe icon in the Explore Tab ([gol-cha](https://github.com/mastodon/mastodon/pull/26113)) +- Fix unexpected redirection to `/explore` after sign-in ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26143)) +- Fix `/api/v1/statuses/:id/unfavourite` and `/api/v1/statuses/:id/unreblog` returning non-updated counts ([c960657](https://github.com/mastodon/mastodon/pull/24365)) +- Fix clicking the “Back” button sometimes leading out of Mastodon ([c960657](https://github.com/mastodon/mastodon/pull/23953), [CSFlorin](https://github.com/mastodon/mastodon/pull/24835), [S-H-GAMELINKS](https://github.com/mastodon/mastodon/pull/24867), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25281)) +- Fix processing of `null` ActivityPub activities ([tribela](https://github.com/mastodon/mastodon/pull/26021)) +- Fix hashtag posts not being removed from home feed on hashtag unfollow ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26028)) +- Fix for "follows you" indicator in light web UI not readable ([vmstan](https://github.com/mastodon/mastodon/pull/25993)) +- Fix incorrect line break between icon and number of reposts & favourites ([edent](https://github.com/mastodon/mastodon/pull/26004)) +- Fix sounds not being loaded from assets host ([Signez](https://github.com/mastodon/mastodon/pull/25931)) +- Fix buttons showing inconsistent styles ([teeerevor](https://github.com/mastodon/mastodon/pull/25903), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25965), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26341), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26482)) +- Fix trend calculation working on too many items at a time ([Gargron](https://github.com/mastodon/mastodon/pull/25835)) +- Fix dropdowns being disabled for logged out users in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25714), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25964)) +- Fix explore page being inaccessible when opted-out of trends in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25716)) +- Fix re-activated accounts possibly getting deleted by `AccountDeletionWorker` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25711)) +- Fix `/api/v2/search` not working with following query param ([danielmbrasil](https://github.com/mastodon/mastodon/pull/25681)) +- Fix inefficient query when requesting a new confirmation email from a logged-in account ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25669)) +- Fix unnecessary concurrent calls to `/api/*/instance` in web UI ([mgmn](https://github.com/mastodon/mastodon/pull/25663)) +- Fix resolving local URL for remote content ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25637)) +- Fix search not being easily findable on smaller screens in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25576), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25631)) +- Fix j/k keyboard shortcuts on some status lists ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25554)) +- Fix missing validation on `default_privacy` setting ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25513)) +- Fix incorrect pagination headers in `/api/v2/admin/accounts` ([danielmbrasil](https://github.com/mastodon/mastodon/pull/25477)) +- Fix non-interactive upload container being given a `button` role and tabIndex ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25462)) +- Fix always redirecting to onboarding in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25396)) +- Fix inconsistent use of middle dot (·) instead of bullet (•) to separate items ([j-f1](https://github.com/mastodon/mastodon/pull/25248)) +- Fix spacing of middle dots in the detailed status meta section ([j-f1](https://github.com/mastodon/mastodon/pull/25247)) +- Fix prev/next buttons color in media viewer ([renchap](https://github.com/mastodon/mastodon/pull/25231)) +- Fix email addresses not being properly updated in `tootctl maintenance fix-duplicates` ([mjankowski](https://github.com/mastodon/mastodon/pull/25118)) +- Fix unicode surrogate pairs sometimes being broken in page title ([eai04191](https://github.com/mastodon/mastodon/pull/25148)) +- Fix various inefficient queries against account domains ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25126)) +- Fix video player offering to expand in a lightbox when it's in an `iframe` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25067)) +- Fix post embed previews ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25071)) +- Fix inadequate error handling in several API controllers when given invalid parameters ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24947), [danielmbrasil](https://github.com/mastodon/mastodon/pull/24958), [danielmbrasil](https://github.com/mastodon/mastodon/pull/25063), [danielmbrasil](https://github.com/mastodon/mastodon/pull/25072), [danielmbrasil](https://github.com/mastodon/mastodon/pull/25386), [danielmbrasil](https://github.com/mastodon/mastodon/pull/25595)) +- Fix uncaught `ActiveRecord::StatementInvalid` in Mastodon::IpBlocksCLI ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24861)) +- Fix various edge cases with local moves ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24812)) +- Fix `tootctl accounts cull` crashing when encountering a domain resolving to a private address ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23378)) +- Fix `tootctl accounts approve --number N` not aproving the N earliest registrations ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24605)) +- Fix being unable to clear media description when editing posts ([c960657](https://github.com/mastodon/mastodon/pull/24720)) +- Fix unavailable translations not falling back to English ([mgmn](https://github.com/mastodon/mastodon/pull/24727)) +- Fix anonymous visitors getting a session cookie on first visit ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24584), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24650), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24664)) +- Fix cutting off first letter of hashtag links sometimes in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24623)) +- Fix crash in `tootctl accounts create --reattach --force` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24557), [danielmbrasil](https://github.com/mastodon/mastodon/pull/24680)) +- Fix characters being emojified even when using Variation Selector 15 (text) ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20949), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24615)) +- Fix uncaught ActiveRecord::StatementInvalid exception in `Mastodon::AccountsCLI#approve` ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24590)) +- Fix email confirmation skip option in `tootctl accounts modify USERNAME --email EMAIL --confirm` ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24578)) +- Fix tooltip for dates without time ([c960657](https://github.com/mastodon/mastodon/pull/24244)) +- Fix missing loading spinner and loading more on scroll in Private Mentions column ([c960657](https://github.com/mastodon/mastodon/pull/24446)) +- Fix account header image missing from `/settings/profile` on narrow screens ([c960657](https://github.com/mastodon/mastodon/pull/24433)) +- Fix height of announcements not being updated when using reduced animations ([c960657](https://github.com/mastodon/mastodon/pull/24354)) +- Fix inconsistent radius in advanced interface drawer ([thislight](https://github.com/mastodon/mastodon/pull/24407)) +- Fix loading more trending posts on scroll in the advanced interface ([OmmyZhang](https://github.com/mastodon/mastodon/pull/24314)) +- Fix poll ending notification for edited polls ([c960657](https://github.com/mastodon/mastodon/pull/24311)) +- Fix max width of media in `/about` and `/privacy-policy` ([mgmn](https://github.com/mastodon/mastodon/pull/24180)) +- Fix streaming API not being usable without `DATABASE_URL` ([Gargron](https://github.com/mastodon/mastodon/pull/23960)) +- Fix external authentication not running onboarding code for new users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23458)) -## [4.0.10] - 2023-09-19 +## [4.1.8] - 2023-09-19 ### Fixed +- Fix post edits not being forwarded as expected ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26936)) - Fix moderator rights inconsistencies ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26729)) - Fix crash when encountering invalid URL ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26814)) - Fix cached posts including stale stats ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26409)) @@ -77,10 +497,10 @@ This means that no security fix will be made available for this branch after thi ### Security -- Fix missing HTML sanitization in translation API (CVE-2023-42452) -- Fix incorrect domain name normalization (CVE-2023-42451) +- Fix missing HTML sanitization in translation API (CVE-2023-42452, [GHSA-2693-xr3m-jhqr](https://github.com/mastodon/mastodon/security/advisories/GHSA-2693-xr3m-jhqr)) +- Fix incorrect domain name normalization (CVE-2023-42451, [GHSA-v3xf-c9qf-j667](https://github.com/mastodon/mastodon/security/advisories/GHSA-v3xf-c9qf-j667)) -## [4.0.9] - 2023-09-05 +## [4.1.7] - 2023-09-05 ### Changed @@ -92,7 +512,7 @@ This means that no security fix will be made available for this branch after thi - Fix `/api/v1/timelines/tag/:hashtag` allowing for unauthenticated access when public preview is disabled ([danielmbrasil](https://github.com/mastodon/mastodon/pull/26237)) - Fix inefficiencies in `PlainTextFormatter` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26727)) -## [4.0.8] - 2023-07-31 +## [4.1.6] - 2023-07-31 ### Fixed @@ -100,7 +520,7 @@ This means that no security fix will be made available for this branch after thi - Fix wrong filters sometimes applying in streaming ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26159), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26213), [renchap](https://github.com/mastodon/mastodon/pull/26233)) - Fix incorrect connect timeout in outgoing requests ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26116)) -## [4.0.7] - 2023-07-21 +## [4.1.5] - 2023-07-21 ### Added @@ -112,7 +532,7 @@ This means that no security fix will be made available for this branch after thi ### Fixed -- Fix moderation interface for remote instances with a .zip TLD ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25886)) +- Fix moderation interface for remote instances with a .zip TLD ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25885)) - Fix remote accounts being possibly persisted to database with incomplete protocol values ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25886)) - Fix trending publishers table not rendering correctly on narrow screens ([vmstan](https://github.com/mastodon/mastodon/pull/25945)) @@ -120,7 +540,7 @@ This means that no security fix will be made available for this branch after thi - Fix CSP headers being unintentionally wide ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26105)) -## [4.0.6] - 2023-07-07 +## [4.1.4] - 2023-07-07 ### Fixed @@ -128,12 +548,18 @@ This means that no security fix will be made available for this branch after thi - Fix crash in admin interface when viewing a remote user with verified links ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25796)) - Fix processing of media files with unusual names ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25788)) -## [4.0.5] - 2023-07-06 +## [4.1.3] - 2023-07-06 + +### Added + +- Add fallback redirection when getting a webfinger query `LOCAL_DOMAIN@LOCAL_DOMAIN` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23600)) ### Changed - Change OpenGraph-based embeds to allow fullscreen ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25058)) +- Change AccessTokensVacuum to also delete expired tokens ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24868)) - Change profile updates to be sent to recently-mentioned servers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24852)) +- Change automatic post deletion thresholds and load detection ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24614)) - Change `/api/v1/statuses/:id/history` to always return at least one item ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25510)) - Change auto-linking to allow carets in URL query params ([renchap](https://github.com/mastodon/mastodon/pull/25216)) @@ -146,12 +572,15 @@ This means that no security fix will be made available for this branch after thi - Fix wrong view being displayed when a webhook fails validation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25464)) - Fix soft-deleted post cleanup scheduler overwhelming the streaming server ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25519)) - Fix incorrect pagination headers in `/api/v2/admin/accounts` ([danielmbrasil](https://github.com/mastodon/mastodon/pull/25477)) +- Fix multiple inefficiencies in automatic post cleanup worker ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24607), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24785), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24840)) - Fix performance of streaming by parsing message JSON once ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25278), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25361)) - Fix CSP headers when `S3_ALIAS_HOST` includes a path component ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25273)) -- Fix `tootctl accounts approve --number N` not aproving N earliest registrations ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24605)) +- Fix `tootctl accounts approve --number N` not approving N earliest registrations ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24605)) +- Fix reports not being closed when performing batch suspensions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24988)) - Fix being able to vote on your own polls ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25015)) - Fix race condition when reblogging a status ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25016)) - Fix “Authorized applications” inefficiently and incorrectly getting last use date ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25060)) +- Fix “Authorized applications” crashing when listing apps with certain admin API scopes ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25713)) - Fix multiple N+1s in ConversationsController ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25134), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25399), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25499)) - Fix user archive takeouts when using OpenStack Swift ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24431)) - Fix searching for remote content by URL not working under certain conditions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25637)) @@ -167,7 +596,7 @@ This means that no security fix will be made available for this branch after thi - Fix arbitrary file creation through media processing (CVE-2023-36460) - Fix possible XSS in preview cards (CVE-2023-36459) -## [4.0.4] - 2023-04-04 +## [4.1.2] - 2023-04-04 ### Fixed @@ -178,26 +607,43 @@ This means that no security fix will be made available for this branch after thi ### Security -- Update Ruby to 3.0.6 due to ReDoS vulnerabilities ([saizai](https://github.com/mastodon/mastodon/pull/24333)) +- Update Ruby to 3.0.6 due to ReDoS vulnerabilities ([saizai](https://github.com/mastodon/mastodon/pull/24334)) - Fix unescaped user input in LDAP query ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24379)) -# [4.0.3] - 2023-03-16 +## [4.1.1] - 2023-03-16 ### Added - Add redirection from paths with url-encoded `@` to their decoded form ([thijskh](https://github.com/mastodon/mastodon/pull/23593)) - Add `lang` attribute to native language names in language picker in Web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23749)) - Add headers to outgoing mails to avoid auto-replies ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23597)) +- Add support for refreshing many accounts at once with `tootctl accounts refresh` ([9p4](https://github.com/mastodon/mastodon/pull/23304)) +- Add confirmation modal when clicking to edit a post with a non-empty compose form ([PauloVilarinho](https://github.com/mastodon/mastodon/pull/23936)) +- Add support for the HAproxy PROXY protocol through the `PROXY_PROTO_V1` environment variable ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24064)) +- Add `SENDFILE_HEADER` environment variable ([Gargron](https://github.com/mastodon/mastodon/pull/24123)) +- Add cache headers to static files served through Rails ([Gargron](https://github.com/mastodon/mastodon/pull/24120)) + +### Changed + +- Increase contrast of upload progress bar background ([toolmantim](https://github.com/mastodon/mastodon/pull/23836)) +- Change post auto-deletion throttling constants to better scale with server size ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23320)) +- Change order of bookmark and favourite sidebar entries in single-column UI for consistency ([TerryGarcia](https://github.com/mastodon/mastodon/pull/23701)) +- Change `ActivityPub::DeliveryWorker` retries to be spread out more ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21956)) ### Fixed -- Fix “Remove all followers from the selected domains” being more destructive than it claims ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23805)) +- Fix “Remove all followers from the selected domains” also removing follows and notifications ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23805)) +- Fix streaming metrics format ([emilweth](https://github.com/mastodon/mastodon/pull/23519), [emilweth](https://github.com/mastodon/mastodon/pull/23520)) - Fix case-sensitive check for previously used hashtags in hashtag autocompletion ([deanveloper](https://github.com/mastodon/mastodon/pull/23526)) +- Fix focus point of already-attached media not saving after edit ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23566)) - Fix sidebar behavior in settings/admin UI on mobile ([wxt2005](https://github.com/mastodon/mastodon/pull/23764)) - Fix inefficiency when searching accounts per username in admin interface ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23801)) +- Fix duplicate “Publish” button on mobile ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23804)) - Fix server error when failing to follow back followers from `/relationships` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23787)) - Fix server error when attempting to display the edit history of a trendable post in the admin interface ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23574)) +- Fix `tootctl accounts migrate` crashing because of a typo ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23567)) - Fix original account being unfollowed on migration before the follow request to the new account could be sent ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21957)) +- Fix the “Back” button in column headers sometimes leaving Mastodon ([c960657](https://github.com/mastodon/mastodon/pull/23953)) - Fix pgBouncer resetting application name on every transaction ([Gargron](https://github.com/mastodon/mastodon/pull/23958)) - Fix unconfirmed accounts being counted as active users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23803)) - Fix `/api/v1/streaming` sub-paths not being redirected ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23988)) @@ -208,13 +654,230 @@ This means that no security fix will be made available for this branch after thi - Fix dashboard crash on ElasticSearch server error ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23751)) - Fix incorrect post links in strikes when the account is remote ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23611)) - Fix misleading error code when receiving invalid WebAuthn credentials ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23568)) +- Fix duplicate mails being sent when the SMTP server is too slow to close the connection ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23750)) ### Security - Change user backups to use expiring URLs for download when possible ([Gargron](https://github.com/mastodon/mastodon/pull/24136)) - Add warning for object storage misconfiguration ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24137)) +## [4.1.0] - 2023-02-10 + +### Added + +- **Add support for importing/exporting server-wide domain blocks** ([enbylenore](https://github.com/mastodon/mastodon/pull/20597), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/21471), [dariusk](https://github.com/mastodon/mastodon/pull/22803), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/21470)) +- **Add listing of followed hashtags** ([connorshea](https://github.com/mastodon/mastodon/pull/21773)) +- **Add support for editing media description and focus point of already-sent posts** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20878)) + - Previously, you could add and remove attachments, but not edit media description of already-attached media + - REST API changes: + - `PUT /api/v1/statuses/:id` now takes an extra `media_attributes[]` array parameter with the `id` of the updated media and their updated `description`, `focus`, and `thumbnail` +- **Add follow request banner on account header** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20785)) + - REST API changes: + - `Relationship` entities have an extra `requested_by` boolean attribute representing whether the represented user has requested to follow you +- **Add confirmation screen when handling reports** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22375), [Gargron](https://github.com/mastodon/mastodon/pull/23156), [tribela](https://github.com/mastodon/mastodon/pull/23178)) +- Add option to make the landing page be `/about` even when trends are enabled ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20808)) +- Add `noindex` setting back to the admin interface ([prplecake](https://github.com/mastodon/mastodon/pull/22205)) +- Add instance peers API endpoint toggle back to the admin interface ([dariusk](https://github.com/mastodon/mastodon/pull/22810)) +- Add instance activity API endpoint toggle back to the admin interface ([dariusk](https://github.com/mastodon/mastodon/pull/22833)) +- Add setting for status page URL ([Gargron](https://github.com/mastodon/mastodon/pull/23390), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23499)) + - REST API changes: + - Add `configuration.urls.status` attribute to the object returned by `GET /api/v2/instance` +- Add `account.approved` webhook ([Saiv46](https://github.com/mastodon/mastodon/pull/22938)) +- Add 12 hours option to polls ([Pleclown](https://github.com/mastodon/mastodon/pull/21131)) +- Add dropdown menu item to open admin interface for remote domains ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21895)) +- Add `--remove-headers`, `--prune-profiles` and `--include-follows` flags to `tootctl media remove` ([evanphilip](https://github.com/mastodon/mastodon/pull/22149)) +- Add `--email` and `--dry-run` options to `tootctl accounts delete` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22328)) +- Add `tootctl accounts migrate` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22330)) +- Add `tootctl accounts prune` ([tribela](https://github.com/mastodon/mastodon/pull/18397)) +- Add `tootctl domains purge` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22063)) +- Add `SIDEKIQ_CONCURRENCY` environment variable ([muffinista](https://github.com/mastodon/mastodon/pull/19589)) +- Add `DB_POOL` environment variable support for streaming server ([Gargron](https://github.com/mastodon/mastodon/pull/23470)) +- Add `MIN_THREADS` environment variable to set minimum Puma threads ([jimeh](https://github.com/mastodon/mastodon/pull/21048)) +- Add explanation text to log-in page ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20946)) +- Add user profile OpenGraph tag on post pages ([bramus](https://github.com/mastodon/mastodon/pull/21423)) +- Add maskable icon support for Android ([workeffortwaste](https://github.com/mastodon/mastodon/pull/20904)) +- Add Belarusian to supported languages ([Mixaill](https://github.com/mastodon/mastodon/pull/22022)) +- Add Western Frisian to supported languages ([ykzts](https://github.com/mastodon/mastodon/pull/18602)) +- Add Montenegrin to the language picker ([ayefries](https://github.com/mastodon/mastodon/pull/21013)) +- Add Southern Sami and Lule Sami to the language picker ([Jullan-M](https://github.com/mastodon/mastodon/pull/21262)) +- Add logging for Rails cache timeouts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21667)) +- Add color highlight for active hashtag “follow” button ([MFTabriz](https://github.com/mastodon/mastodon/pull/21629)) +- Add brotli compression to `assets:precompile` ([Izorkin](https://github.com/mastodon/mastodon/pull/19025)) +- Add “disabled” account filter to the `/admin/accounts` UI ([tribela](https://github.com/mastodon/mastodon/pull/21282)) +- Add transparency to modal background for accessibility ([edent](https://github.com/mastodon/mastodon/pull/18081)) +- Add `lang` attribute to image description textarea and poll option field ([c960657](https://github.com/mastodon/mastodon/pull/23293)) +- Add `spellcheck` attribute to Content Warning and poll option input fields ([c960657](https://github.com/mastodon/mastodon/pull/23395)) +- Add `title` attribute to video elements in media attachments ([bramus](https://github.com/mastodon/mastodon/pull/21420)) +- Add left and right margins to emojis ([dsblank](https://github.com/mastodon/mastodon/pull/20464)) +- Add `roles` attribute to `Account` entities in REST API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23255), [tribela](https://github.com/mastodon/mastodon/pull/23428)) +- Add `reading:autoplay:gifs` to `/api/v1/preferences` ([j-f1](https://github.com/mastodon/mastodon/pull/22706)) +- Add `hide_collections` parameter to `/api/v1/accounts/credentials` ([CarlSchwan](https://github.com/mastodon/mastodon/pull/22790)) +- Add `policy` attribute to web push subscription objects in REST API at `/api/v1/push/subscriptions` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23210)) +- Add metrics endpoint to streaming API ([Gargron](https://github.com/mastodon/mastodon/pull/23388), [Gargron](https://github.com/mastodon/mastodon/pull/23469)) +- Add more specific error messages to HTTP signature verification ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21617)) +- Add Storj DCS to cloud object storage options in the `mastodon:setup` rake task ([jtolio](https://github.com/mastodon/mastodon/pull/21929)) +- Add checkmark symbol in the checkbox for sensitive media ([sidp](https://github.com/mastodon/mastodon/pull/22795)) +- Add missing accessibility attributes to logout link in modals ([kytta](https://github.com/mastodon/mastodon/pull/22549)) +- Add missing accessibility attributes to “Hide image” button in `MediaGallery` ([hs4man21](https://github.com/mastodon/mastodon/pull/22513)) +- Add missing accessibility attributes to hide content warning field when disabled ([hs4man21](https://github.com/mastodon/mastodon/pull/22568)) +- Add `aria-hidden` to footer circle dividers to improve accessibility ([hs4man21](https://github.com/mastodon/mastodon/pull/22576)) +- Add `lang` attribute to compose form inputs ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23240)) + +### Changed + +- **Ensure exact match is the first result in hashtag searches** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21315)) +- Change account search to return followed accounts first ([dariusk](https://github.com/mastodon/mastodon/pull/22956)) +- Change batch account suspension to create a strike ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20897)) +- Change default reply language to match the default language when replying to a translated post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22272)) +- Change misleading wording about waitlists ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20850)) +- Increase width of the unread notification border ([connorshea](https://github.com/mastodon/mastodon/pull/21692)) +- Change new post notification button on profiles to make it more apparent when it is enabled ([tribela](https://github.com/mastodon/mastodon/pull/22541)) +- Change trending tags admin interface to always show batch action controls ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23013)) +- Change wording of some OAuth scope descriptions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22491)) +- Change wording of admin report handling actions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18388)) +- Change confirm prompts for relationships management ([tribela](https://github.com/mastodon/mastodon/pull/19411)) +- Change language surrounding disability in prompts for media descriptions ([hs4man21](https://github.com/mastodon/mastodon/pull/20923)) +- Change confusing wording in the sign in banner ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22490)) +- Change `POST /settings/applications/:id` to regenerate token on scopes change ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23359)) +- Change account moderation notes to make links clickable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22553)) +- Change link previews for statuses to never use avatar as fallback ([Gargron](https://github.com/mastodon/mastodon/pull/23376)) +- Change email address input to be read-only for logged-in users when requesting a new confirmation e-mail ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23247)) +- Change notifications per page from 15 to 40 in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/23348)) +- Change number of stored items in home feed from 400 to 800 ([Gargron](https://github.com/mastodon/mastodon/pull/23349)) +- Change API rate limits from 300/5min per user to 1500/5min per user, 300/5min per app ([Gargron](https://github.com/mastodon/mastodon/pull/23347)) +- Save avatar or header correctly even if the other one fails ([tribela](https://github.com/mastodon/mastodon/pull/18465)) +- Change `referrer-policy` to `same-origin` application-wide ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23014), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23037)) +- Add 'private' to `Cache-Control`, match Rails expectations ([daxtens](https://github.com/mastodon/mastodon/pull/20608)) +- Make the button that expands the compose form differentiable from the button that publishes a post ([Tak](https://github.com/mastodon/mastodon/pull/20864)) +- Change automatic post deletion configuration to be accessible to moved users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20774)) +- Make tag following idempotent ([trwnh](https://github.com/mastodon/mastodon/pull/20860), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/21285)) +- Use buildx functions for faster builds ([inductor](https://github.com/mastodon/mastodon/pull/20692)) +- Split off Dockerfile components for faster builds ([moritzheiber](https://github.com/mastodon/mastodon/pull/20933), [ineffyble](https://github.com/mastodon/mastodon/pull/20948), [BtbN](https://github.com/mastodon/mastodon/pull/21028)) +- Change last occurrence of “silence” to “limit” in UI text ([cincodenada](https://github.com/mastodon/mastodon/pull/20637)) +- Change “hide toot” to “hide post” ([seanthegeek](https://github.com/mastodon/mastodon/pull/22385)) +- Don't allow URLs that contain non-normalized paths to be verified ([dgl](https://github.com/mastodon/mastodon/pull/20999)) +- Change the “Trending now” header to be a link to the Explore page ([connorshea](https://github.com/mastodon/mastodon/pull/21759)) +- Change PostgreSQL connection timeout from 2 minutes to 15 seconds ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21790)) +- Make handle more easily selectable on profile page ([cadars](https://github.com/mastodon/mastodon/pull/21479)) +- Allow admins to refresh remotely-suspended accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22327)) +- Change dropdown menu to contain “Copy link to post” even for non-public posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21316)) +- Allow adding relays in secure mode and limited federation mode ([ineffyble](https://github.com/mastodon/mastodon/pull/22324)) +- Change timestamps to be displayed using the user's timezone throughout the moderation interface ([FrancisMurillo](https://github.com/mastodon/mastodon/pull/21878), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/22555)) +- Change CSP directives on API to be tight and concise ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20960)) +- Change web UI to not autofocus the compose form ([raboof](https://github.com/mastodon/mastodon/pull/16517), [Akkiesoft](https://github.com/mastodon/mastodon/pull/23094)) +- Change idempotency key handling for posting when database access is slow ([lambda](https://github.com/mastodon/mastodon/pull/21840)) +- Change remote media files to be downloaded outside of transactions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21796)) +- Improve contrast of charts in “poll has ended” notifications ([j-f1](https://github.com/mastodon/mastodon/pull/22575)) +- Change OEmbed detection and validation to be somewhat more lenient ([ineffyble](https://github.com/mastodon/mastodon/pull/22533)) +- Widen ElasticSearch version detection to not display a warning for OpenSearch ([VyrCossont](https://github.com/mastodon/mastodon/pull/22422), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23064)) +- Change link verification to allow pages larger than 1MB as long as the link is in the first 1MB ([untitaker](https://github.com/mastodon/mastodon/pull/22879)) +- Update default Node.js version to Node.js 16 ([ineffyble](https://github.com/mastodon/mastodon/pull/22223), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/22342)) + +### Removed + +- Officially remove support for Ruby 2.6 ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21477)) +- Remove `object-fit` polyfill used for old versions of Microsoft Edge ([shuuji3](https://github.com/mastodon/mastodon/pull/22693)) +- Remove `intersection-observer` polyfill for old Safari support ([shuuji3](https://github.com/mastodon/mastodon/pull/23284)) +- Remove empty `title` tag from mailer layout ([nametoolong](https://github.com/mastodon/mastodon/pull/23078)) +- Remove post count and last posts from ActivityPub representation of hashtag collections ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23460)) + +### Fixed + +- **Fix changing domain block severity not undoing individual account effects** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22135)) +- Fix suspension worker crashing on S3-compatible setups without ACL support ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22487)) +- Fix possible race conditions when suspending/unsuspending accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22363)) +- Fix being stuck in edit mode when deleting the edited posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22126)) +- Fix attached media uploads not being cleared when replying to a post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23504)) +- Fix filters not being applied to some notification types ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23211)) +- Fix incorrect link in push notifications for some event types ([elizabeth-dev](https://github.com/mastodon/mastodon/pull/23286)) +- Fix some performance issues with `/admin/instances` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21907)) +- Fix some pre-4.0 admin audit logs ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22091)) +- Fix moderation audit log items for warnings having incorrect links ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23242)) +- Fix account activation being sometimes triggered before email confirmation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23245)) +- Fix missing OAuth scopes for admin APIs ([trwnh](https://github.com/mastodon/mastodon/pull/20918), [trwnh](https://github.com/mastodon/mastodon/pull/20979)) +- Fix voter count not being cleared when a poll is reset ([afontenot](https://github.com/mastodon/mastodon/pull/21700)) +- Fix attachments of edited posts not being fetched ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21565)) +- Fix irreversible and whole_word parameters handling in `/api/v1/filters` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21988)) +- Fix 500 error when marking posts as sensitive while some of them are deleted ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22134)) +- Fix expanded posts not always being scrolled into view ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21797)) +- Fix not being able to scroll the remote interaction modal on small screens ([xendke](https://github.com/mastodon/mastodon/pull/21763)) +- Fix not being able to scroll in post history modal ([cadars](https://github.com/mastodon/mastodon/pull/23396)) +- Fix audio player volume control on Safari ([minacle](https://github.com/mastodon/mastodon/pull/23187)) +- Fix disappearing “Explore” tabs on Safari ([nyura](https://github.com/mastodon/mastodon/pull/20917), [ykzts](https://github.com/mastodon/mastodon/pull/20982)) +- Fix wrong padding in RTL layout ([Gargron](https://github.com/mastodon/mastodon/pull/23157)) +- Fix drag & drop upload area display in single-column mode ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23217)) +- Fix being unable to get a single EmailDomainBlock from the admin API ([trwnh](https://github.com/mastodon/mastodon/pull/20846)) +- Fix admin-set follow recommandations being case-sensitive ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23500)) +- Fix unserialized `role` on account entities in admin API ([Gargron](https://github.com/mastodon/mastodon/pull/23290)) +- Fix pagination of followed tags ([trwnh](https://github.com/mastodon/mastodon/pull/20861)) +- Fix dropdown menu positions when scrolling ([sidp](https://github.com/mastodon/mastodon/pull/22916), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23062)) +- Fix email with empty domain name labels passing validation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23246)) +- Fix mysterious registration failure when “Require a reason to join” is set with open registrations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22127)) +- Fix attachment rendering of edited posts in OpenGraph ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22270)) +- Fix invalid/empty RSS feed link on account pages ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20772)) +- Fix error in `VerifyLinkService` when processing links with no href ([joshuap](https://github.com/mastodon/mastodon/pull/20741)) +- Fix error in `VerifyLinkService` when processing links with invalid URLs ([untitaker](https://github.com/mastodon/mastodon/pull/23204)) +- Fix media uploads with FFmpeg 5 ([dead10ck](https://github.com/mastodon/mastodon/pull/21191)) +- Fix sensitive flag not being set when replying to a post with a content warning under certain conditions ([kedamaDQ](https://github.com/mastodon/mastodon/pull/21724)) +- Fix misleading message briefly showing up when loading follow requests under some conditions ([c960657](https://github.com/mastodon/mastodon/pull/23386)) +- Fix “Share @:user's profile” profile menu item not working ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21490)) +- Fix crash and incorrect behavior in `tootctl domains crawl` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19004)) +- Fix autoplay on iOS ([jamesadney](https://github.com/mastodon/mastodon/pull/21422)) +- Fix user clean-up scheduler crash when an unconfirmed account has a moderation note ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23318)) +- Fix spaces not being stripped in admin account search ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21324)) +- Fix spaces not being stripped when adding relays ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22655)) +- Fix infinite loading spinner instead of soft 404 for non-existing remote accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21303)) +- Fix minor visual issue with the top border of verified account fields ([j-f1](https://github.com/mastodon/mastodon/pull/22006)) +- Fix pending account approval and rejection not being recorded in the admin audit log ([FrancisMurillo](https://github.com/mastodon/mastodon/pull/22088)) +- Fix “Sign up” button with closed registrations not opening modal on mobile ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22060)) +- Fix UI header overflowing on mobile ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21783)) +- Fix 500 error when trying to migrate to an invalid address ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21462)) +- Fix crash when trying to fetch unobtainable avatar of user using external authentication ([lochiiconnectivity](https://github.com/mastodon/mastodon/pull/22462)) +- Fix processing error on incoming malformed JSON-LD under some situations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23416)) +- Fix potential duplicate posts in Explore tab ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22121)) +- Fix deprecation warning in `tootctl accounts rotate` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22120)) +- Fix styling of featured tags in light theme ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23252)) +- Fix missing style in warning and strike cards ([AtelierSnek](https://github.com/mastodon/mastodon/pull/22177), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/22302)) +- Fix wasteful request to `/api/v1/custom_emojis` when not logged in ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22326)) +- Fix replies sometimes being delivered to user-blocked domains ([tribela](https://github.com/mastodon/mastodon/pull/22117)) +- Fix admin dashboard crash when using some ElasticSearch replacements ([cortices](https://github.com/mastodon/mastodon/pull/21006)) +- Fix profile avatar being slightly offset into left border ([RiedleroD](https://github.com/mastodon/mastodon/pull/20994)) +- Fix N+1 queries in `NotificationsController` ([nametoolong](https://github.com/mastodon/mastodon/pull/21202)) +- Fix being unable to react to announcements with the keycap number sign emoji ([kescherCode](https://github.com/mastodon/mastodon/pull/22231)) +- Fix height computation of post embeds ([hodgesmr](https://github.com/mastodon/mastodon/pull/22141)) +- Fix accessibility issue of the search bar due to hidden placeholder ([alexstine](https://github.com/mastodon/mastodon/pull/21275)) +- Fix layout change handler not being removed due to a typo ([nschonni](https://github.com/mastodon/mastodon/pull/21829)) +- Fix typo in the default `S3_HOSTNAME` used in the `mastodon:setup` rake task ([danp](https://github.com/mastodon/mastodon/pull/19932)) +- Fix the top action bar appearing in the multi-column layout ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20943)) +- Fix inability to use local LibreTranslate without setting `ALLOWED_PRIVATE_ADDRESSES` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21926)) +- Fix punycoded local domains not being prettified in initial state ([Tritlo](https://github.com/mastodon/mastodon/pull/21440)) +- Fix CSP violation warning by removing inline CSS from SVG logo ([luxiaba](https://github.com/mastodon/mastodon/pull/20814)) +- Fix margin for search field on medium window size ([minacle](https://github.com/mastodon/mastodon/pull/21606)) +- Fix search popout scrolling with the page in single-column mode ([rgroothuijsen](https://github.com/mastodon/mastodon/pull/16463)) +- Fix minor post cache hydration discrepancy ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19879)) +- Fix `・` detection in hashtags ([parthoghosh24](https://github.com/mastodon/mastodon/pull/22888)) +- Fix hashtag follows bypassing user blocks ([tribela](https://github.com/mastodon/mastodon/pull/22849)) +- Fix moved accounts being incorrectly redirected to account settings when trying to view a remote profile ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22497)) +- Fix site upload validations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22479)) +- Fix “Add new domain block” button using last submitted search value instead of the current one ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22485)) +- Fix misleading hashtag warning when posting with “Followers only” or “Mentioned people only” visibility ([n0toose](https://github.com/mastodon/mastodon/pull/22827)) +- Fix embedded posts with videos grabbing focus ([Akkiesoft](https://github.com/mastodon/mastodon/pull/22778)) +- Fix `$` not being escaped in `.env.production` files generated by the `mastodon:setup` rake task ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23012), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23072)) +- Fix sanitizer parsing link text as HTML when stripping unsupported links ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22558)) +- Fix `scheduled_at` input not using `datetime-local` when editing announcements ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21896)) +- Fix REST API serializer for `Account` not including `moved` when the moved account has itself moved ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22483)) +- Fix `/api/v1/admin/trends/tags` using wrong serializer ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18943)) +- Fix situations in which instance actor can be set to a Mastodon-incompatible name ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22307)) + +### Security + +- Add `form-action` CSP directive ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20781), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/20958), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/20962)) +- Fix unbounded recursion in account discovery ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22025)) +- Revoke all authorized applications on password reset ([FrancisMurillo](https://github.com/mastodon/mastodon/pull/21325)) +- Fix unbounded recursion in post discovery ([ClearlyClaire,nametoolong](https://github.com/mastodon/mastodon/pull/23506)) + ## [4.0.2] - 2022-11-15 + ### Fixed - Fix wrong color on mentions hidden behind content warning in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/20724)) @@ -222,6 +885,7 @@ This means that no security fix will be made available for this branch after thi - Fix `unsafe-eval` being used when `wasm-unsafe-eval` is enough in Content Security Policy ([Gargron](https://github.com/mastodon/mastodon/pull/20729), [prplecake](https://github.com/mastodon/mastodon/pull/20606)) ## [4.0.1] - 2022-11-14 + ### Fixed - Fix nodes order being sometimes mangled when rewriting emoji ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20677)) @@ -425,6 +1089,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix out-of-bound reads in blurhash transcoder ([delroth](https://github.com/mastodon/mastodon/pull/20388)) ## [3.5.3] - 2022-05-26 + ### Added - **Add language dropdown to compose form in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/18420), [ykzts](https://github.com/mastodon/mastodon/pull/18460)) @@ -472,6 +1137,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix confirmation redirect to app without `Location` header ([Gargron](https://github.com/mastodon/mastodon/pull/18523)) ## [3.5.2] - 2022-05-04 + ### Added - Add warning on direct messages screen in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/18289)) @@ -524,6 +1190,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix error in alias settings page ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18004)) ## [3.5.1] - 2022-04-08 + ### Added - Add pagination for trending statuses in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/17976)) @@ -567,6 +1234,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix error when indexing statuses into Elasticsearch ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17912)) ## [3.5.0] - 2022-03-30 + ### Added - **Add support for incoming edited posts** ([Gargron](https://github.com/mastodon/mastodon/pull/16697), [Gargron](https://github.com/mastodon/mastodon/pull/17727), [Gargron](https://github.com/mastodon/mastodon/pull/17728), [Gargron](https://github.com/mastodon/mastodon/pull/17320), [Gargron](https://github.com/mastodon/mastodon/pull/17404), [Gargron](https://github.com/mastodon/mastodon/pull/17390), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17335), [Gargron](https://github.com/mastodon/mastodon/pull/17696), [Gargron](https://github.com/mastodon/mastodon/pull/17745), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17740), [Gargron](https://github.com/mastodon/mastodon/pull/17697), [Gargron](https://github.com/mastodon/mastodon/pull/17648), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17531), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17499), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17498), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17380), [Gargron](https://github.com/mastodon/mastodon/pull/17373), [Gargron](https://github.com/mastodon/mastodon/pull/17334), [Gargron](https://github.com/mastodon/mastodon/pull/17333), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17699), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17748)) @@ -766,6 +1434,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix being able to bypass e-mail restrictions ([Gargron](https://github.com/mastodon/mastodon/pull/17909)) ## [3.4.6] - 2022-02-03 + ### Fixed - Fix `mastodon:webpush:generate_vapid_key` task requiring a functional environment ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17338)) @@ -780,6 +1449,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Disable legacy XSS filtering ([Wonderfall](https://github.com/mastodon/mastodon/pull/17289)) ## [3.4.5] - 2022-01-31 + ### Added - Add more advanced migration tests ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17393)) @@ -794,6 +1464,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix followers synchronization mechanism ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16510)) ## [3.4.4] - 2021-11-26 + ### Fixed - Fix error when suspending user with an already blocked canonical email ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17036)) @@ -811,11 +1482,13 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix handling of recursive toots in WebUI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17041)) ## [3.4.3] - 2021-11-06 + ### Fixed - Fix login being broken due to inaccurately applied backport fix in 3.4.2 ([Gargron](https://github.com/mastodon/mastodon/commit/5c47a18c8df3231aa25c6d1f140a71a7fac9cbf9)) ## [3.4.2] - 2021-11-06 + ### Added - Add `configuration` attribute to `GET /api/v1/instance` ([Gargron](https://github.com/mastodon/mastodon/pull/16485)) @@ -859,6 +1532,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix revoking a specific session not working ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16943)) ## [3.4.1] - 2021-06-03 + ### Added - Add new emoji assets from Twemoji 13.1.0 ([Gargron](https://github.com/mastodon/mastodon/pull/16345)) @@ -878,6 +1552,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix mailer jobs for deleted notifications erroring out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16294)) ## [3.4.0] - 2021-05-16 + ### Added - **Add follow recommendations for onboarding** ([Gargron](https://github.com/mastodon/mastodon/pull/15945), [Gargron](https://github.com/mastodon/mastodon/pull/16161), [Gargron](https://github.com/mastodon/mastodon/pull/16060), [Gargron](https://github.com/mastodon/mastodon/pull/16077), [Gargron](https://github.com/mastodon/mastodon/pull/16078), [Gargron](https://github.com/mastodon/mastodon/pull/16160), [Gargron](https://github.com/mastodon/mastodon/pull/16079), [noellabo](https://github.com/mastodon/mastodon/pull/16044), [noellabo](https://github.com/mastodon/mastodon/pull/16045), [Gargron](https://github.com/mastodon/mastodon/pull/16152), [Gargron](https://github.com/mastodon/mastodon/pull/16153), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16082), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16173), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16159), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/16189)) @@ -913,7 +1588,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - This method allows an app through which a user signed-up to request a new confirmation e-mail to be sent, or to change the e-mail of the account before it is confirmed - Add `GET /api/v1/accounts/lookup` to REST API ([Gargron](https://github.com/mastodon/mastodon/pull/15740), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/15750)) - This method allows to quickly convert a username of a known account to an ID that can be used with the REST API, or to check if a username is available - for sign-up + for sign-up - Add `policy` param to `POST /api/v1/push/subscriptions` in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/16040)) - This param allows an app to control from whom notifications should be delivered as push notifications to the app - Add `details` to error response for `POST /api/v1/accounts` in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/15803)) @@ -1023,6 +1698,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix app name, website and redirect URIs not having a maximum length ([Gargron](https://github.com/mastodon/mastodon/pull/16042)) ## [3.3.0] - 2020-12-27 + ### Added - **Add hotkeys for audio/video control in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/15158), [Gargron](https://github.com/mastodon/mastodon/pull/15198)) @@ -1199,6 +1875,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix resolving accounts sometimes creating duplicate records for a given ActivityPub identifier ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/15364)) ## [3.2.2] - 2020-12-19 + ### Added - Add `tootctl maintenance fix-duplicates` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14860), [Gargron](https://github.com/mastodon/mastodon/pull/15223)) @@ -1225,6 +1902,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix resolving accounts sometimes creating duplicate records for a given ActivityPub identifier ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/15364)) ## [3.2.1] - 2020-10-19 + ### Added - Add support for latest HTTP Signatures spec draft ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14556)) @@ -1254,6 +1932,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix files served as `application/octet-stream` being rejected without attempting mime type detection ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14452)) ## [3.2.0] - 2020-07-27 + ### Added - Add `SMTP_SSL` environment variable ([OmmyZhang](https://github.com/mastodon/mastodon/pull/14309)) @@ -1389,7 +2068,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix unique username constraint for local users not being enforced in database ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14099)) - Fix unnecessary gap under video modal in web UI ([mfmfuyu](https://github.com/mastodon/mastodon/pull/14098)) - Fix 2FA and sign in token pages not respecting user locale ([mfmfuyu](https://github.com/mastodon/mastodon/pull/14087)) -- Fix unapproved users being able to view profiles when in limited-federation mode *and* requiring approval for sign-ups ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14093)) +- Fix unapproved users being able to view profiles when in limited-federation mode _and_ requiring approval for sign-ups ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14093)) - Fix initial audio volume not corresponding to what's displayed in audio player in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14057)) - Fix timelines sometimes jumping when closing modals in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14019)) - Fix memory usage of downloading remote files ([Gargron](https://github.com/mastodon/mastodon/pull/14184), [Gargron](https://github.com/mastodon/mastodon/pull/14181), [noellabo](https://github.com/mastodon/mastodon/pull/14356)) @@ -1407,6 +2086,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Clear out media attachments in a separate worker (slow) ## [3.1.5] - 2020-07-07 + ### Security - Fix media attachment enumeration ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/14254)) @@ -1414,6 +2094,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix other sessions not being logged out on password change ([Gargron](https://github.com/mastodon/mastodon/pull/14252)) ## [3.1.4] - 2020-05-14 + ### Added - Add `vi` to available locales ([taicv](https://github.com/mastodon/mastodon/pull/13542)) @@ -1452,7 +2133,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix regression in `tootctl media remove-orphans` ([Gargron](https://github.com/mastodon/mastodon/pull/13405)) - Fix old unique jobs digests not having been cleaned up ([Gargron](https://github.com/mastodon/mastodon/pull/13683)) - Fix own following/followers not showing muted users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13614)) -- Fix list of followed people ignoring sorting on Follows & Followers page ([taras2358](https://github.com/mastodon/mastodon/pull/13676)) +- Fix list of followed people ignoring sorting on Follows & Followers page ([taras2358](https://github.com/mastodon/mastodon/pull/13676)) - Fix wrong pgHero Content-Security-Policy when `CDN_HOST` is set ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13595)) - Fix needlessly deduplicating usernames on collisions with remote accounts when signing-up through SAML/CAS ([kaiyou](https://github.com/mastodon/mastodon/pull/13581)) - Fix page incorrectly scrolling when bringing up dropdown menus in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13574)) @@ -1481,6 +2162,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - The issue only affects developers of apps who are shared between multiple users, such as server-side apps like cross-posters ## [3.1.3] - 2020-04-05 + ### Added - Add ability to filter audit log in admin UI ([Gargron](https://github.com/mastodon/mastodon/pull/13381)) @@ -1554,6 +2236,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix re-sending of e-mail confirmation not being rate limited ([Gargron](https://github.com/mastodon/mastodon/pull/13360)) ## [v3.1.2] - 2020-02-27 + ### Added - Add `--reset-password` option to `tootctl accounts modify` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/13126)) @@ -1580,11 +2263,13 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix leak of arbitrary statuses through unfavourite action in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/13161)) ## [3.1.1] - 2020-02-10 + ### Fixed - Fix yanked dependency preventing installation ([mayaeh](https://github.com/mastodon/mastodon/pull/13059)) ## [3.1.0] - 2020-02-09 + ### Added - Add bookmarks ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/7107), [Gargron](https://github.com/mastodon/mastodon/pull/12494), [Gomasy](https://github.com/mastodon/mastodon/pull/12381)) @@ -1749,6 +2434,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix settings pages being cacheable by the browser ([Gargron](https://github.com/mastodon/mastodon/pull/12714)) ## [3.0.1] - 2019-10-10 + ### Added - Add `tootctl media usage` command ([Gargron](https://github.com/mastodon/mastodon/pull/12115)) @@ -1782,6 +2468,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix `tootctl accounts cull` advertising unused option flag ([Kjwon15](https://github.com/mastodon/mastodon/pull/12074)) ## [3.0.0] - 2019-10-03 + ### Added - Add "not available" label to unloaded media attachments in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/11715), [Gargron](https://github.com/mastodon/mastodon/pull/11745)) @@ -1978,6 +2665,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix performance of GIF re-encoding and always strip EXIF data from videos ([Gargron](https://github.com/mastodon/mastodon/pull/12057)) ## [2.9.3] - 2019-08-10 + ### Added - Add GIF and WebP support for custom emojis ([Gargron](https://github.com/mastodon/mastodon/pull/11519)) @@ -2037,6 +2725,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix blocked domains still being able to fill database with account records ([Gargron](https://github.com/mastodon/mastodon/pull/11219)) ## [2.9.2] - 2019-06-22 + ### Added - Add `short_description` and `approval_required` to `GET /api/v1/instance` ([Gargron](https://github.com/mastodon/mastodon/pull/11146)) @@ -2051,6 +2740,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix audio not being downloaded from remote servers ([Gargron](https://github.com/mastodon/mastodon/pull/11145)) ## [2.9.1] - 2019-06-22 + ### Added - Add moderation API ([Gargron](https://github.com/mastodon/mastodon/pull/9387)) @@ -2076,6 +2766,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix scrolling behaviour in compose form ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/11093)) ## [2.9.0] - 2019-06-13 + ### Added - **Add single-column mode in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/10807), [Gargron](https://github.com/mastodon/mastodon/pull/10848), [Gargron](https://github.com/mastodon/mastodon/pull/11003), [Gargron](https://github.com/mastodon/mastodon/pull/10961), [Hanage999](https://github.com/mastodon/mastodon/pull/10915), [noellabo](https://github.com/mastodon/mastodon/pull/10917), [abcang](https://github.com/mastodon/mastodon/pull/10859), [Gargron](https://github.com/mastodon/mastodon/pull/10820), [Gargron](https://github.com/mastodon/mastodon/pull/10835), [Gargron](https://github.com/mastodon/mastodon/pull/10809), [Gargron](https://github.com/mastodon/mastodon/pull/10963), [noellabo](https://github.com/mastodon/mastodon/pull/10883), [Hanage999](https://github.com/mastodon/mastodon/pull/10839)) @@ -2130,6 +2821,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix login sometimes redirecting to paths that are not pages ([Gargron](https://github.com/mastodon/mastodon/pull/11019)) ## [2.8.4] - 2019-05-24 + ### Fixed - Fix delivery not retrying on some inbox errors that should be retriable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10812)) @@ -2141,6 +2833,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Require specific OAuth scopes for specific endpoints of the streaming API, instead of merely requiring a token for all endpoints, and allow using WebSockets protocol negotiation to specify the access token instead of using a query string ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10818)) ## [2.8.3] - 2019-05-19 + ### Added - Add `og:image:alt` OpenGraph tag ([BenLubar](https://github.com/mastodon/mastodon/pull/10779)) @@ -2163,6 +2856,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix "invited by" not showing up in admin UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10791)) ## [2.8.2] - 2019-05-05 + ### Added - Add `SOURCE_TAG` environment variable ([ushitora-anqou](https://github.com/mastodon/mastodon/pull/10698)) @@ -2175,6 +2869,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix closing video modal scrolling timelines to top ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10695)) ## [2.8.1] - 2019-05-04 + ### Added - Add link to existing domain block when trying to block an already-blocked domain ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10663)) @@ -2214,6 +2909,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix confirmation modals being too narrow for a secondary action button ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10586)) ## [2.8.0] - 2019-04-10 + ### Added - Add polls ([Gargron](https://github.com/mastodon/mastodon/pull/10111), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10155), [Gargron](https://github.com/mastodon/mastodon/pull/10184), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10196), [Gargron](https://github.com/mastodon/mastodon/pull/10248), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10255), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10322), [Gargron](https://github.com/mastodon/mastodon/pull/10138), [Gargron](https://github.com/mastodon/mastodon/pull/10139), [Gargron](https://github.com/mastodon/mastodon/pull/10144), [Gargron](https://github.com/mastodon/mastodon/pull/10145),[Gargron](https://github.com/mastodon/mastodon/pull/10146), [Gargron](https://github.com/mastodon/mastodon/pull/10148), [Gargron](https://github.com/mastodon/mastodon/pull/10151), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10150), [Gargron](https://github.com/mastodon/mastodon/pull/10168), [Gargron](https://github.com/mastodon/mastodon/pull/10165), [Gargron](https://github.com/mastodon/mastodon/pull/10172), [Gargron](https://github.com/mastodon/mastodon/pull/10170), [Gargron](https://github.com/mastodon/mastodon/pull/10171), [Gargron](https://github.com/mastodon/mastodon/pull/10186), [Gargron](https://github.com/mastodon/mastodon/pull/10189), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10200), [rinsuki](https://github.com/mastodon/mastodon/pull/10203), [Gargron](https://github.com/mastodon/mastodon/pull/10213), [Gargron](https://github.com/mastodon/mastodon/pull/10246), [Gargron](https://github.com/mastodon/mastodon/pull/10265), [Gargron](https://github.com/mastodon/mastodon/pull/10261), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10333), [Gargron](https://github.com/mastodon/mastodon/pull/10352), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10140), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10142), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10141), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10162), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10161), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10158), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10156), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10160), [Gargron](https://github.com/mastodon/mastodon/pull/10185), [Gargron](https://github.com/mastodon/mastodon/pull/10188), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10195), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10208), [Gargron](https://github.com/mastodon/mastodon/pull/10187), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10214), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/10209)) @@ -2297,6 +2993,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix `tootctl accounts cull` sometimes removing accounts that are temporarily unreachable ([BenLubar](https://github.com/mastodon/mastodon/pull/10460)) ## [2.7.4] - 2019-03-05 + ### Fixed - Fix web UI not cleaning up notifications after block ([Gargron](https://github.com/mastodon/mastodon/pull/10108)) @@ -2311,6 +3008,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix edit profile page crash for suspended-then-unsuspended users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10178)) ## [2.7.3] - 2019-02-23 + ### Added - Add domain filter to the admin federation page ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/10071)) @@ -2328,6 +3026,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Change custom emojis to randomize stored file name ([hinaloe](https://github.com/mastodon/mastodon/pull/10090)) ## [2.7.2] - 2019-02-17 + ### Added - Add support for IPv6 in e-mail validation ([zoc](https://github.com/mastodon/mastodon/pull/10009)) @@ -2369,6 +3068,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Change error graphic to hover-to-play ([Gargron](https://github.com/mastodon/mastodon/pull/10055)) ## [2.7.1] - 2019-01-28 + ### Fixed - Fix SSO authentication not working due to missing agreement boolean ([Gargron](https://github.com/mastodon/mastodon/pull/9915)) @@ -2383,6 +3083,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix missing strong style for landing page description ([Kjwon15](https://github.com/mastodon/mastodon/pull/9892)) ## [2.7.0] - 2019-01-20 + ### Added - Add link for adding a user to a list from their profile ([namelessGonbai](https://github.com/mastodon/mastodon/pull/9062)) @@ -2512,6 +3213,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Add tombstones for remote statuses to prevent replay attacks ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/9830)) ## [2.6.5] - 2018-12-01 + ### Changed - Change lists to display replies to others on the list and list owner ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/9324)) @@ -2521,11 +3223,13 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix failures caused by commonly-used JSON-LD contexts being unavailable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/9412)) ## [2.6.4] - 2018-11-30 + ### Fixed - Fix yarn dependencies not installing due to yanked event-stream package ([Gargron](https://github.com/mastodon/mastodon/pull/9401)) ## [2.6.3] - 2018-11-30 + ### Added - Add hyphen to characters allowed in remote usernames ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/9345)) @@ -2545,6 +3249,7 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix TLS handshake timeout not being enforced ([Gargron](https://github.com/mastodon/mastodon/pull/9381)) ## [2.6.2] - 2018-11-23 + ### Added - Add Page to whitelisted ActivityPub types ([mbajur](https://github.com/mastodon/mastodon/pull/9188)) @@ -2579,12 +3284,14 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix HTTP connection timeout of 10s not being enforced ([Gargron](https://github.com/mastodon/mastodon/pull/9329)) ## [2.6.1] - 2018-10-30 + ### Fixed - Fix resolving resources by URL not working due to a regression in [valerauko](https://github.com/mastodon/mastodon/pull/9132) ([Gargron](https://github.com/mastodon/mastodon/pull/9171)) - Fix reducer error in web UI when a conversation has no last status ([Gargron](https://github.com/mastodon/mastodon/pull/9173)) ## [2.6.0] - 2018-10-30 + ### Added - Add link ownership verification ([Gargron](https://github.com/mastodon/mastodon/pull/8703)) @@ -2689,11 +3396,13 @@ Some of the features in this release have been funded through the [NGI0 Discover - Fix handling of content types with profile ([valerauko](https://github.com/mastodon/mastodon/pull/9132)) ## [2.5.2] - 2018-10-12 + ### Security - Fix XSS vulnerability ([Gargron](https://github.com/mastodon/mastodon/pull/8959)) ## [2.5.1] - 2018-10-07 + ### Fixed - Fix database migrations for PostgreSQL below 9.5 ([Gargron](https://github.com/mastodon/mastodon/pull/8903)) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 7cec57180..c88a0d938 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,45 +2,131 @@ ## Our Pledge -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. ## Our Standards -Examples of behavior that contributes to creating a positive environment include: +Examples of behavior that contributes to a positive environment for our +community include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting -## Our Responsibilities +## Enforcement Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. ## Scope -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at eugen@zeonfederated.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[hello@joinmastodon.org](mailto:hello@joinmastodon.org). +All complaints will be reviewed and investigated promptly and fairly. -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9963054b3..c1a5fef79 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,4 @@ -Contributing -============ +# Contributing Thank you for considering contributing to Mastodon 🐘 @@ -28,9 +27,9 @@ You can submit translations via [Crowdin](https://crowdin.com/project/mastodon). Example: -|Not ideal|Better| -|---|----| -|Fixed NoMethodError in RemovalWorker|Fix nil error when removing statuses caused by race condition| +| Not ideal | Better | +| ------------------------------------ | ------------------------------------------------------------- | +| Fixed NoMethodError in RemovalWorker | Fix nil error when removing statuses caused by race condition | It is not always possible to phrase every change in such a manner, but it is desired. @@ -42,8 +41,6 @@ It is not always possible to phrase every change in such a manner, but it is des - Code style rules (rubocop, eslint) - Normalization of locale files (i18n-tasks) -**Note**: You may need to log in and authorise the GitHub account your fork of this repository belongs to with CircleCI to enable some of the automated checks to run. - ## Documentation The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/documentation](https://github.com/mastodon/documentation). diff --git a/Capfile b/Capfile index bf3ae7e24..86efa5bac 100644 --- a/Capfile +++ b/Capfile @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'capistrano/setup' require 'capistrano/deploy' require 'capistrano/scm/git' diff --git a/Dockerfile b/Dockerfile index 577cd0845..980d70509 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,122 +1,105 @@ -FROM ubuntu:20.04 as build-dep +# syntax=docker/dockerfile:1.4 +# This needs to be bookworm-slim because the Ruby image is built on bookworm-slim +ARG NODE_VERSION="20.6-bookworm-slim" -# Use bash for the shell -SHELL ["/bin/bash", "-c"] -RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections +FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.3-slim as ruby +FROM node:${NODE_VERSION} as build -# Install Node v16 (LTS) -ENV NODE_VER="16.17.1" -RUN ARCH= && \ - dpkgArch="$(dpkg --print-architecture)" && \ - case "${dpkgArch##*-}" in \ - amd64) ARCH='x64';; \ - ppc64el) ARCH='ppc64le';; \ - s390x) ARCH='s390x';; \ - arm64) ARCH='arm64';; \ - armhf) ARCH='armv7l';; \ - i386) ARCH='x86';; \ - *) echo "unsupported architecture"; exit 1 ;; \ - esac && \ - echo "Etc/UTC" > /etc/localtime && \ - apt-get update && \ - apt-get -yq dist-upgrade && \ - apt-get install -y --no-install-recommends ca-certificates wget python3 apt-utils && \ - cd ~ && \ - wget -q https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-$ARCH.tar.gz && \ - tar xf node-v$NODE_VER-linux-$ARCH.tar.gz && \ - rm node-v$NODE_VER-linux-$ARCH.tar.gz && \ - mv node-v$NODE_VER-linux-$ARCH /opt/node +COPY --link --from=ruby /opt/ruby /opt/ruby -# Install Ruby 3.0 -ENV RUBY_VER="3.0.6" -RUN apt-get update && \ - apt-get install -y --no-install-recommends build-essential \ - bison libyaml-dev libgdbm-dev libreadline-dev libjemalloc-dev \ - libncurses5-dev libffi-dev zlib1g-dev libssl-dev && \ - cd ~ && \ - wget https://cache.ruby-lang.org/pub/ruby/${RUBY_VER%.*}/ruby-$RUBY_VER.tar.gz && \ - tar xf ruby-$RUBY_VER.tar.gz && \ - cd ruby-$RUBY_VER && \ - ./configure --prefix=/opt/ruby \ - --with-jemalloc \ - --with-shared \ - --disable-install-doc && \ - make -j"$(nproc)" > /dev/null && \ - make install && \ - rm -rf ../ruby-$RUBY_VER.tar.gz ../ruby-$RUBY_VER +ENV DEBIAN_FRONTEND="noninteractive" \ + PATH="${PATH}:/opt/ruby/bin" -ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin" - -RUN npm install -g npm@9 && \ - npm install -g yarn && \ - gem install bundler && \ - apt-get update && \ - apt-get install -y --no-install-recommends git libicu-dev libidn11-dev \ - libpq-dev shared-mime-info +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +WORKDIR /opt/mastodon COPY Gemfile* package.json yarn.lock /opt/mastodon/ -RUN cd /opt/mastodon && \ - bundle config set --local deployment 'true' && \ - bundle config set --local without 'development test' && \ - bundle config set silence_root_warning true && \ - bundle install -j"$(nproc)" && \ - yarn install --pure-lockfile +# hadolint ignore=DL3008 +RUN apt-get update && \ + apt-get -yq dist-upgrade && \ + apt-get install -y --no-install-recommends build-essential \ + git \ + libicu-dev \ + libidn-dev \ + libpq-dev \ + libjemalloc-dev \ + zlib1g-dev \ + libgdbm-dev \ + libgmp-dev \ + libssl-dev \ + libyaml-0-2 \ + ca-certificates \ + libreadline8 \ + python3 \ + shared-mime-info && \ + bundle config set --local deployment 'true' && \ + bundle config set --local without 'development test' && \ + bundle config set silence_root_warning true && \ + bundle install -j"$(nproc)" && \ + yarn install --pure-lockfile --production --network-timeout 600000 && \ + yarn cache clean -FROM ubuntu:20.04 +FROM node:${NODE_VERSION} -# Copy over all the langs needed for runtime -COPY --from=build-dep /opt/node /opt/node -COPY --from=build-dep /opt/ruby /opt/ruby +# Use those args to specify your own version flags & suffixes +ARG MASTODON_VERSION_PRERELEASE="" +ARG MASTODON_VERSION_METADATA="" -# Add more PATHs to the PATH -ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin" +ARG UID="991" +ARG GID="991" + +COPY --link --from=ruby /opt/ruby /opt/ruby -# Create the mastodon user -ARG UID=991 -ARG GID=991 SHELL ["/bin/bash", "-o", "pipefail", "-c"] -RUN apt-get update && \ - echo "Etc/UTC" > /etc/localtime && \ - apt-get install -y --no-install-recommends whois wget && \ - addgroup --gid $GID mastodon && \ - useradd -m -u $UID -g $GID -d /opt/mastodon mastodon && \ - echo "mastodon:$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 24 | mkpasswd -s -m sha-256)" | chpasswd && \ - rm -rf /var/lib/apt/lists/* -# Install mastodon runtime deps -RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections -RUN apt-get update && \ - apt-get -y --no-install-recommends install \ - libssl1.1 libpq5 imagemagick ffmpeg libjemalloc2 \ - libicu66 libidn11 libyaml-0-2 \ - file ca-certificates tzdata libreadline8 gcc tini apt-utils && \ - ln -s /opt/mastodon /mastodon && \ - gem install bundler && \ - rm -rf /var/cache && \ - rm -rf /var/lib/apt/lists/* +ENV DEBIAN_FRONTEND="noninteractive" \ + PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" + +# Ignoring these here since we don't want to pin any versions and the Debian image removes apt-get content after use +# hadolint ignore=DL3008,DL3009 +RUN apt-get update && \ + echo "Etc/UTC" > /etc/localtime && \ + groupadd -g "${GID}" mastodon && \ + useradd -l -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \ + apt-get -y --no-install-recommends install whois \ + wget \ + procps \ + libssl3 \ + libpq5 \ + imagemagick \ + ffmpeg \ + libjemalloc2 \ + libicu72 \ + libidn12 \ + libyaml-0-2 \ + file \ + ca-certificates \ + tzdata \ + libreadline8 \ + tini && \ + ln -s /opt/mastodon /mastodon + +# Note: no, cleaning here since Debian does this automatically +# See the file /etc/apt/apt.conf.d/docker-clean within the Docker image's filesystem -# Copy over mastodon source, and dependencies from building, and set permissions COPY --chown=mastodon:mastodon . /opt/mastodon -COPY --from=build-dep --chown=mastodon:mastodon /opt/mastodon /opt/mastodon +COPY --chown=mastodon:mastodon --from=build /opt/mastodon /opt/mastodon -# Run mastodon services in prod mode -ENV RAILS_ENV="production" -ENV NODE_ENV="production" - -# Tell rails to serve static files -ENV RAILS_SERVE_STATIC_FILES="true" -ENV BIND="0.0.0.0" +ENV RAILS_ENV="production" \ + NODE_ENV="production" \ + RAILS_SERVE_STATIC_FILES="true" \ + BIND="0.0.0.0" \ + MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \ + MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" # Set the run user USER mastodon +WORKDIR /opt/mastodon # Precompile assets -RUN cd ~ && \ - OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile && \ - yarn cache clean +RUN OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile # Set the work dir and the container entry point -WORKDIR /opt/mastodon ENTRYPOINT ["/usr/bin/tini", "--"] EXPOSE 3000 4000 diff --git a/FEDERATION.md b/FEDERATION.md index 2cccac037..96b4dea05 100644 --- a/FEDERATION.md +++ b/FEDERATION.md @@ -31,4 +31,5 @@ More information on HTTP Signatures, as well as examples, can be found here: htt - Linked-Data Signatures: https://docs.joinmastodon.org/spec/security/#ld - Bearcaps: https://docs.joinmastodon.org/spec/bearcaps/ -- Followers collection synchronization: https://git.activitypub.dev/ActivityPubDev/Fediverse-Enhancement-Proposals/src/branch/main/feps/fep-8fcf.md +- Followers collection synchronization: https://codeberg.org/fediverse/fep/src/branch/main/fep/8fcf/fep-8fcf.md +- Search indexing consent for actors: https://codeberg.org/fediverse/fep/src/branch/main/fep/5feb/fep-5feb.md diff --git a/Gemfile b/Gemfile index 355b7e43f..7f7da8c57 100644 --- a/Gemfile +++ b/Gemfile @@ -1,48 +1,48 @@ # frozen_string_literal: true source 'https://rubygems.org' -ruby '>= 2.6.0', '< 3.1.0' +ruby '>= 3.0.0' -gem 'pkg-config', '~> 1.4' -gem 'rexml', '~> 3.2' - -gem 'puma', '~> 5.6' -gem 'rails', '~> 6.1.7' +gem 'puma', '~> 6.3' +gem 'rails', '~> 7.0' gem 'sprockets', '~> 3.7.2' gem 'thor', '~> 1.2' -gem 'rack', '~> 2.2.4' +gem 'rack', '~> 2.2.7' -gem 'hamlit-rails', '~> 0.2' -gem 'pg', '~> 1.4' -gem 'makara', '~> 0.5' -gem 'pghero', '~> 2.8' +gem 'haml-rails', '~>2.0' +gem 'pg', '~> 1.5' +gem 'pghero' gem 'dotenv-rails', '~> 2.8' -gem 'aws-sdk-s3', '~> 1.114', require: false -gem 'fog-core', '<= 2.1.0' +gem 'aws-sdk-s3', '~> 1.123', require: false +gem 'fog-core', '<= 2.4.0' gem 'fog-openstack', '~> 0.3', require: false -gem 'kt-paperclip', '~> 7.1' +gem 'kt-paperclip', '~> 7.2' +gem 'md-paperclip-azure', '~> 2.2', require: false gem 'blurhash', '~> 0.1' gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.8' -gem 'bootsnap', '~> 1.13.0', require: false +gem 'bootsnap', '~> 1.16.0', require: false gem 'browser' gem 'charlock_holmes', '~> 0.7.7' -gem 'chewy', '~> 7.2' -gem 'devise', '~> 4.8' -gem 'devise-two-factor', '~> 4.0' +gem 'chewy', '~> 7.3' +gem 'devise', '~> 4.9' +gem 'devise-two-factor', '~> 4.1' group :pam_authentication, optional: true do gem 'devise_pam_authenticatable2', '~> 9.2' end -gem 'net-ldap', '~> 0.17' -gem 'omniauth-cas', '~> 2.0' -gem 'omniauth-saml', '~> 1.10' -gem 'gitlab-omniauth-openid-connect', '~>0.10.0', require: 'omniauth_openid_connect' -gem 'omniauth', '~> 1.9' -gem 'omniauth-rails_csrf_protection', '~> 0.1' +gem 'net-ldap', '~> 0.18' + +# TODO: Point back at released omniauth-cas gem when PR merged +# https://github.com/dlindahl/omniauth-cas/pull/68 +gem 'omniauth-cas', github: 'stanhu/omniauth-cas', ref: '4211e6d05941b4a981f9a36b49ec166cecd0e271' +gem 'omniauth-saml', '~> 2.0' +gem 'omniauth_openid_connect', '~> 0.6.1' +gem 'omniauth', '~> 2.0' +gem 'omniauth-rails_csrf_protection', '~> 1.0' gem 'color_diff', '~> 0.1' gem 'discard', '~> 1.2' @@ -51,100 +51,143 @@ gem 'ed25519', '~> 1.3' gem 'fast_blank', '~> 1.0' gem 'fastimage' gem 'hiredis', '~> 0.6' -gem 'redis-namespace', '~> 1.9' +gem 'redis-namespace', '~> 1.10' gem 'htmlentities', '~> 4.3' gem 'http', '~> 5.1' gem 'http_accept_language', '~> 2.1' -gem 'httplog', '~> 1.6.0' +gem 'httplog', '~> 1.6.2' gem 'idn-ruby', require: 'idn' gem 'kaminari', '~> 1.2' gem 'link_header', '~> 0.0' -gem 'mime-types', '~> 3.4.1', require: 'mime/types/columnar' -gem 'nokogiri', '~> 1.13' -gem 'nsa', '~> 0.2' -gem 'oj', '~> 3.13' +gem 'mime-types', '~> 3.5.0', require: 'mime/types/columnar' +gem 'nokogiri', '~> 1.15' +gem 'nsa' +gem 'oj', '~> 3.14' gem 'ox', '~> 2.14' gem 'parslet' -gem 'posix-spawn' -gem 'pundit', '~> 2.2' +gem 'public_suffix', '~> 5.0' +gem 'pundit', '~> 2.3' gem 'premailer-rails' gem 'rack-attack', '~> 6.6' -gem 'rack-cors', '~> 1.1', require: 'rack/cors' -gem 'rails-i18n', '~> 6.0' -gem 'rails-settings-cached', '~> 0.6' -gem 'redcarpet', '~> 3.5' +gem 'rack-cors', '~> 2.0', require: 'rack/cors' +gem 'rails-i18n', '~> 7.0' +gem 'rails-settings-cached', '~> 0.6', git: 'https://github.com/mastodon/rails-settings-cached.git', branch: 'v0.6.6-aliases-true' +gem 'redcarpet', '~> 3.6' gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis'] gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' -gem 'rqrcode', '~> 2.1' -gem 'ruby-progressbar', '~> 1.11' +gem 'rqrcode', '~> 2.2' +gem 'ruby-progressbar', '~> 1.13' gem 'sanitize', '~> 6.0' -gem 'scenic', '~> 1.6' +gem 'scenic', '~> 1.7' gem 'sidekiq', '~> 6.5' -gem 'sidekiq-scheduler', '~> 4.0' +gem 'sidekiq-scheduler', '~> 5.0' gem 'sidekiq-unique-jobs', '~> 7.1' gem 'sidekiq-bulk', '~> 0.2.0' gem 'simple-navigation', '~> 4.4' -gem 'simple_form', '~> 5.1' +gem 'simple_form', '~> 5.2' gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie' -gem 'stoplight', '~> 3.0.0' -gem 'strong_migrations', '~> 0.7' +gem 'stoplight', '~> 3.0.1' +gem 'strong_migrations', '~> 0.8' gem 'tty-prompt', '~> 0.23', require: false gem 'twitter-text', '~> 3.1.0' -gem 'tzinfo-data', '~> 1.2022' +gem 'tzinfo-data', '~> 1.2023' gem 'webpacker', '~> 5.4' gem 'webpush', github: 'ClearlyClaire/webpush', ref: 'f14a4d52e201128b1b00245d11b6de80d6cfdcd9' -gem 'webauthn', '~> 2.5' +gem 'webauthn', '~> 3.0' gem 'json-ld' gem 'json-ld-preloaded', '~> 3.2' gem 'rdf-normalize', '~> 0.5' -group :development, :test do - gem 'fabrication', '~> 2.30' - gem 'fuubar', '~> 2.5' - gem 'i18n-tasks', '~> 1.0', require: false - gem 'pry-byebug', '~> 3.10' - gem 'pry-rails', '~> 0.3' - gem 'rspec-rails', '~> 5.1' -end - -group :production, :test do - gem 'private_address_check', '~> 0.5' -end +gem 'private_address_check', '~> 0.5' group :test do - gem 'capybara', '~> 3.37' + # Used to split testing into chunks in CI + gem 'rspec_chunked', '~> 0.6' + + # RSpec progress bar formatter + gem 'fuubar', '~> 2.5' + + # Extra RSpec extenion methods and helpers for sidekiq + gem 'rspec-sidekiq', '~> 4.0' + + # Browser integration testing + gem 'capybara', '~> 3.39' + gem 'selenium-webdriver' + + # Used to reset the database between system tests + gem 'database_cleaner-active_record' + + # Used to mock environment variables gem 'climate_control', '~> 0.2' - gem 'faker', '~> 2.23' - gem 'microformats', '~> 4.4' + + # Generating fake data for specs + gem 'faker', '~> 3.2' + + # Generate test objects for specs + gem 'fabrication', '~> 2.30' + + # Add back helpers functions removed in Rails 5.1 gem 'rails-controller-testing', '~> 1.0' - gem 'rspec-sidekiq', '~> 3.1' - gem 'simplecov', '~> 0.21', require: false + + # Validate schemas in specs + gem 'json-schema', '~> 4.0' + + # Test harness fo rack components + gem 'rack-test', '~> 2.1' + + # Coverage formatter for RSpec test if DISABLE_SIMPLECOV is false + gem 'simplecov', '~> 0.22', require: false + + # Stub web requests for specs gem 'webmock', '~> 3.18' - gem 'rspec_junit_formatter', '~> 0.6' - gem 'rack-test', '~> 2.0' end group :development do - gem 'active_record_query_trace', '~> 1.8' + # Code linting CLI and plugins + gem 'rubocop', require: false + gem 'rubocop-capybara', require: false + gem 'rubocop-performance', require: false + gem 'rubocop-rails', require: false + gem 'rubocop-rspec', require: false + + # Annotates modules with schema gem 'annotate', '~> 3.2' + + # Enhanced error message pages for development gem 'better_errors', '~> 2.9' gem 'binding_of_caller', '~> 1.0' - gem 'bullet', '~> 7.0' + + # Preview mail in the browser gem 'letter_opener', '~> 1.8' gem 'letter_opener_web', '~> 2.0' - gem 'memory_profiler' - gem 'rubocop', '~> 1.30', require: false - gem 'rubocop-rails', '~> 2.15', require: false - gem 'brakeman', '~> 5.3', require: false + + # Security analysis CLI tools + gem 'brakeman', '~> 6.0', require: false gem 'bundler-audit', '~> 0.9', require: false + # Linter CLI for HAML files + gem 'haml_lint', require: false + + # Deployment automation gem 'capistrano', '~> 3.17' gem 'capistrano-rails', '~> 1.6' gem 'capistrano-rbenv', '~> 2.2' gem 'capistrano-yarn', '~> 2.0' - gem 'stackprof' + # Validate missing i18n keys + gem 'i18n-tasks', '~> 1.0', require: false +end + +group :development, :test do + # Profiling tools + gem 'memory_profiler', require: false + gem 'ruby-prof', require: false + gem 'stackprof', require: false + gem 'test-prof' + + # RSpec runner for rails + gem 'rspec-rails', '~> 6.0' end group :production do @@ -155,3 +198,10 @@ gem 'concurrent-ruby', require: false gem 'connection_pool', require: false gem 'xorcist', '~> 1.1' gem 'cocoon', '~> 1.2' + +gem 'net-http', '~> 0.3.2' +gem 'rubyzip', '~> 2.3' + +gem 'hcaptcha', '~> 7.1' + +gem 'mail', '~> 2.8' diff --git a/Gemfile.lock b/Gemfile.lock index 0dfeeb092..34958703a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,43 +7,68 @@ GIT hkdf (~> 0.2) jwt (~> 2.0) +GIT + remote: https://github.com/mastodon/rails-settings-cached.git + revision: 86328ef0bd04ce21cc0504ff5e334591e8c2ccab + branch: v0.6.6-aliases-true + specs: + rails-settings-cached (0.6.6) + rails (>= 4.2.0) + +GIT + remote: https://github.com/stanhu/omniauth-cas.git + revision: 4211e6d05941b4a981f9a36b49ec166cecd0e271 + ref: 4211e6d05941b4a981f9a36b49ec166cecd0e271 + specs: + omniauth-cas (2.0.0) + addressable (~> 2.3) + nokogiri (~> 1.5) + omniauth (>= 1.2, < 3) + GEM remote: https://rubygems.org/ specs: - actioncable (6.1.7.6) - actionpack (= 6.1.7.6) - activesupport (= 6.1.7.6) + actioncable (7.0.8.4) + actionpack (= 7.0.8.4) + activesupport (= 7.0.8.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.6) - actionpack (= 6.1.7.6) - activejob (= 6.1.7.6) - activerecord (= 6.1.7.6) - activestorage (= 6.1.7.6) - activesupport (= 6.1.7.6) + actionmailbox (7.0.8.4) + actionpack (= 7.0.8.4) + activejob (= 7.0.8.4) + activerecord (= 7.0.8.4) + activestorage (= 7.0.8.4) + activesupport (= 7.0.8.4) mail (>= 2.7.1) - actionmailer (6.1.7.6) - actionpack (= 6.1.7.6) - actionview (= 6.1.7.6) - activejob (= 6.1.7.6) - activesupport (= 6.1.7.6) + net-imap + net-pop + net-smtp + actionmailer (7.0.8.4) + actionpack (= 7.0.8.4) + actionview (= 7.0.8.4) + activejob (= 7.0.8.4) + activesupport (= 7.0.8.4) mail (~> 2.5, >= 2.5.4) + net-imap + net-pop + net-smtp rails-dom-testing (~> 2.0) - actionpack (6.1.7.6) - actionview (= 6.1.7.6) - activesupport (= 6.1.7.6) - rack (~> 2.0, >= 2.0.9) + actionpack (7.0.8.4) + actionview (= 7.0.8.4) + activesupport (= 7.0.8.4) + rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.6) - actionpack (= 6.1.7.6) - activerecord (= 6.1.7.6) - activestorage (= 6.1.7.6) - activesupport (= 6.1.7.6) + actiontext (7.0.8.4) + actionpack (= 7.0.8.4) + activerecord (= 7.0.8.4) + activestorage (= 7.0.8.4) + activesupport (= 7.0.8.4) + globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (6.1.7.6) - activesupport (= 6.1.7.6) + actionview (7.0.8.4) + activesupport (= 7.0.8.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -53,29 +78,27 @@ GEM activemodel (>= 4.1, < 7.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - active_record_query_trace (1.8) - activejob (6.1.7.6) - activesupport (= 6.1.7.6) + activejob (7.0.8.4) + activesupport (= 7.0.8.4) globalid (>= 0.3.6) - activemodel (6.1.7.6) - activesupport (= 6.1.7.6) - activerecord (6.1.7.6) - activemodel (= 6.1.7.6) - activesupport (= 6.1.7.6) - activestorage (6.1.7.6) - actionpack (= 6.1.7.6) - activejob (= 6.1.7.6) - activerecord (= 6.1.7.6) - activesupport (= 6.1.7.6) + activemodel (7.0.8.4) + activesupport (= 7.0.8.4) + activerecord (7.0.8.4) + activemodel (= 7.0.8.4) + activesupport (= 7.0.8.4) + activestorage (7.0.8.4) + actionpack (= 7.0.8.4) + activejob (= 7.0.8.4) + activerecord (= 7.0.8.4) + activesupport (= 7.0.8.4) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.6) + activesupport (7.0.8.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.1) + addressable (2.8.5) public_suffix (>= 2.0.2, < 6.0) aes_key_wrap (1.1.0) airbrussh (1.4.1) @@ -85,31 +108,41 @@ GEM activerecord (>= 3.2, < 8.0) rake (>= 10.4, < 14.0) ast (2.4.2) - attr_encrypted (3.1.0) + attr_encrypted (4.0.0) encryptor (~> 3.0.0) attr_required (1.0.1) awrence (1.2.1) aws-eventstream (1.2.0) - aws-partitions (1.587.0) - aws-sdk-core (3.130.2) + aws-partitions (1.809.0) + aws-sdk-core (3.181.0) aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.525.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.5) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.71.0) + aws-sdk-core (~> 3, >= 3.177.0) aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.56.0) - aws-sdk-core (~> 3, >= 3.127.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.114.0) - aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-s3 (1.133.0) + aws-sdk-core (~> 3, >= 3.181.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.0) + aws-sigv4 (~> 1.6) + aws-sigv4 (1.6.0) aws-eventstream (~> 1, >= 1.0.2) - bcrypt (3.1.17) - better_errors (2.9.1) - coderay (>= 1.0.0) + azure-storage-blob (2.0.3) + azure-storage-common (~> 2.0) + nokogiri (~> 1, >= 1.10.8) + azure-storage-common (2.0.4) + faraday (~> 1.0) + faraday_middleware (~> 1.0, >= 1.0.0.rc1) + net-http-persistent (~> 4.0) + nokogiri (~> 1, >= 1.10.8) + base64 (0.1.1) + bcp47_spec (0.2.1) + bcrypt (3.1.18) + better_errors (2.10.1) erubi (>= 1.0.0) rack (>= 0.9.0) + rouge (>= 1.0.0) better_html (2.0.1) actionview (>= 6.0) activesupport (>= 6.0) @@ -117,34 +150,29 @@ GEM erubi (~> 1.4) parser (>= 2.4) smart_properties - bindata (2.4.10) + bindata (2.4.15) binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) - blurhash (0.1.6) - ffi (~> 1.14) - bootsnap (1.13.0) + blurhash (0.1.7) + bootsnap (1.16.0) msgpack (~> 1.2) - brakeman (5.3.1) - browser (4.2.0) - brpoplpush-redis_script (0.1.2) + brakeman (6.0.1) + browser (5.3.1) + brpoplpush-redis_script (0.1.3) concurrent-ruby (~> 1.0, >= 1.0.5) - redis (>= 1.0, <= 5.0) + redis (>= 1.0, < 6) builder (3.2.4) - bullet (7.0.3) - activesupport (>= 3.0.0) - uniform_notifier (~> 1.11) bundler-audit (0.9.1) bundler (>= 1.2.0, < 3) thor (~> 1.0) - byebug (11.1.3) - capistrano (3.17.1) + capistrano (3.17.3) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) sshkit (>= 1.9.0) - capistrano-bundler (2.0.1) + capistrano-bundler (2.1.0) capistrano (~> 3.1) - capistrano-rails (1.6.2) + capistrano-rails (1.6.3) capistrano (~> 3.1) capistrano-bundler (>= 1.1, < 3) capistrano-rbenv (2.2.0) @@ -152,7 +180,7 @@ GEM sshkit (~> 1.3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (3.37.1) + capybara (3.39.2) addressable matrix mini_mime (>= 0.1.3) @@ -163,37 +191,41 @@ GEM xpath (~> 3.2) case_transform (0.2) activesupport - cbor (0.5.9.6) - charlock_holmes (0.7.7) - chewy (7.2.4) + cbor (0.5.9.8) + charlock_holmes (0.7.8) + chewy (7.3.4) activesupport (>= 5.2) elasticsearch (>= 7.12.0, < 7.14.0) elasticsearch-dsl chunky_png (1.4.0) climate_control (0.2.0) cocoon (1.2.15) - coderay (1.1.3) color_diff (0.1) - concurrent-ruby (1.2.2) - connection_pool (2.3.0) - cose (1.2.1) + concurrent-ruby (1.2.3) + connection_pool (2.4.1) + cose (1.3.0) cbor (~> 0.5.9) openssl-signature_algorithm (~> 1.0) crack (0.4.5) rexml crass (1.0.6) - css_parser (1.7.1) + css_parser (1.14.0) addressable - debug_inspector (1.0.0) - devise (4.8.1) + database_cleaner-active_record (2.1.0) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0.0) + database_cleaner-core (2.0.1) + date (3.3.4) + debug_inspector (1.1.0) + devise (4.9.2) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-two-factor (4.0.2) + devise-two-factor (4.1.0) activesupport (< 7.1) - attr_encrypted (>= 1.3, < 4, != 2) + attr_encrypted (>= 1.3, < 5, != 2) devise (~> 4.0) railties (< 7.1) rotp (~> 6.0) @@ -203,7 +235,7 @@ GEM diff-lcs (1.5.0) discard (1.2.1) activerecord (>= 4.2, < 8) - docile (1.3.4) + docile (1.4.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) doorkeeper (5.6.6) @@ -223,14 +255,14 @@ GEM faraday (~> 1) multi_json encryptor (3.0.0) - erubi (1.11.0) + erubi (1.12.0) et-orbi (1.2.7) tzinfo - excon (0.76.0) + excon (0.100.0) fabrication (2.30.0) - faker (2.23.0) + faker (3.2.1) i18n (>= 1.8.11, < 2) - faraday (1.9.3) + faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -246,15 +278,17 @@ GEM faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) faraday-httpclient (1.0.1) - faraday-multipart (1.0.3) - multipart-post (>= 1.2, < 3) + faraday-multipart (1.0.4) + multipart-post (~> 2) faraday-net_http (1.0.1) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) faraday-retry (1.0.3) + faraday_middleware (1.2.0) + faraday (~> 1.0) fast_blank (1.0.1) - fastimage (2.2.6) + fastimage (2.3.1) ffi (1.15.5) ffi-compiler (1.0.1) ffi (>= 1.0.0) @@ -271,35 +305,39 @@ GEM fog-core (>= 1.45, <= 2.1.0) fog-json (>= 1.0) ipaddress (>= 0.8) - formatador (0.2.5) - fugit (1.7.1) + formatador (0.3.0) + fugit (1.8.1) et-orbi (~> 1, >= 1.2.7) raabro (~> 1.4) fuubar (2.5.1) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) - gitlab-omniauth-openid-connect (0.10.0) - addressable (~> 2.7) - omniauth (>= 1.9, < 3) - openid_connect (~> 1.2) - globalid (1.0.1) + globalid (1.1.0) activesupport (>= 5.0) - hamlit (2.13.0) + haml (6.1.2) temple (>= 0.8.2) thor tilt - hamlit-rails (0.2.3) - actionpack (>= 4.0.1) - activesupport (>= 4.0.1) - hamlit (>= 1.2.0) - railties (>= 4.0.1) + haml-rails (2.1.0) + actionpack (>= 5.1) + activesupport (>= 5.1) + haml (>= 4.0.6) + railties (>= 5.1) + haml_lint (0.50.0) + haml (>= 4.0, < 6.2) + parallel (~> 1.10) + rainbow + rubocop (>= 1.0) + sysexits (~> 1.1) hashdiff (1.0.1) hashie (5.0.0) - highline (2.0.3) + hcaptcha (7.1.0) + json + highline (2.1.0) hiredis (0.6.3) hkdf (0.3.0) htmlentities (4.3.4) - http (5.1.0) + http (5.1.1) addressable (~> 2.8) http-cookie (~> 1.0) http-form_data (~> 2.2) @@ -309,10 +347,10 @@ GEM http-form_data (2.3.0) http_accept_language (2.1.1) httpclient (2.8.3) - httplog (1.6.0) + httplog (1.6.2) rack (>= 2.0) rainbow (>= 2.0.0) - i18n (1.14.1) + i18n (1.14.5) concurrent-ruby (~> 1.0) i18n-tasks (1.0.12) activesupport (>= 4.0.2) @@ -325,27 +363,30 @@ GEM rails-i18n rainbow (>= 2.2.2, < 4.0) terminal-table (>= 1.5.1) - idn-ruby (0.1.4) + idn-ruby (0.1.5) ipaddress (0.8.3) - jmespath (1.6.1) - json (2.6.2) - json-canonicalization (0.3.0) - json-jwt (1.13.0) + jmespath (1.6.2) + json (2.6.3) + json-canonicalization (1.0.0) + json-jwt (1.15.3.1) activesupport (>= 4.2) aes_key_wrap bindata - json-ld (3.2.3) + httpclient + json-ld (3.3.1) htmlentities (~> 4.3) - json-canonicalization (~> 0.3) + json-canonicalization (~> 1.0) link_header (~> 0.0, >= 0.0.8) multi_json (~> 1.15) - rack (~> 2.2) - rdf (~> 3.2, >= 3.2.9) - json-ld-preloaded (3.2.0) + rack (>= 2.2, < 4) + rdf (~> 3.3) + json-ld-preloaded (3.2.2) json-ld (~> 3.2) rdf (~> 3.2) + json-schema (4.0.0) + addressable (>= 2.8) jsonapi-renderer (0.2.2) - jwt (2.4.1) + jwt (2.7.1) kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -358,14 +399,15 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - kt-paperclip (7.1.1) + kt-paperclip (7.2.1) activemodel (>= 4.2.0) activesupport (>= 4.2.0) marcel (~> 1.0.1) mime-types terrapin (~> 0.6.0) - launchy (2.5.0) - addressable (~> 2.7) + language_server-protocol (3.17.0.3) + launchy (2.5.2) + addressable (~> 2.8) letter_opener (1.8.1) launchy (>= 2.2, < 3) letter_opener_web (2.0.0) @@ -377,237 +419,273 @@ GEM llhttp-ffi (0.4.0) ffi-compiler (~> 1.0) rake (~> 13.0) - lograge (0.12.0) + lograge (0.13.0) actionpack (>= 4) activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.19.1) + loofah (2.21.4) crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) + nokogiri (>= 1.12.0) + mail (2.8.1) mini_mime (>= 0.1.1) - makara (0.5.1) - activerecord (>= 5.2.0) - marcel (1.0.2) + net-imap + net-pop + net-smtp + marcel (1.0.4) mario-redis-lock (1.2.1) redis (>= 3.0.5) matrix (0.4.2) - memory_profiler (1.0.0) + md-paperclip-azure (2.2.0) + addressable (~> 2.5) + azure-storage-blob (~> 2.0.1) + hashie (~> 5.0) + memory_profiler (1.0.1) method_source (1.0.0) - microformats (4.4.1) - json (~> 2.2) - nokogiri (~> 1.10) - mime-types (3.4.1) + mime-types (3.5.1) mime-types-data (~> 3.2015) - mime-types-data (3.2022.0105) + mime-types-data (3.2023.0808) mini_mime (1.1.5) - mini_portile2 (2.8.5) - minitest (5.20.0) - msgpack (1.5.4) + mini_portile2 (2.8.7) + minitest (5.19.0) + msgpack (1.7.1) multi_json (1.15.0) - multipart-post (2.1.1) - net-ldap (0.17.1) - net-scp (4.0.0.rc1) + multipart-post (2.3.0) + net-http (0.3.2) + uri + net-http-persistent (4.0.2) + connection_pool (~> 2.2) + net-imap (0.3.7) + date + net-protocol + net-ldap (0.18.0) + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-scp (4.0.0) net-ssh (>= 2.6.5, < 8.0.0) - net-ssh (7.0.1) - nio4r (2.5.9) - nokogiri (1.16.2) + net-smtp (0.3.4) + net-protocol + net-ssh (7.1.0) + nio4r (2.7.3) + nokogiri (1.16.6) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nsa (0.2.8) - activesupport (>= 4.2, < 7) + nsa (0.3.0) + activesupport (>= 4.2, < 7.2) concurrent-ruby (~> 1.0, >= 1.0.2) sidekiq (>= 3.5) statsd-ruby (~> 1.4, >= 1.4.0) - oj (3.13.21) - omniauth (1.9.2) + oj (3.16.1) + omniauth (2.1.1) hashie (>= 3.4.6) - rack (>= 1.6.2, < 3) - omniauth-cas (2.0.0) - addressable (~> 2.3) - nokogiri (~> 1.5) - omniauth (~> 1.2) - omniauth-rails_csrf_protection (0.1.2) + rack (>= 2.2.3) + rack-protection + omniauth-rails_csrf_protection (1.0.1) actionpack (>= 4.2) - omniauth (>= 1.3.1) - omniauth-saml (1.10.3) - omniauth (~> 1.3, >= 1.3.2) - ruby-saml (~> 1.9) - openid_connect (1.3.0) + omniauth (~> 2.0) + omniauth-saml (2.1.0) + omniauth (~> 2.0) + ruby-saml (~> 1.12) + omniauth_openid_connect (0.6.1) + omniauth (>= 1.9, < 3) + openid_connect (~> 1.1) + openid_connect (1.4.2) activemodel attr_required (>= 1.0.0) - json-jwt (>= 1.5.0) - rack-oauth2 (>= 1.6.1) - swd (>= 1.0.0) + json-jwt (>= 1.15.0) + net-smtp + rack-oauth2 (~> 1.21) + swd (~> 1.3) tzinfo validate_email validate_url - webfinger (>= 1.0.1) - openssl (3.0.0) - openssl-signature_algorithm (1.2.1) - openssl (> 2.0, < 3.1) + webfinger (~> 1.2) + openssl (3.1.0) + openssl-signature_algorithm (1.3.0) + openssl (> 2.0) orm_adapter (0.5.0) - ox (2.14.11) - parallel (1.22.1) - parser (3.1.2.1) + ox (2.14.17) + parallel (1.23.0) + parser (3.2.2.3) ast (~> 2.4.1) + racc parslet (2.0.0) pastel (0.8.0) tty-color (~> 0.5) - pg (1.4.3) - pghero (2.8.3) - activerecord (>= 5) - pkg-config (1.4.9) - posix-spawn (0.3.15) - premailer (1.14.2) + pg (1.5.5) + pghero (3.3.4) + activerecord (>= 6) + premailer (1.21.0) addressable - css_parser (>= 1.6.0) + css_parser (>= 1.12.0) htmlentities (>= 4.0.0) - premailer-rails (1.11.1) + premailer-rails (1.12.0) actionmailer (>= 3) + net-smtp premailer (~> 1.7, >= 1.7.9) private_address_check (0.5.0) - pry (0.14.1) - coderay (~> 1.1) - method_source (~> 1.0) - pry-byebug (3.10.1) - byebug (~> 11.0) - pry (>= 0.13, < 0.15) - pry-rails (0.3.9) - pry (>= 0.10.4) - public_suffix (5.0.0) - puma (5.6.7) + public_suffix (5.0.3) + puma (6.4.2) nio4r (~> 2.0) - pundit (2.2.0) + pundit (2.3.0) activesupport (>= 3.0.0) raabro (1.4.0) racc (1.7.3) - rack (2.2.8) - rack-attack (6.6.1) - rack (>= 1.0, < 3) - rack-cors (1.1.1) + rack (2.2.9) + rack-attack (6.7.0) + rack (>= 1.0, < 4) + rack-cors (2.0.2) rack (>= 2.0.0) - rack-oauth2 (1.19.0) + rack-oauth2 (1.21.3) activesupport attr_required httpclient json-jwt (>= 1.11.0) rack (>= 2.1.0) - rack-proxy (0.7.0) + rack-protection (3.0.5) rack - rack-test (2.0.2) + rack-proxy (0.7.6) + rack + rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.6) - actioncable (= 6.1.7.6) - actionmailbox (= 6.1.7.6) - actionmailer (= 6.1.7.6) - actionpack (= 6.1.7.6) - actiontext (= 6.1.7.6) - actionview (= 6.1.7.6) - activejob (= 6.1.7.6) - activemodel (= 6.1.7.6) - activerecord (= 6.1.7.6) - activestorage (= 6.1.7.6) - activesupport (= 6.1.7.6) + rails (7.0.8.4) + actioncable (= 7.0.8.4) + actionmailbox (= 7.0.8.4) + actionmailer (= 7.0.8.4) + actionpack (= 7.0.8.4) + actiontext (= 7.0.8.4) + actionview (= 7.0.8.4) + activejob (= 7.0.8.4) + activemodel (= 7.0.8.4) + activerecord (= 7.0.8.4) + activestorage (= 7.0.8.4) + activesupport (= 7.0.8.4) bundler (>= 1.15.0) - railties (= 6.1.7.6) - sprockets-rails (>= 2.0.0) + railties (= 7.0.8.4) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) activesupport (>= 5.0.1.rc1) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + rails-dom-testing (2.1.1) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.4.4) - loofah (~> 2.19, >= 2.19.1) - rails-i18n (6.0.0) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) + rails-i18n (7.0.7) i18n (>= 0.7, < 2) - railties (>= 6.0.0, < 7) - rails-settings-cached (0.6.6) - rails (>= 4.2.0) - railties (6.1.7.6) - actionpack (= 6.1.7.6) - activesupport (= 6.1.7.6) + railties (>= 6.0.0, < 8) + railties (7.0.8.4) + actionpack (= 7.0.8.4) + activesupport (= 7.0.8.4) method_source rake (>= 12.2) thor (~> 1.0) + zeitwerk (~> 2.5) rainbow (3.1.1) rake (13.0.6) - rdf (3.2.9) + rdf (3.3.1) + bcp47_spec (~> 0.2) link_header (~> 0.0, >= 0.0.8) - rdf-normalize (0.5.0) + rdf-normalize (0.6.1) rdf (~> 3.2) - redcarpet (3.5.1) - redis (4.5.1) - redis-namespace (1.9.0) + redcarpet (3.6.0) + redis (4.8.1) + redis-namespace (1.11.0) redis (>= 4) - regexp_parser (2.5.0) + redlock (1.3.2) + redis (>= 3.0.0, < 6.0) + regexp_parser (2.8.1) request_store (1.5.1) rack (>= 1.4) - responders (3.0.1) - actionpack (>= 5.0) - railties (>= 5.0) - rexml (3.2.5) - rotp (6.2.0) + responders (3.1.0) + actionpack (>= 5.2) + railties (>= 5.2) + rexml (3.2.8) + strscan (>= 3.0.9) + rotp (6.3.0) + rouge (4.1.2) rpam2 (4.0.2) - rqrcode (2.1.2) + rqrcode (2.2.0) chunky_png (~> 1.0) rqrcode_core (~> 1.0) rqrcode_core (1.2.0) - rspec-core (3.11.0) - rspec-support (~> 3.11.0) - rspec-expectations (3.11.0) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-mocks (3.11.1) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-rails (5.1.2) - actionpack (>= 5.2) - activesupport (>= 5.2) - railties (>= 5.2) - rspec-core (~> 3.10) - rspec-expectations (~> 3.10) - rspec-mocks (~> 3.10) - rspec-support (~> 3.10) - rspec-sidekiq (3.1.0) - rspec-core (~> 3.0, >= 3.0.0) - sidekiq (>= 2.4.0) - rspec-support (3.11.1) - rspec_junit_formatter (0.6.0) - rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.30.1) + rspec-support (~> 3.12.0) + rspec-rails (6.0.3) + actionpack (>= 6.1) + activesupport (>= 6.1) + railties (>= 6.1) + rspec-core (~> 3.12) + rspec-expectations (~> 3.12) + rspec-mocks (~> 3.12) + rspec-support (~> 3.12) + rspec-sidekiq (4.0.1) + rspec-core (~> 3.0) + rspec-expectations (~> 3.0) + rspec-mocks (~> 3.0) + sidekiq (>= 5, < 8) + rspec-support (3.12.1) + rspec_chunked (0.6) + rubocop (1.56.3) + base64 (~> 0.1.1) + json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.1.0.0) + parser (>= 3.2.2.3) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.18.0, < 2.0) + rubocop-ast (>= 1.28.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.18.0) - parser (>= 3.1.1.0) - rubocop-rails (2.15.0) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.29.0) + parser (>= 3.2.1.0) + rubocop-capybara (2.18.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.23.1) + rubocop (~> 1.33) + rubocop-performance (1.19.0) + rubocop (>= 1.7.0, < 2.0) + rubocop-ast (>= 0.4.0) + rubocop-rails (2.20.2) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 1.7.0, < 2.0) - ruby-progressbar (1.11.0) - ruby-saml (1.13.0) - nokogiri (>= 1.10.5) + rubocop (>= 1.33.0, < 2.0) + rubocop-rspec (2.23.2) + rubocop (~> 1.33) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + ruby-prof (1.6.3) + ruby-progressbar (1.13.0) + ruby-saml (1.15.0) + nokogiri (>= 1.13.10) rexml ruby2_keywords (0.0.5) - rufus-scheduler (3.8.2) + rubyzip (2.3.2) + rufus-scheduler (3.9.1) fugit (~> 1.1, >= 1.1.6) safety_net_attestation (0.4.0) jwt (~> 2.0) sanitize (6.0.2) crass (~> 1.0.2) nokogiri (>= 1.12.0) - scenic (1.6.0) + scenic (1.7.0) activerecord (>= 4.0.0) railties (>= 4.0.0) + selenium-webdriver (4.11.0) + rexml (~> 3.2, >= 3.2.5) + rubyzip (>= 1.2.2, < 3.0) + websocket (~> 1.0) semantic_range (3.0.0) sidekiq (6.5.12) connection_pool (>= 2.2.5, < 3) @@ -615,10 +693,9 @@ GEM redis (>= 4.5.0, < 5) sidekiq-bulk (0.2.0) sidekiq - sidekiq-scheduler (4.0.3) - redis (>= 4.2.0) + sidekiq-scheduler (5.0.3) rufus-scheduler (~> 3.2) - sidekiq (>= 4, < 7) + sidekiq (>= 6, < 8) tilt (>= 1.4.0) sidekiq-unique-jobs (7.1.33) brpoplpush-redis_script (> 0.1.1, <= 2.0.0) @@ -628,15 +705,15 @@ GEM thor (>= 0.20, < 3.0) simple-navigation (4.4.0) activesupport (>= 2.3.2) - simple_form (5.1.0) + simple_form (5.2.0) actionpack (>= 5.2) activemodel (>= 5.2) - simplecov (0.21.2) + simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) - simplecov_json_formatter (0.1.2) + simplecov_json_formatter (0.1.4) smart_properties (1.17.0) sprockets (3.7.2) concurrent-ruby (~> 1.0) @@ -645,28 +722,33 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) - sshkit (1.21.2) + sshkit (1.21.5) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) - stackprof (0.2.22) + stackprof (0.2.25) statsd-ruby (1.5.0) - stoplight (3.0.0) - strong_migrations (0.7.9) - activerecord (>= 5) + stoplight (3.0.2) + redlock (~> 1.0) + strong_migrations (0.8.0) + activerecord (>= 5.2) + strscan (3.0.9) swd (1.3.0) activesupport (>= 3) attr_required (>= 0.0.5) httpclient (>= 2.4) - temple (0.8.2) + sysexits (1.2.0) + temple (0.10.2) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) terrapin (0.6.0) climate_control (>= 0.0.3, < 1.0) - thor (1.2.2) - tilt (2.0.11) - tpm-key_attestation (0.11.0) + test-prof (1.2.3) + thor (1.3.1) + tilt (2.2.0) + timeout (0.4.1) + tpm-key_attestation (0.12.0) bindata (~> 2.4) - openssl (> 2.0, < 3.1) + openssl (> 2.0) openssl-signature_algorithm (~> 1.0) tty-color (0.6.0) tty-cursor (0.7.1) @@ -683,13 +765,13 @@ GEM unf (~> 0.1.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - tzinfo-data (1.2022.4) + tzinfo-data (1.2023.3) tzinfo (>= 1.0.0) unf (0.1.4) unf_ext unf_ext (0.0.8.2) - unicode-display_width (2.3.0) - uniform_notifier (1.16.0) + unicode-display_width (2.4.2) + uri (0.12.2) validate_email (0.1.6) activemodel (>= 3.0) mail (>= 2.2.5) @@ -698,27 +780,28 @@ GEM public_suffix warden (1.2.9) rack (>= 2.0.9) - webauthn (2.5.2) + webauthn (3.0.0) android_key_attestation (~> 0.3.0) awrence (~> 1.1) bindata (~> 2.4) cbor (~> 0.5.9) cose (~> 1.1) - openssl (>= 2.2, < 3.1) + openssl (>= 2.2) safety_net_attestation (~> 0.4.0) - tpm-key_attestation (~> 0.11.0) + tpm-key_attestation (~> 0.12.0) webfinger (1.2.0) activesupport httpclient (>= 2.4) - webmock (3.18.1) + webmock (3.19.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webpacker (5.4.3) + webpacker (5.4.4) activesupport (>= 5.2) rack-proxy (>= 0.6.1) railties (>= 5.2) semantic_range (>= 2.3.0) + websocket (1.2.9) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) @@ -726,139 +809,145 @@ GEM xorcist (1.1.3) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.12) + zeitwerk (2.6.16) PLATFORMS ruby DEPENDENCIES active_model_serializers (~> 0.10) - active_record_query_trace (~> 1.8) addressable (~> 2.8) annotate (~> 3.2) - aws-sdk-s3 (~> 1.114) + aws-sdk-s3 (~> 1.123) better_errors (~> 2.9) binding_of_caller (~> 1.0) blurhash (~> 0.1) - bootsnap (~> 1.13.0) - brakeman (~> 5.3) + bootsnap (~> 1.16.0) + brakeman (~> 6.0) browser - bullet (~> 7.0) bundler-audit (~> 0.9) capistrano (~> 3.17) capistrano-rails (~> 1.6) capistrano-rbenv (~> 2.2) capistrano-yarn (~> 2.0) - capybara (~> 3.37) + capybara (~> 3.39) charlock_holmes (~> 0.7.7) - chewy (~> 7.2) + chewy (~> 7.3) climate_control (~> 0.2) cocoon (~> 1.2) color_diff (~> 0.1) concurrent-ruby connection_pool - devise (~> 4.8) - devise-two-factor (~> 4.0) + database_cleaner-active_record + devise (~> 4.9) + devise-two-factor (~> 4.1) devise_pam_authenticatable2 (~> 9.2) discard (~> 1.2) doorkeeper (~> 5.6) dotenv-rails (~> 2.8) ed25519 (~> 1.3) fabrication (~> 2.30) - faker (~> 2.23) + faker (~> 3.2) fast_blank (~> 1.0) fastimage - fog-core (<= 2.1.0) + fog-core (<= 2.4.0) fog-openstack (~> 0.3) fuubar (~> 2.5) - gitlab-omniauth-openid-connect (~> 0.10.0) - hamlit-rails (~> 0.2) + haml-rails (~> 2.0) + haml_lint + hcaptcha (~> 7.1) hiredis (~> 0.6) htmlentities (~> 4.3) http (~> 5.1) http_accept_language (~> 2.1) - httplog (~> 1.6.0) + httplog (~> 1.6.2) i18n-tasks (~> 1.0) idn-ruby json-ld json-ld-preloaded (~> 3.2) + json-schema (~> 4.0) kaminari (~> 1.2) - kt-paperclip (~> 7.1) + kt-paperclip (~> 7.2) letter_opener (~> 1.8) letter_opener_web (~> 2.0) link_header (~> 0.0) lograge (~> 0.12) - makara (~> 0.5) + mail (~> 2.8) mario-redis-lock (~> 1.2) + md-paperclip-azure (~> 2.2) memory_profiler - microformats (~> 4.4) - mime-types (~> 3.4.1) - net-ldap (~> 0.17) - nokogiri (~> 1.13) - nsa (~> 0.2) - oj (~> 3.13) - omniauth (~> 1.9) - omniauth-cas (~> 2.0) - omniauth-rails_csrf_protection (~> 0.1) - omniauth-saml (~> 1.10) + mime-types (~> 3.5.0) + net-http (~> 0.3.2) + net-ldap (~> 0.18) + nokogiri (~> 1.15) + nsa + oj (~> 3.14) + omniauth (~> 2.0) + omniauth-cas! + omniauth-rails_csrf_protection (~> 1.0) + omniauth-saml (~> 2.0) + omniauth_openid_connect (~> 0.6.1) ox (~> 2.14) parslet - pg (~> 1.4) - pghero (~> 2.8) - pkg-config (~> 1.4) - posix-spawn + pg (~> 1.5) + pghero premailer-rails private_address_check (~> 0.5) - pry-byebug (~> 3.10) - pry-rails (~> 0.3) - puma (~> 5.6) - pundit (~> 2.2) - rack (~> 2.2.4) + public_suffix (~> 5.0) + puma (~> 6.3) + pundit (~> 2.3) + rack (~> 2.2.7) rack-attack (~> 6.6) - rack-cors (~> 1.1) - rack-test (~> 2.0) - rails (~> 6.1.7) + rack-cors (~> 2.0) + rack-test (~> 2.1) + rails (~> 7.0) rails-controller-testing (~> 1.0) - rails-i18n (~> 6.0) - rails-settings-cached (~> 0.6) + rails-i18n (~> 7.0) + rails-settings-cached (~> 0.6)! rdf-normalize (~> 0.5) - redcarpet (~> 3.5) + redcarpet (~> 3.6) redis (~> 4.5) - redis-namespace (~> 1.9) - rexml (~> 3.2) - rqrcode (~> 2.1) - rspec-rails (~> 5.1) - rspec-sidekiq (~> 3.1) - rspec_junit_formatter (~> 0.6) - rubocop (~> 1.30) - rubocop-rails (~> 2.15) - ruby-progressbar (~> 1.11) + redis-namespace (~> 1.10) + rqrcode (~> 2.2) + rspec-rails (~> 6.0) + rspec-sidekiq (~> 4.0) + rspec_chunked (~> 0.6) + rubocop + rubocop-capybara + rubocop-performance + rubocop-rails + rubocop-rspec + ruby-prof + ruby-progressbar (~> 1.13) + rubyzip (~> 2.3) sanitize (~> 6.0) - scenic (~> 1.6) + scenic (~> 1.7) + selenium-webdriver sidekiq (~> 6.5) sidekiq-bulk (~> 0.2.0) - sidekiq-scheduler (~> 4.0) + sidekiq-scheduler (~> 5.0) sidekiq-unique-jobs (~> 7.1) simple-navigation (~> 4.4) - simple_form (~> 5.1) - simplecov (~> 0.21) + simple_form (~> 5.2) + simplecov (~> 0.22) sprockets (~> 3.7.2) sprockets-rails (~> 3.4) stackprof - stoplight (~> 3.0.0) - strong_migrations (~> 0.7) + stoplight (~> 3.0.1) + strong_migrations (~> 0.8) + test-prof thor (~> 1.2) tty-prompt (~> 0.23) twitter-text (~> 3.1.0) - tzinfo-data (~> 1.2022) - webauthn (~> 2.5) + tzinfo-data (~> 1.2023) + webauthn (~> 3.0) webmock (~> 3.18) webpacker (~> 5.4) webpush! xorcist (~> 1.1) RUBY VERSION - ruby 3.0.3p157 + ruby 3.2.2p53 BUNDLED WITH - 2.3.25 + 2.4.13 diff --git a/Procfile.dev b/Procfile.dev index ba04fb661..fbb2c2de2 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,4 +1,4 @@ web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq stream: env PORT=4000 yarn run start -webpack: ./bin/webpack-dev-server --listen-host 0.0.0.0 +webpack: bin/webpack-dev-server diff --git a/README.md b/README.md index c89345085..483d62ad4 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Photo by [Joana Mujollari](https://www.flickr.com/photos/141654969@N04/26777339042/), CC0 / Public Domain. -Mastodon is a **free, open-source social network server** based on ActivityPub. This is *not* the official version of Mastodon; this is a separate version (i.e. a fork) maintained by [Darius Kazemi](https://friend.camp/@darius). For more information on Mastodon, you can see the [official website](https://joinmastodon.org) and the [upstream repo](https://github.com/tootsuite/mastodon). +Mastodon is a **free, open-source social network server** based on ActivityPub. This is _not_ the official version of Mastodon; this is a separate version (i.e. a fork) maintained by [Darius Kazemi](https://friend.camp/@darius). For more information on Mastodon, you can see the [official website](https://joinmastodon.org) and the [upstream repo](https://github.com/tootsuite/mastodon). -__Hometown__ is a light weight fork of Mastodon. By "light weight" I don't mean more efficient; I mean this fork is 99.999% identical to Mastodon with a few key tweaks. This project is based on the principle of: minimum code change for maximum user experience change. This makes it easy for the basically-one-person who runs the project to keep it up to date. By our best understanding, our major changes are not wanted by the Mastodon project, hence maintaining this fork instead of trying to commit the changes to Mastodon. +**Hometown** is a light weight fork of Mastodon. By "light weight" I don't mean more efficient; I mean this fork is 99.999% identical to Mastodon with a few key tweaks. This project is based on the principle of: minimum code change for maximum user experience change. This makes it easy for the basically-one-person who runs the project to keep it up to date. By our best understanding, our major changes are not wanted by the Mastodon project, hence maintaining this fork instead of trying to commit the changes to Mastodon. Please [check out our wiki](https://github.com/hometown-fork/hometown/wiki) for a list of Hometown-exclusive features. Some but not all of these are covered in this document. @@ -81,14 +81,14 @@ Hometown uses [semantic versioning](https://semver.org) and follows a versioning ## Contributing to Hometown Setting up your Hometown development environment is [exactly like setting up your Mastodon development environment](https://docs.joinmastodon.org/dev/overview/). Pull requests should be made to the `hometown-dev` branch, which is our default branch in Github. -======= + You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository or submit translations using Crowdin. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md). If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon). **IRC channel**: #mastodon on irc.libera.chat ## License -Copyright (C) 2016-2022 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md)) +Copyright (C) 2016-2023 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md)) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. diff --git a/Rakefile b/Rakefile index ba6b733dd..e51cf0e17 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,8 @@ +# frozen_string_literal: true + # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require File.expand_path('../config/application', __FILE__) +require File.expand_path('config/application', __dir__) Rails.application.load_tasks diff --git a/SECURITY.md b/SECURITY.md index 5091fb9e6..81472b01b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,8 +1,11 @@ # Security Policy -If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can reach us at . +If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can either: -You should *not* report such issues on GitHub or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk. +- open a [Github security issue on the Mastodon project](https://github.com/mastodon/mastodon/security/advisories/new) +- reach us at + +You should _not_ report such issues on public GitHub issues or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk. ## Scope @@ -10,8 +13,8 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through ## Supported Versions -| Version | Supported | -| ------- | ------------------ | -| 4.1.x | Yes | -| 4.0.x | No | -| < 4.0 | No | +| Version | Supported | +| ------- | --------- | +| 4.2.x | Yes | +| 4.1.x | Yes | +| < 4.1 | No | diff --git a/Vagrantfile b/Vagrantfile index 3e73d9e47..4303f8e06 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -3,16 +3,14 @@ ENV["PORT"] ||= "3000" -$provision = < - - - - - - - 中国域名网站 - - - -
- -
- - -
-
-

网址大全

- -
-
-
-
-
-

中文域名简介

-

- “中国域名”是中文域名的一种,特指以“中国”为后缀的中文域名,是我国域名体系和全球互联网域名体系的重要组成部分。“中国”是在全球互联网上代表中国的中文顶级域名,于2010年7月正式纳入全球互联网域名体系,全球互联网域名体系,全球网民可通过联网计算机在世界任何国家和地区实现无障碍访问。“中国”域名在使用上和 .CN,相似属于互联网上的基础服务,基于域名可以提供WWW.EMAIL FTP等应用服务。 -

-
- - - - diff --git a/spec/fixtures/requests/low_confidence_latin1.txt b/spec/fixtures/requests/low_confidence_latin1.txt new file mode 100644 index 000000000..39c3e23d6 --- /dev/null +++ b/spec/fixtures/requests/low_confidence_latin1.txt @@ -0,0 +1,17 @@ +HTTP/1.1 200 OK +server: nginx +date: Thu, 13 Jun 2024 14:33:13 GMT +content-type: text/html; charset=ISO-8859-1 +content-length: 158 +accept-ranges: bytes + + + + + + Tofu l'orange + + +

Tofu l'orange

+ + diff --git a/spec/generators/post_deployment_migration_generator_spec.rb b/spec/generators/post_deployment_migration_generator_spec.rb new file mode 100644 index 000000000..d552880e3 --- /dev/null +++ b/spec/generators/post_deployment_migration_generator_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'rails/generators/testing/behaviour' +require 'rails/generators/testing/assertions' + +require 'generators/post_deployment_migration/post_deployment_migration_generator' + +describe PostDeploymentMigrationGenerator, type: :generator do + include Rails::Generators::Testing::Behaviour + include Rails::Generators::Testing::Assertions + include FileUtils + + tests described_class + destination File.expand_path('../../tmp', __dir__) + before { prepare_destination } + after { rm_rf(destination_root) } + + describe 'the migration' do + it 'generates expected file' do + run_generator %w(Changes) + + assert_migration('db/post_migrate/changes.rb', /disable_ddl/) + assert_migration('db/post_migrate/changes.rb', /change/) + end + end +end diff --git a/spec/helpers/accounts_helper_spec.rb b/spec/helpers/accounts_helper_spec.rb index 2b35b23b7..2c949cde6 100644 --- a/spec/helpers/accounts_helper_spec.rb +++ b/spec/helpers/accounts_helper_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe AccountsHelper, type: :helper do +RSpec.describe AccountsHelper do def set_not_embedded_view params[:controller] = "not_#{StatusesHelper::EMBEDDED_CONTROLLER}" params[:action] = "not_#{StatusesHelper::EMBEDDED_ACTION}" @@ -13,15 +15,15 @@ RSpec.describe AccountsHelper, type: :helper do describe '#display_name' do it 'uses the display name when it exists' do - account = Account.new(display_name: "Display", username: "Username") + account = Account.new(display_name: 'Display', username: 'Username') - expect(helper.display_name(account)).to eq "Display" + expect(helper.display_name(account)).to eq 'Display' end it 'uses the username when display name is nil' do - account = Account.new(display_name: nil, username: "Username") + account = Account.new(display_name: nil, username: 'Username') - expect(helper.display_name(account)).to eq "Username" + expect(helper.display_name(account)).to eq 'Username' end end diff --git a/spec/helpers/admin/account_moderation_notes_helper_spec.rb b/spec/helpers/admin/account_moderation_notes_helper_spec.rb index 622ce8806..6386f07ac 100644 --- a/spec/helpers/admin/account_moderation_notes_helper_spec.rb +++ b/spec/helpers/admin/account_moderation_notes_helper_spec.rb @@ -2,11 +2,11 @@ require 'rails_helper' -RSpec.describe Admin::AccountModerationNotesHelper, type: :helper do +RSpec.describe Admin::AccountModerationNotesHelper do include AccountsHelper describe '#admin_account_link_to' do - context 'account is nil' do + context 'when Account is nil' do let(:account) { nil } it 'returns nil' do @@ -30,7 +30,7 @@ RSpec.describe Admin::AccountModerationNotesHelper, type: :helper do end describe '#admin_account_inline_link_to' do - context 'account is nil' do + context 'when Account is nil' do let(:account) { nil } it 'returns nil' do @@ -42,13 +42,11 @@ RSpec.describe Admin::AccountModerationNotesHelper, type: :helper do let(:account) { Fabricate(:account) } it 'calls #link_to' do - expect(helper).to receive(:link_to).with( - admin_account_path(account.id), - class: name_tag_classes(account, true), - title: account.acct - ) + result = helper.admin_account_inline_link_to(account) - helper.admin_account_inline_link_to(account) + expect(result).to match(name_tag_classes(account, true)) + expect(result).to match(account.acct) + expect(result).to match(admin_account_path(account.id)) end end end diff --git a/spec/helpers/admin/action_log_helper_spec.rb b/spec/helpers/admin/action_log_helper_spec.rb deleted file mode 100644 index 9d7ed4ab7..000000000 --- a/spec/helpers/admin/action_log_helper_spec.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Admin::ActionLogsHelper, type: :helper do -end diff --git a/spec/helpers/admin/dashboard_helper_spec.rb b/spec/helpers/admin/dashboard_helper_spec.rb new file mode 100644 index 000000000..59062e483 --- /dev/null +++ b/spec/helpers/admin/dashboard_helper_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::DashboardHelper do + describe 'relevant_account_timestamp' do + context 'with an account with older sign in' do + let(:account) { Fabricate(:account) } + let(:stamp) { 10.days.ago } + + it 'returns a time element' do + account.user.update(current_sign_in_at: stamp) + result = helper.relevant_account_timestamp(account) + + expect(result).to match('time-ago') + expect(result).to match(I18n.l(stamp)) + end + end + + context 'with an account with newer sign in' do + let(:account) { Fabricate(:account) } + + it 'returns a time element' do + account.user.update(current_sign_in_at: 10.hours.ago) + result = helper.relevant_account_timestamp(account) + + expect(result).to eq(I18n.t('generic.today')) + end + end + + context 'with an account where the user is pending' do + let(:account) { Fabricate(:account) } + + it 'returns a time element' do + account.user.update(current_sign_in_at: nil) + account.user.update(approved: false) + result = helper.relevant_account_timestamp(account) + + expect(result).to match('time-ago') + expect(result).to match(I18n.l(account.user.created_at)) + end + end + + context 'with an account with a last status value' do + let(:account) { Fabricate(:account) } + let(:stamp) { 5.minutes.ago } + + it 'returns a time element' do + account.user.update(current_sign_in_at: nil) + account.account_stat.update(last_status_at: stamp) + result = helper.relevant_account_timestamp(account) + + expect(result).to match('time-ago') + expect(result).to match(I18n.l(stamp)) + end + end + + context 'with an account without sign in or last status or pending' do + let(:account) { Fabricate(:account) } + + it 'returns a time element' do + account.user.update(current_sign_in_at: nil) + result = helper.relevant_account_timestamp(account) + + expect(result).to eq('-') + end + end + end +end diff --git a/spec/helpers/admin/filter_helper_spec.rb b/spec/helpers/admin/filter_helper_spec.rb index 9d4ea2829..40ed63239 100644 --- a/spec/helpers/admin/filter_helper_spec.rb +++ b/spec/helpers/admin/filter_helper_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Admin::FilterHelper do @@ -5,8 +7,7 @@ describe Admin::FilterHelper do params = ActionController::Parameters.new( { test: 'test' } ) - allow(helper).to receive(:params).and_return(params) - allow(helper).to receive(:url_for).and_return('/test') + allow(helper).to receive_messages(params: params, url_for: '/test') result = helper.filter_link_to('text', { resolved: true }) expect(result).to match(/text/) diff --git a/spec/helpers/admin/trends/statuses_helper_spec.rb b/spec/helpers/admin/trends/statuses_helper_spec.rb new file mode 100644 index 000000000..92caae690 --- /dev/null +++ b/spec/helpers/admin/trends/statuses_helper_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Trends::StatusesHelper do + describe '.one_line_preview' do + before do + allow(helper).to receive(:current_user).and_return(Fabricate.build(:user)) + end + + context 'with a local status' do + let(:status) { Fabricate.build(:status, text: 'Test local status') } + + it 'renders a correct preview text' do + result = helper.one_line_preview(status) + + expect(result).to eq 'Test local status' + end + end + + context 'with a remote status' do + let(:status) { Fabricate.build(:status, uri: 'https://sfd.sdf', text: '

Test remote status

text

') } + + it 'renders a correct preview text' do + result = helper.one_line_preview(status) + + expect(result).to eq 'Test remote status' + end + end + + context 'with a status that has empty text' do + let(:status) { Fabricate.build(:status, text: '') } + + it 'renders a correct preview text' do + result = helper.one_line_preview(status) + + expect(result).to eq '' + end + end + + context 'with a status that has emoji' do + before { Fabricate(:custom_emoji, shortcode: 'florpy') } + + let(:status) { Fabricate(:status, text: 'hello there :florpy:') } + + it 'renders a correct preview text' do + result = helper.one_line_preview(status) + + expect(result).to match 'hello there' + expect(result).to match ' Hello this is a nice message for you to quote. + > Be careful because it has two lines. + EXPECTED + end + end + + describe 'storage_host' do + context 'when S3 alias is present' do + around do |example| + ClimateControl.modify S3_ALIAS_HOST: 's3.alias' do + example.run + end + end + + it 'returns true' do + expect(helper.storage_host).to eq('https://s3.alias') + end + end + + context 'when S3 alias includes a path component' do + around do |example| + ClimateControl.modify S3_ALIAS_HOST: 's3.alias/path' do + example.run + end + end + + it 'returns a correct URL' do + expect(helper.storage_host).to eq('https://s3.alias/path') + end + end + + context 'when S3 cloudfront is present' do + around do |example| + ClimateControl.modify S3_CLOUDFRONT_HOST: 's3.cloudfront' do + example.run + end + end + + it 'returns true' do + expect(helper.storage_host).to eq('https://s3.cloudfront') + end + end + end + + describe 'storage_host?' do + context 'when S3 alias is present' do + around do |example| + ClimateControl.modify S3_ALIAS_HOST: 's3.alias' do + example.run + end + end + + it 'returns true' do + expect(helper.storage_host?).to be true + end + end + + context 'when S3 cloudfront is present' do + around do |example| + ClimateControl.modify S3_CLOUDFRONT_HOST: 's3.cloudfront' do + example.run + end + end + + it 'returns true' do + expect(helper.storage_host?).to be true + end + end + + context 'when neither env value is present' do + it 'returns false' do + expect(helper.storage_host?).to be false + end + end + end + + describe 'visibility_icon' do + it 'returns a globe icon for a public visible status' do + result = helper.visibility_icon Status.new(visibility: 'public') + expect(result).to match(/globe/) + end + + it 'returns an unlock icon for a unlisted visible status' do + result = helper.visibility_icon Status.new(visibility: 'unlisted') + expect(result).to match(/unlock/) + end + + it 'returns a lock icon for a private visible status' do + result = helper.visibility_icon Status.new(visibility: 'private') + expect(result).to match(/lock/) + end + + it 'returns an at icon for a direct visible status' do + result = helper.visibility_icon Status.new(visibility: 'direct') + expect(result).to match(/at/) + end + end + describe 'title' do around do |example| site_title = Setting.site_title @@ -113,10 +290,11 @@ describe ApplicationHelper do Setting.site_title = site_title end - it 'returns site title on production enviroment' do + it 'returns site title on production environment' do Setting.site_title = 'site title' - expect(Rails.env).to receive(:production?).and_return(true) + allow(Rails.env).to receive(:production?).and_return(true) expect(helper.title).to eq 'site title' + expect(Rails.env).to have_received(:production?) end end end diff --git a/spec/helpers/flashes_helper_spec.rb b/spec/helpers/flashes_helper_spec.rb index ea143eed7..035e8a1de 100644 --- a/spec/helpers/flashes_helper_spec.rb +++ b/spec/helpers/flashes_helper_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -describe FlashesHelper, type: :helper do +describe FlashesHelper do describe 'user_facing_flashes' do it 'returns user facing flashes' do flash[:alert] = 'an alert' diff --git a/spec/helpers/formatting_helper_spec.rb b/spec/helpers/formatting_helper_spec.rb new file mode 100644 index 000000000..d6e7631f6 --- /dev/null +++ b/spec/helpers/formatting_helper_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe FormattingHelper do + include Devise::Test::ControllerHelpers + + describe '#rss_status_content_format' do + let(:status) { Fabricate(:status, text: 'Hello world<>', spoiler_text: 'This is a spoiler<>', poll: Fabricate(:poll, options: %w(Yes<> No))) } + let(:html) { helper.rss_status_content_format(status) } + + it 'renders the spoiler text' do + expect(html).to include('

This is a spoiler<>


') + end + + it 'renders the status text' do + expect(html).to include('

Hello world<>

') + end + + it 'renders the poll' do + expect(html).to include('Yes<>
') + end + end +end diff --git a/spec/helpers/home_helper_spec.rb b/spec/helpers/home_helper_spec.rb index a3dc6f836..c6baec5a1 100644 --- a/spec/helpers/home_helper_spec.rb +++ b/spec/helpers/home_helper_spec.rb @@ -1,9 +1,119 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe HomeHelper, type: :helper do +RSpec.describe HomeHelper do describe 'default_props' do it 'returns default properties according to the context' do expect(helper.default_props).to eq locale: I18n.locale end end + + describe 'account_link_to' do + context 'with a missing account' do + let(:account) { nil } + + it 'returns a button' do + result = helper.account_link_to(account) + + expect(result).to match t('about.contact_missing') + end + end + + context 'with a valid account' do + let(:account) { Fabricate(:account) } + + it 'returns a link to the account' do + without_partial_double_verification do + allow(helper).to receive_messages(current_account: account, prefers_autoplay?: false) + result = helper.account_link_to(account) + + expect(result).to match "@#{account.acct}" + end + end + end + end + + describe 'obscured_counter' do + context 'with a value of less than zero' do + let(:count) { -10 } + + it 'returns the correct string' do + expect(helper.obscured_counter(count)).to eq '0' + end + end + + context 'with a value of zero' do + let(:count) { 0 } + + it 'returns the correct string' do + expect(helper.obscured_counter(count)).to eq '0' + end + end + + context 'with a value of one' do + let(:count) { 1 } + + it 'returns the correct string' do + expect(helper.obscured_counter(count)).to eq '1' + end + end + + context 'with a value of more than one' do + let(:count) { 10 } + + it 'returns the correct string' do + expect(helper.obscured_counter(count)).to eq '1+' + end + end + end + + describe 'custom_field_classes' do + context 'with a verified field' do + let(:field) { instance_double(Account::Field, verified?: true) } + + it 'returns verified string' do + result = helper.custom_field_classes(field) + expect(result).to eq 'verified' + end + end + + context 'with a non-verified field' do + let(:field) { instance_double(Account::Field, verified?: false) } + + it 'returns verified string' do + result = helper.custom_field_classes(field) + expect(result).to eq 'emojify' + end + end + end + + describe 'sign_up_messages' do + context 'with closed registrations' do + it 'returns correct sign up message' do + allow(helper).to receive(:closed_registrations?).and_return(true) + result = helper.sign_up_message + + expect(result).to eq t('auth.registration_closed', instance: 'cb6e6126.ngrok.io') + end + end + + context 'with open registrations' do + it 'returns correct sign up message' do + allow(helper).to receive_messages(closed_registrations?: false, open_registrations?: true) + result = helper.sign_up_message + + expect(result).to eq t('auth.register') + end + end + + context 'with approved registrations' do + it 'returns correct sign up message' do + allow(helper).to receive_messages(closed_registrations?: false, open_registrations?: false, approved_registrations?: true) + result = helper.sign_up_message + + expect(result).to eq t('auth.apply_for_account') + end + end + end end diff --git a/spec/helpers/jsonld_helper_spec.rb b/spec/helpers/jsonld_helper_spec.rb index 54355b848..6367492b9 100644 --- a/spec/helpers/jsonld_helper_spec.rb +++ b/spec/helpers/jsonld_helper_spec.rb @@ -22,14 +22,14 @@ describe JsonLdHelper do end describe '#first_of_value' do - context 'value.is_a?(Array)' do + context 'when value.is_a?(Array)' do it 'returns value.first' do value = ['a'] expect(helper.first_of_value(value)).to be 'a' end end - context '!value.is_a?(Array)' do + context 'with !value.is_a?(Array)' do it 'returns value' do value = 'a' expect(helper.first_of_value(value)).to be 'a' @@ -38,14 +38,14 @@ describe JsonLdHelper do end describe '#supported_context?' do - context "!json.nil? && equals_or_includes?(json['@context'], ActivityPub::TagManager::CONTEXT)" do + context 'when json is present and in an activitypub tagmanager context' do it 'returns true' do json = { '@context' => ActivityPub::TagManager::CONTEXT }.as_json expect(helper.supported_context?(json)).to be true end end - context 'else' do + context 'when not in activitypub tagmanager context' do it 'returns false' do json = nil expect(helper.supported_context?(json)).to be false @@ -66,14 +66,14 @@ describe JsonLdHelper do stub_request(:get, 'https://mallory.test/').to_return(body: '{"id": "https://marvin.test/"}', headers: { 'Content-Type': 'application/activity+json' }) stub_request(:get, 'https://marvin.test/').to_return(body: '{"id": "https://alice.test/"}', headers: { 'Content-Type': 'application/activity+json' }) - expect(fetch_resource('https://mallory.test/', false)).to eq nil + expect(fetch_resource('https://mallory.test/', false)).to be_nil end end context 'when the second argument is true' do it 'returns nil if the retrieved ID and the given URI does not match' do stub_request(:get, 'https://mallory.test/').to_return(body: '{"id": "https://alice.test/"}', headers: { 'Content-Type': 'application/activity+json' }) - expect(fetch_resource('https://mallory.test/', true)).to eq nil + expect(fetch_resource('https://mallory.test/', true)).to be_nil end end end @@ -81,7 +81,7 @@ describe JsonLdHelper do describe '#fetch_resource_without_id_validation' do it 'returns nil if the status code is not 200' do stub_request(:get, 'https://host.test/').to_return(status: 400, body: '{}', headers: { 'Content-Type': 'application/activity+json' }) - expect(fetch_resource_without_id_validation('https://host.test/')).to eq nil + expect(fetch_resource_without_id_validation('https://host.test/')).to be_nil end it 'returns hash' do @@ -90,7 +90,7 @@ describe JsonLdHelper do end end - context 'compaction and forwarding' do + context 'with compaction and forwarding' do let(:json) do { '@context' => [ @@ -113,7 +113,7 @@ describe JsonLdHelper do { 'type' => 'Mention', 'href' => ['foo'], - } + }, ], }, 'signature' => { @@ -150,7 +150,7 @@ describe JsonLdHelper do patch_for_forwarding!(json, compacted) expect(compacted['to']).to eq ['https://www.w3.org/ns/activitystreams#Public'] expect(compacted.dig('object', 'tag', 0, 'href')).to eq ['foo'] - expect(safe_for_forwarding?(json, compacted)).to eq true + expect(safe_for_forwarding?(json, compacted)).to be true end end @@ -160,14 +160,14 @@ describe JsonLdHelper do compacted = compact(json) deemed_compatible = patch_for_forwarding!(json, compacted) expect(compacted['to']).to eq ['https://www.w3.org/ns/activitystreams#Public'] - expect(safe_for_forwarding?(json, compacted)).to eq true + expect(safe_for_forwarding?(json, compacted)).to be true end it 'deems an unsafe compacting as such' do compacted = compact(json) deemed_compatible = patch_for_forwarding!(json, compacted) expect(compacted['to']).to eq ['https://www.w3.org/ns/activitystreams#Public'] - expect(safe_for_forwarding?(json, compacted)).to eq false + expect(safe_for_forwarding?(json, compacted)).to be false end end end diff --git a/spec/helpers/languages_helper_spec.rb b/spec/helpers/languages_helper_spec.rb index 217c9b239..98c8064a3 100644 --- a/spec/helpers/languages_helper_spec.rb +++ b/spec/helpers/languages_helper_spec.rb @@ -10,14 +10,54 @@ describe LanguagesHelper do end describe 'native_locale_name' do - it 'finds the human readable native name from a key' do - expect(helper.native_locale_name(:de)).to eq('Deutsch') + context 'with a blank locale' do + it 'defaults to a generic value' do + expect(helper.native_locale_name(nil)).to eq(I18n.t('generic.none')) + end + end + + context 'with a locale of `und`' do + it 'defaults to a generic value' do + expect(helper.native_locale_name('und')).to eq(I18n.t('generic.none')) + end + end + + context 'with a supported locale' do + it 'finds the human readable native name from a key' do + expect(helper.native_locale_name(:de)).to eq('Deutsch') + end + end + + context 'with a regional locale' do + it 'finds the human readable regional name from a key' do + expect(helper.native_locale_name('en-GB')).to eq('English (British)') + end + end + + context 'with a non-existent locale' do + it 'returns the supplied locale value' do + expect(helper.native_locale_name(:xxx)).to eq(:xxx) + end end end describe 'standard_locale_name' do - it 'finds the human readable standard name from a key' do - expect(helper.standard_locale_name(:de)).to eq('German') + context 'with a blank locale' do + it 'defaults to a generic value' do + expect(helper.standard_locale_name(nil)).to eq(I18n.t('generic.none')) + end + end + + context 'with a non-existent locale' do + it 'returns the supplied locale value' do + expect(helper.standard_locale_name(:xxx)).to eq(:xxx) + end + end + + context 'with a supported locale' do + it 'finds the human readable standard name from a key' do + expect(helper.standard_locale_name(:de)).to eq('German') + end end end end diff --git a/spec/helpers/media_component_helper_spec.rb b/spec/helpers/media_component_helper_spec.rb new file mode 100644 index 000000000..71a9af6f3 --- /dev/null +++ b/spec/helpers/media_component_helper_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe MediaComponentHelper do + describe 'render_video_component' do + let(:media) { Fabricate(:media_attachment, type: :video, status: Fabricate(:status)) } + let(:result) { helper.render_video_component(media.status) } + + before do + without_partial_double_verification do + allow(helper).to receive(:current_account).and_return(media.account) + end + end + + it 'renders a react component for the video' do + expect(parsed_html.div['data-component']).to eq('Video') + end + end + + describe 'render_audio_component' do + let(:media) { Fabricate(:media_attachment, type: :audio, status: Fabricate(:status)) } + let(:result) { helper.render_audio_component(media.status) } + + before do + without_partial_double_verification do + allow(helper).to receive(:current_account).and_return(media.account) + end + end + + it 'renders a react component for the audio' do + expect(parsed_html.div['data-component']).to eq('Audio') + end + end + + describe 'render_media_gallery_component' do + let(:media) { Fabricate(:media_attachment, type: :audio, status: Fabricate(:status)) } + let(:result) { helper.render_media_gallery_component(media.status) } + + before do + without_partial_double_verification do + allow(helper).to receive(:current_account).and_return(media.account) + end + end + + it 'renders a react component for the media gallery' do + expect(parsed_html.div['data-component']).to eq('MediaGallery') + end + end + + describe 'render_card_component' do + let(:status) { Fabricate(:status, preview_cards: [Fabricate(:preview_card)]) } + let(:result) { helper.render_card_component(status) } + + before do + without_partial_double_verification do + allow(helper).to receive(:current_account).and_return(status.account) + end + end + + it 'returns the correct react component markup' do + expect(parsed_html.div['data-component']).to eq('Card') + end + end + + describe 'render_poll_component' do + let(:status) { Fabricate(:status, poll: Fabricate(:poll)) } + let(:result) { helper.render_poll_component(status) } + + before do + without_partial_double_verification do + allow(helper).to receive(:current_account).and_return(status.account) + end + end + + it 'returns the correct react component markup' do + expect(parsed_html.div['data-component']).to eq('Poll') + end + end + + private + + def parsed_html + Nokogiri::Slop(result) + end +end diff --git a/spec/helpers/react_component_helper_spec.rb b/spec/helpers/react_component_helper_spec.rb new file mode 100644 index 000000000..28208b619 --- /dev/null +++ b/spec/helpers/react_component_helper_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ReactComponentHelper do + describe 'react_component' do + context 'with no block passed in' do + let(:result) { helper.react_component('name', { one: :two }) } + + it 'returns a tag with data attributes' do + expect(parsed_html.div['data-component']).to eq('Name') + expect(parsed_html.div['data-props']).to eq('{"one":"two"}') + end + end + + context 'with a block passed in' do + let(:result) do + helper.react_component('name', { one: :two }) do + helper.content_tag(:nav, 'ok') + end + end + + it 'returns a tag with data attributes' do + expect(parsed_html.div['data-component']).to eq('Name') + expect(parsed_html.div['data-props']).to eq('{"one":"two"}') + expect(parsed_html.div.nav.content).to eq('ok') + end + end + end + + describe 'react_admin_component' do + let(:result) { helper.react_admin_component('name', { one: :two }) } + + it 'returns a tag with data attributes' do + expect(parsed_html.div['data-admin-component']).to eq('Name') + expect(parsed_html.div['data-props']).to eq('{"one":"two"}') + end + end + + private + + def parsed_html + Nokogiri::Slop(result) + end +end diff --git a/spec/helpers/routing_helper_spec.rb b/spec/helpers/routing_helper_spec.rb index 940392c9b..852d02ceb 100644 --- a/spec/helpers/routing_helper_spec.rb +++ b/spec/helpers/routing_helper_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe RoutingHelper, type: :helper do +RSpec.describe RoutingHelper do describe '.full_asset_url' do around do |example| use_s3 = Rails.configuration.x.use_s3 @@ -24,7 +24,7 @@ RSpec.describe RoutingHelper, type: :helper do end end - context 'Do not use S3' do + context 'when not using S3' do before do Rails.configuration.x.use_s3 = false end @@ -32,7 +32,7 @@ RSpec.describe RoutingHelper, type: :helper do it_behaves_like 'returns full path URL' end - context 'Use S3' do + context 'when using S3' do before do Rails.configuration.x.use_s3 = true end diff --git a/spec/helpers/settings_helper_spec.rb b/spec/helpers/settings_helper_spec.rb new file mode 100644 index 000000000..cba5c6ee8 --- /dev/null +++ b/spec/helpers/settings_helper_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe SettingsHelper do + describe 'session_device_icon' do + context 'with a mobile device' do + let(:session) { SessionActivation.new(user_agent: 'Mozilla/5.0 (iPhone)') } + + it 'detects the device and returns a descriptive string' do + result = helper.session_device_icon(session) + + expect(result).to eq('mobile') + end + end + + context 'with a tablet device' do + let(:session) { SessionActivation.new(user_agent: 'Mozilla/5.0 (iPad)') } + + it 'detects the device and returns a descriptive string' do + result = helper.session_device_icon(session) + + expect(result).to eq('tablet') + end + end + + context 'with a desktop device' do + let(:session) { SessionActivation.new(user_agent: 'Mozilla/5.0 (Macintosh)') } + + it 'detects the device and returns a descriptive string' do + result = helper.session_device_icon(session) + + expect(result).to eq('desktop') + end + end + end +end diff --git a/spec/helpers/statuses_helper_spec.rb b/spec/helpers/statuses_helper_spec.rb index cba659bfb..c67e1f3f2 100644 --- a/spec/helpers/statuses_helper_spec.rb +++ b/spec/helpers/statuses_helper_spec.rb @@ -1,6 +1,96 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe StatusesHelper, type: :helper do +describe StatusesHelper do + describe 'status_text_summary' do + context 'with blank text' do + let(:status) { Status.new(spoiler_text: '') } + + it 'returns immediately with nil' do + result = helper.status_text_summary(status) + expect(result).to be_nil + end + end + + context 'with present text' do + let(:status) { Status.new(spoiler_text: 'SPOILERS!!!') } + + it 'returns the content warning' do + result = helper.status_text_summary(status) + expect(result).to eq(I18n.t('statuses.content_warning', warning: 'SPOILERS!!!')) + end + end + end + + def status_text_summary(status) + return if status.spoiler_text.blank? + + I18n.t('statuses.content_warning', warning: status.spoiler_text) + end + + describe 'link_to_newer' do + it 'returns a link to newer content' do + url = 'https://example.com' + result = helper.link_to_newer(url) + + expect(result).to match('load-more') + expect(result).to match(I18n.t('statuses.show_newer')) + end + end + + describe 'link_to_older' do + it 'returns a link to older content' do + url = 'https://example.com' + result = helper.link_to_older(url) + + expect(result).to match('load-more') + expect(result).to match(I18n.t('statuses.show_older')) + end + end + + describe 'fa_visibility_icon' do + context 'with a status that is public' do + let(:status) { Status.new(visibility: 'public') } + + it 'returns the correct fa icon' do + result = helper.fa_visibility_icon(status) + + expect(result).to match('fa-globe') + end + end + + context 'with a status that is unlisted' do + let(:status) { Status.new(visibility: 'unlisted') } + + it 'returns the correct fa icon' do + result = helper.fa_visibility_icon(status) + + expect(result).to match('fa-unlock') + end + end + + context 'with a status that is private' do + let(:status) { Status.new(visibility: 'private') } + + it 'returns the correct fa icon' do + result = helper.fa_visibility_icon(status) + + expect(result).to match('fa-lock') + end + end + + context 'with a status that is direct' do + let(:status) { Status.new(visibility: 'direct') } + + it 'returns the correct fa icon' do + result = helper.fa_visibility_icon(status) + + expect(result).to match('fa-at') + end + end + end + describe '#stream_link_target' do it 'returns nil if it is not an embedded view' do set_not_embedded_view @@ -24,129 +114,4 @@ RSpec.describe StatusesHelper, type: :helper do params[:controller] = StatusesHelper::EMBEDDED_CONTROLLER params[:action] = StatusesHelper::EMBEDDED_ACTION end - - describe '#style_classes' do - it do - status = double(reblog?: false) - classes = helper.style_classes(status, false, false, false) - - expect(classes).to eq 'entry' - end - - it do - status = double(reblog?: true) - classes = helper.style_classes(status, false, false, false) - - expect(classes).to eq 'entry entry-reblog' - end - - it do - status = double(reblog?: false) - classes = helper.style_classes(status, true, false, false) - - expect(classes).to eq 'entry entry-predecessor' - end - - it do - status = double(reblog?: false) - classes = helper.style_classes(status, false, true, false) - - expect(classes).to eq 'entry entry-successor' - end - - it do - status = double(reblog?: false) - classes = helper.style_classes(status, false, false, true) - - expect(classes).to eq 'entry entry-center' - end - - it do - status = double(reblog?: true) - classes = helper.style_classes(status, true, true, true) - - expect(classes).to eq 'entry entry-predecessor entry-reblog entry-successor entry-center' - end - end - - describe '#microformats_classes' do - it do - status = double(reblog?: false) - classes = helper.microformats_classes(status, false, false) - - expect(classes).to eq '' - end - - it do - status = double(reblog?: false) - classes = helper.microformats_classes(status, true, false) - - expect(classes).to eq 'p-in-reply-to' - end - - it do - status = double(reblog?: false) - classes = helper.microformats_classes(status, false, true) - - expect(classes).to eq 'p-comment' - end - - it do - status = double(reblog?: true) - classes = helper.microformats_classes(status, true, false) - - expect(classes).to eq 'p-in-reply-to p-repost-of' - end - - it do - status = double(reblog?: true) - classes = helper.microformats_classes(status, true, true) - - expect(classes).to eq 'p-in-reply-to p-repost-of p-comment' - end - end - - describe '#microformats_h_class' do - it do - status = double(reblog?: false) - css_class = helper.microformats_h_class(status, false, false, false) - - expect(css_class).to eq 'h-entry' - end - - it do - status = double(reblog?: true) - css_class = helper.microformats_h_class(status, false, false, false) - - expect(css_class).to eq 'h-cite' - end - - it do - status = double(reblog?: false) - css_class = helper.microformats_h_class(status, true, false, false) - - expect(css_class).to eq 'h-cite' - end - - it do - status = double(reblog?: false) - css_class = helper.microformats_h_class(status, false, true, false) - - expect(css_class).to eq 'h-cite' - end - - it do - status = double(reblog?: false) - css_class = helper.microformats_h_class(status, false, false, true) - - expect(css_class).to eq '' - end - - it do - status = double(reblog?: true) - css_class = helper.microformats_h_class(status, true, true, true) - - expect(css_class).to eq 'h-cite' - end - end end diff --git a/spec/lib/account_reach_finder_spec.rb b/spec/lib/account_reach_finder_spec.rb index 1da95ba6b..e5d85656a 100644 --- a/spec/lib/account_reach_finder_spec.rb +++ b/spec/lib/account_reach_finder_spec.rb @@ -5,31 +5,31 @@ require 'rails_helper' RSpec.describe AccountReachFinder do let(:account) { Fabricate(:account) } - let(:follower1) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-1') } - let(:follower2) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-2') } - let(:follower3) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/a/inbox', shared_inbox_url: 'https://foo.bar/inbox') } + let(:ap_follower_example_com) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-1', domain: 'example.com') } + let(:ap_follower_example_org) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.org/inbox-2', domain: 'example.org') } + let(:ap_follower_with_shared) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/a/inbox', domain: 'foo.bar', shared_inbox_url: 'https://foo.bar/inbox') } - let(:mentioned1) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/b/inbox', shared_inbox_url: 'https://foo.bar/inbox') } - let(:mentioned2) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-3') } - let(:mentioned3) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-4') } + let(:ap_mentioned_with_shared) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/b/inbox', domain: 'foo.bar', shared_inbox_url: 'https://foo.bar/inbox') } + let(:ap_mentioned_example_com) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-3', domain: 'example.com') } + let(:ap_mentioned_example_org) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.org/inbox-4', domain: 'example.org') } - let(:unrelated_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/unrelated-inbox') } + let(:unrelated_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/unrelated-inbox', domain: 'example.com') } before do - follower1.follow!(account) - follower2.follow!(account) - follower3.follow!(account) + ap_follower_example_com.follow!(account) + ap_follower_example_org.follow!(account) + ap_follower_with_shared.follow!(account) Fabricate(:status, account: account).tap do |status| - status.mentions << Mention.new(account: follower1) - status.mentions << Mention.new(account: mentioned1) + status.mentions << Mention.new(account: ap_follower_example_com) + status.mentions << Mention.new(account: ap_mentioned_with_shared) end Fabricate(:status, account: account) Fabricate(:status, account: account).tap do |status| - status.mentions << Mention.new(account: mentioned2) - status.mentions << Mention.new(account: mentioned3) + status.mentions << Mention.new(account: ap_mentioned_example_com) + status.mentions << Mention.new(account: ap_mentioned_example_org) end Fabricate(:status).tap do |status| @@ -39,11 +39,11 @@ RSpec.describe AccountReachFinder do describe '#inboxes' do it 'includes the preferred inbox URL of followers' do - expect(described_class.new(account).inboxes).to include(*[follower1, follower2, follower3].map(&:preferred_inbox_url)) + expect(described_class.new(account).inboxes).to include(*[ap_follower_example_com, ap_follower_example_org, ap_follower_with_shared].map(&:preferred_inbox_url)) end it 'includes the preferred inbox URL of recently-mentioned accounts' do - expect(described_class.new(account).inboxes).to include(*[mentioned1, mentioned2, mentioned3].map(&:preferred_inbox_url)) + expect(described_class.new(account).inboxes).to include(*[ap_mentioned_with_shared, ap_mentioned_example_com, ap_mentioned_example_org].map(&:preferred_inbox_url)) end it 'does not include the inbox of unrelated users' do diff --git a/spec/models/account_statuses_filter_spec.rb b/spec/lib/account_statuses_filter_spec.rb similarity index 85% rename from spec/models/account_statuses_filter_spec.rb rename to spec/lib/account_statuses_filter_spec.rb index 03f0ffeb0..c821eb4ba 100644 --- a/spec/models/account_statuses_filter_spec.rb +++ b/spec/lib/account_statuses_filter_spec.rb @@ -3,12 +3,12 @@ require 'rails_helper' RSpec.describe AccountStatusesFilter do + subject { described_class.new(account, current_account, params) } + let(:account) { Fabricate(:account) } let(:current_account) { nil } let(:params) { {} } - subject { described_class.new(account, current_account, params) } - def status!(visibility) Fabricate(:status, account: account, visibility: visibility) end @@ -199,6 +199,34 @@ RSpec.describe AccountStatusesFilter do end end + context 'when blocking a reblogged domain' do + let(:other_account) { Fabricate(:account, domain: 'example.com') } + let(:reblogging_status) { Fabricate(:status, account: other_account) } + let!(:reblog) { Fabricate(:status, account: account, visibility: 'public', reblog: reblogging_status) } + + before do + current_account.block_domain!(other_account.domain) + end + + it 'does not return reblog of blocked domain' do + expect(subject.results.pluck(:id)).to_not include(reblog.id) + end + end + + context 'when blocking an unrelated domain' do + let(:other_account) { Fabricate(:account, domain: nil) } + let(:reblogging_status) { Fabricate(:status, account: other_account, visibility: 'public') } + let!(:reblog) { Fabricate(:status, account: account, visibility: 'public', reblog: reblogging_status) } + + before do + current_account.block_domain!('example.com') + end + + it 'returns the reblog from the non-blocked domain' do + expect(subject.results.pluck(:id)).to include(reblog.id) + end + end + context 'when muting a reblogged account' do let(:reblog) { status_with_reblog!('public') } diff --git a/spec/lib/activitypub/activity/accept_spec.rb b/spec/lib/activitypub/activity/accept_spec.rb index 304cf2208..d6b607127 100644 --- a/spec/lib/activitypub/activity/accept_spec.rb +++ b/spec/lib/activitypub/activity/accept_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Accept do @@ -41,7 +43,9 @@ RSpec.describe ActivityPub::Activity::Accept do end end - context 'given a relay' do + context 'when given a relay' do + subject { described_class.new(json, sender) } + let!(:relay) { Fabricate(:relay, state: :pending, follow_activity_id: 'https://abc-123/456') } let(:json) do @@ -59,8 +63,6 @@ RSpec.describe ActivityPub::Activity::Accept do }.with_indifferent_access end - subject { described_class.new(json, sender) } - it 'marks the relay as accepted' do subject.perform expect(relay.reload.accepted?).to be true diff --git a/spec/lib/activitypub/activity/add_spec.rb b/spec/lib/activitypub/activity/add_spec.rb index e6408b610..ec6df0171 100644 --- a/spec/lib/activitypub/activity/add_spec.rb +++ b/spec/lib/activitypub/activity/add_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Add do @@ -24,7 +26,7 @@ RSpec.describe ActivityPub::Activity::Add do end context 'when status was not known before' do - let(:service_stub) { double } + let(:service_stub) { instance_double(ActivityPub::FetchRemoteStatusService) } let(:json) do { @@ -48,10 +50,10 @@ RSpec.describe ActivityPub::Activity::Add do end it 'fetches the status and pins it' do - allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil| + allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil, request_id: nil| # rubocop:disable Lint/UnusedBlockArgument expect(uri).to eq 'https://example.com/unknown' - expect(id).to eq true - expect(on_behalf_of&.following?(sender)).to eq true + expect(id).to be true + expect(on_behalf_of&.following?(sender)).to be true status end subject.perform @@ -62,10 +64,10 @@ RSpec.describe ActivityPub::Activity::Add do context 'when there is no local follower' do it 'tries to fetch the status' do - allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil| + allow(service_stub).to receive(:call) do |uri, id: true, on_behalf_of: nil, request_id: nil| # rubocop:disable Lint/UnusedBlockArgument expect(uri).to eq 'https://example.com/unknown' - expect(id).to eq true - expect(on_behalf_of).to eq nil + expect(id).to be true + expect(on_behalf_of).to be_nil nil end subject.perform diff --git a/spec/lib/activitypub/activity/announce_spec.rb b/spec/lib/activitypub/activity/announce_spec.rb index 2ca70712a..b556bfd6c 100644 --- a/spec/lib/activitypub/activity/announce_spec.rb +++ b/spec/lib/activitypub/activity/announce_spec.rb @@ -1,7 +1,11 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Announce do - let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', uri: 'https://example.com/actor') } + subject { described_class.new(json, sender) } + + let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', uri: 'https://example.com/actor', domain: 'example.com') } let(:recipient) { Fabricate(:account) } let(:status) { Fabricate(:status, account: recipient) } @@ -27,8 +31,6 @@ RSpec.describe ActivityPub::Activity::Announce do } end - subject { described_class.new(json, sender) } - describe '#perform' do context 'when sender is followed by a local account' do before do @@ -37,7 +39,7 @@ RSpec.describe ActivityPub::Activity::Announce do subject.perform end - context 'a known status' do + context 'with known status' do let(:object_json) do ActivityPub::TagManager.instance.uri_for(status) end @@ -47,7 +49,7 @@ RSpec.describe ActivityPub::Activity::Announce do end end - context 'an unknown status' do + context 'with unknown status' do let(:object_json) { 'https://example.com/actor/hello-world' } it 'creates a reblog by sender of status' do @@ -58,7 +60,7 @@ RSpec.describe ActivityPub::Activity::Announce do end end - context 'self-boost of a previously unknown status with correct attributedTo' do + context 'when self-boost of a previously unknown status with correct attributedTo' do let(:object_json) do { id: 'https://example.com/actor#bar', @@ -74,7 +76,7 @@ RSpec.describe ActivityPub::Activity::Announce do end end - context 'self-boost of a previously unknown status with correct attributedTo, inlined Collection in audience' do + context 'when self-boost of a previously unknown status with correct attributedTo, inlined Collection in audience' do let(:object_json) do { id: 'https://example.com/actor#bar', @@ -82,10 +84,10 @@ RSpec.describe ActivityPub::Activity::Announce do content: 'Lorem ipsum', attributedTo: 'https://example.com/actor', to: { - 'type': 'OrderedCollection', - 'id': 'http://example.com/followers', - 'first': 'http://example.com/followers?page=true', - } + type: 'OrderedCollection', + id: 'http://example.com/followers', + first: 'http://example.com/followers?page=true', + }, } end @@ -110,18 +112,18 @@ RSpec.describe ActivityPub::Activity::Announce do end context 'when the sender is relayed' do - let!(:relay_account) { Fabricate(:account, inbox_url: 'https://relay.example.com/inbox') } + subject { described_class.new(json, sender, relayed_through_actor: relay_account) } + + let!(:relay_account) { Fabricate(:account, inbox_url: 'https://relay.example.com/inbox', domain: 'relay.example.com') } let!(:relay) { Fabricate(:relay, inbox_url: 'https://relay.example.com/inbox') } let(:object_json) { 'https://example.com/actor/hello-world' } - subject { described_class.new(json, sender, relayed_through_actor: relay_account) } - before do stub_request(:get, 'https://example.com/actor/hello-world').to_return(body: Oj.dump(unknown_object_json), headers: { 'Content-Type': 'application/activity+json' }) end - context 'and the relay is enabled' do + context 'when the relay is enabled' do before do relay.update(state: :accepted) subject.perform @@ -133,13 +135,13 @@ RSpec.describe ActivityPub::Activity::Announce do end end - context 'and the relay is disabled' do + context 'when the relay is disabled' do before do subject.perform end it 'does not fetch the remote status' do - expect(a_request(:get, 'https://example.com/actor/hello-world')).not_to have_been_made + expect(a_request(:get, 'https://example.com/actor/hello-world')).to_not have_been_made expect(Status.find_by(uri: 'https://example.com/actor/hello-world')).to be_nil end diff --git a/spec/lib/activitypub/activity/block_spec.rb b/spec/lib/activitypub/activity/block_spec.rb index 42bdfdc81..6f6898401 100644 --- a/spec/lib/activitypub/activity/block_spec.rb +++ b/spec/lib/activitypub/activity/block_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Block do diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 1a25395fa..7594efd59 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Create do @@ -21,6 +23,109 @@ RSpec.describe ActivityPub::Activity::Create do stub_request(:get, 'http://example.com/emojib.png').to_return(body: attachment_fixture('emojo.png'), headers: { 'Content-Type' => 'application/octet-stream' }) end + describe 'processing posts received out of order' do + let(:follower) { Fabricate(:account, username: 'bob') } + + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), 'post1'].join('/'), + type: 'Note', + to: [ + 'https://www.w3.org/ns/activitystreams#Public', + ActivityPub::TagManager.instance.uri_for(follower), + ], + content: '@bob lorem ipsum', + published: 1.hour.ago.utc.iso8601, + updated: 1.hour.ago.utc.iso8601, + tag: { + type: 'Mention', + href: ActivityPub::TagManager.instance.uri_for(follower), + }, + } + end + + let(:reply_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), 'reply'].join('/'), + type: 'Note', + inReplyTo: object_json[:id], + to: [ + 'https://www.w3.org/ns/activitystreams#Public', + ActivityPub::TagManager.instance.uri_for(follower), + ], + content: '@bob lorem ipsum', + published: Time.now.utc.iso8601, + updated: Time.now.utc.iso8601, + tag: { + type: 'Mention', + href: ActivityPub::TagManager.instance.uri_for(follower), + }, + } + end + + def activity_for_object(json) + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: [json[:id], 'activity'].join('/'), + type: 'Create', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: json, + }.with_indifferent_access + end + + before do + follower.follow!(sender) + end + + around do |example| + Sidekiq::Testing.fake! do + example.run + Sidekiq::Worker.clear_all + end + end + + it 'correctly processes posts and inserts them in timelines', :aggregate_failures do + # Simulate a temporary failure preventing from fetching the parent post + stub_request(:get, object_json[:id]).to_return(status: 500) + + # When receiving the reply… + described_class.new(activity_for_object(reply_json), sender, delivery: true).perform + + # NOTE: Refering explicitly to the workers is a bit awkward + DistributionWorker.drain + FeedInsertWorker.drain + + # …it creates a status with an unknown parent + reply = Status.find_by(uri: reply_json[:id]) + expect(reply.reply?).to be true + expect(reply.in_reply_to_id).to be_nil + + # …and creates a notification + expect(LocalNotificationWorker.jobs.size).to eq 1 + + # …but does not insert it into timelines + expect(redis.zscore(FeedManager.instance.key(:home, follower.id), reply.id)).to be_nil + + # When receiving the parent… + described_class.new(activity_for_object(object_json), sender, delivery: true).perform + + Sidekiq::Worker.drain_all + + # …it creates a status and insert it into timelines + parent = Status.find_by(uri: object_json[:id]) + expect(parent.reply?).to be false + expect(parent.in_reply_to_id).to be_nil + expect(reply.reload.in_reply_to_id).to eq parent.id + + # Check that the both statuses have been inserted into the home feed + expect(redis.zscore(FeedManager.instance.key(:home, follower.id), parent.id)).to be_within(0.1).of(parent.id.to_f) + expect(redis.zscore(FeedManager.instance.key(:home, follower.id), reply.id)).to be_within(0.1).of(reply.id.to_f) + + # Creates two notifications + expect(Notification.count).to eq 2 + end + end + describe '#perform' do context 'when fetching' do subject { described_class.new(json, sender) } @@ -29,7 +134,47 @@ RSpec.describe ActivityPub::Activity::Create do subject.perform end - context 'object has been edited' do + context 'when object publication date is below ISO8601 range' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + published: '-0977-11-03T08:31:22Z', + } + end + + it 'creates status with a valid creation date', :aggregate_failures do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.text).to eq 'Lorem ipsum' + + expect(status.created_at).to be_within(30).of(Time.now.utc) + end + end + + context 'when object publication date is above ISO8601 range' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + published: '10000-11-03T08:31:22Z', + } + end + + it 'creates status with a valid creation date', :aggregate_failures do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.text).to eq 'Lorem ipsum' + + expect(status.created_at).to be_within(30).of(Time.now.utc) + end + end + + context 'when object has been edited' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -40,22 +185,20 @@ RSpec.describe ActivityPub::Activity::Create do } end - it 'creates status' do + it 'creates status with appropriate creation and edition dates', :aggregate_failures do status = sender.statuses.first expect(status).to_not be_nil expect(status.text).to eq 'Lorem ipsum' - end - it 'marks status as edited' do - status = sender.statuses.first + expect(status.created_at).to eq '2022-01-22T15:00:00Z'.to_datetime - expect(status).to_not be_nil - expect(status.edited?).to eq true + expect(status.edited?).to be true + expect(status.edited_at).to eq '2022-01-22T16:00:00Z'.to_datetime end end - context 'object has update date equal to creation date' do + context 'when object has update date equal to creation date' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -77,11 +220,11 @@ RSpec.describe ActivityPub::Activity::Create do status = sender.statuses.first expect(status).to_not be_nil - expect(status.edited?).to eq false + expect(status.edited?).to be false end end - context 'unknown object type' do + context 'with an unknown object type' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -95,7 +238,7 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'standalone' do + context 'with a standalone' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -119,7 +262,7 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'public with explicit public address' do + context 'when public with explicit public address' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -137,7 +280,7 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'public with as:Public' do + context 'when public with as:Public' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -155,7 +298,7 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'public with Public' do + context 'when public with Public' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -173,7 +316,7 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'unlisted with explicit public address' do + context 'when unlisted with explicit public address' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -191,7 +334,7 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'unlisted with as:Public' do + context 'when unlisted with as:Public' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -209,7 +352,7 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'unlisted with Public' do + context 'when unlisted with Public' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -227,7 +370,7 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'private' do + context 'when private' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -245,17 +388,17 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'private with inlined Collection in audience' do + context 'when private with inlined Collection in audience' do let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, type: 'Note', content: 'Lorem ipsum', to: { - 'type': 'OrderedCollection', - 'id': 'http://example.com/followers', - 'first': 'http://example.com/followers?page=true', - } + type: 'OrderedCollection', + id: 'http://example.com/followers', + first: 'http://example.com/followers?page=true', + }, } end @@ -267,7 +410,7 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'limited' do + context 'when limited' do let(:recipient) { Fabricate(:account) } let(:object_json) do @@ -292,7 +435,7 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'direct' do + context 'when direct' do let(:recipient) { Fabricate(:account) } let(:object_json) do @@ -316,7 +459,7 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'as a reply' do + context 'with a reply' do let(:original_status) { Fabricate(:status) } let(:object_json) do @@ -408,7 +551,6 @@ RSpec.describe ActivityPub::Activity::Create do end end - context 'with media attachments with long description' do let(:object_json) do { @@ -687,7 +829,7 @@ RSpec.describe ActivityPub::Activity::Create do replies: { type: 'Collection', totalItems: 3, - } + }, }, ], } @@ -717,7 +859,7 @@ RSpec.describe ActivityPub::Activity::Create do id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, type: 'Note', name: 'Yellow', - inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status) + inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status), } end @@ -742,7 +884,7 @@ RSpec.describe ActivityPub::Activity::Create do id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, type: 'Note', name: 'Yellow', - inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status) + inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status), } end @@ -753,11 +895,9 @@ RSpec.describe ActivityPub::Activity::Create do end context 'with an encrypted message' do - let(:recipient) { Fabricate(:account) } - let(:target_device) { Fabricate(:device, account: recipient) } - subject { described_class.new(json, sender, delivery: true, delivered_to_account_id: recipient.id) } + let(:recipient) { Fabricate(:account) } let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -779,6 +919,7 @@ RSpec.describe ActivityPub::Activity::Create do }, } end + let(:target_device) { Fabricate(:device, account: recipient) } before do subject.perform @@ -833,14 +974,9 @@ RSpec.describe ActivityPub::Activity::Create do end context 'when sender replies to local status' do - let!(:local_status) { Fabricate(:status) } - subject { described_class.new(json, sender, delivery: true) } - before do - subject.perform - end - + let!(:local_status) { Fabricate(:status) } let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -850,6 +986,10 @@ RSpec.describe ActivityPub::Activity::Create do } end + before do + subject.perform + end + it 'creates status' do status = sender.statuses.first @@ -859,14 +999,9 @@ RSpec.describe ActivityPub::Activity::Create do end context 'when sender targets a local user' do - let!(:local_account) { Fabricate(:account) } - subject { described_class.new(json, sender, delivery: true) } - before do - subject.perform - end - + let!(:local_account) { Fabricate(:account) } let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -876,6 +1011,10 @@ RSpec.describe ActivityPub::Activity::Create do } end + before do + subject.perform + end + it 'creates status' do status = sender.statuses.first @@ -885,14 +1024,9 @@ RSpec.describe ActivityPub::Activity::Create do end context 'when sender cc\'s a local user' do - let!(:local_account) { Fabricate(:account) } - subject { described_class.new(json, sender, delivery: true) } - before do - subject.perform - end - + let!(:local_account) { Fabricate(:account) } let(:object_json) do { id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, @@ -902,6 +1036,10 @@ RSpec.describe ActivityPub::Activity::Create do } end + before do + subject.perform + end + it 'creates status' do status = sender.statuses.first diff --git a/spec/lib/activitypub/activity/delete_spec.rb b/spec/lib/activitypub/activity/delete_spec.rb index 9dfb8a61b..3a73b3726 100644 --- a/spec/lib/activitypub/activity/delete_spec.rb +++ b/spec/lib/activitypub/activity/delete_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Delete do @@ -30,6 +32,7 @@ RSpec.describe ActivityPub::Activity::Delete do context 'when the status has been reblogged' do describe '#perform' do subject { described_class.new(json, sender) } + let!(:reblogger) { Fabricate(:account) } let!(:follower) { Fabricate(:account, username: 'follower', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } let!(:reblog) { Fabricate(:status, account: reblogger, reblog: status) } @@ -53,6 +56,7 @@ RSpec.describe ActivityPub::Activity::Delete do context 'when the status has been reported' do describe '#perform' do subject { described_class.new(json, sender) } + let!(:reporter) { Fabricate(:account) } before do diff --git a/spec/lib/activitypub/activity/flag_spec.rb b/spec/lib/activitypub/activity/flag_spec.rb index 6d7a8a7ec..8593d567f 100644 --- a/spec/lib/activitypub/activity/flag_spec.rb +++ b/spec/lib/activitypub/activity/flag_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Flag do @@ -137,11 +139,41 @@ RSpec.describe ActivityPub::Activity::Flag do expect(report.status_ids).to eq [] end end + + context 'when an account is passed but no status' do + let(:mentioned) { Fabricate(:account) } + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: flag_id, + type: 'Flag', + content: 'Boo!!', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: [ + ActivityPub::TagManager.instance.uri_for(flagged), + ], + }.with_indifferent_access + end + + before do + subject.perform + end + + it 'creates a report with no attached status' do + report = Report.find_by(account: sender, target_account: flagged) + + expect(report).to_not be_nil + expect(report.comment).to eq 'Boo!!' + expect(report.status_ids).to eq [] + end + end end describe '#perform with a defined uri' do subject { described_class.new(json, sender) } - let (:flag_id) { 'http://example.com/reports/1' } + + let(:flag_id) { 'http://example.com/reports/1' } before do subject.perform diff --git a/spec/lib/activitypub/activity/follow_spec.rb b/spec/lib/activitypub/activity/follow_spec.rb index fd4ede82b..c1829cb8d 100644 --- a/spec/lib/activitypub/activity/follow_spec.rb +++ b/spec/lib/activitypub/activity/follow_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Follow do @@ -18,7 +20,7 @@ RSpec.describe ActivityPub::Activity::Follow do subject { described_class.new(json, sender) } context 'with no prior follow' do - context 'unlocked account' do + context 'with an unlocked account' do before do subject.perform end @@ -33,7 +35,7 @@ RSpec.describe ActivityPub::Activity::Follow do end end - context 'silenced account following an unlocked account' do + context 'when silenced account following an unlocked account' do before do sender.touch(:silenced_at) subject.perform @@ -49,7 +51,7 @@ RSpec.describe ActivityPub::Activity::Follow do end end - context 'unlocked account muting the sender' do + context 'with an unlocked account muting the sender' do before do recipient.mute!(sender) subject.perform @@ -65,7 +67,7 @@ RSpec.describe ActivityPub::Activity::Follow do end end - context 'locked account' do + context 'when locked account' do before do recipient.update(locked: true) subject.perform @@ -87,7 +89,7 @@ RSpec.describe ActivityPub::Activity::Follow do sender.active_relationships.create!(target_account: recipient, uri: 'bar') end - context 'unlocked account' do + context 'with an unlocked account' do before do subject.perform end @@ -101,7 +103,7 @@ RSpec.describe ActivityPub::Activity::Follow do end end - context 'silenced account following an unlocked account' do + context 'when silenced account following an unlocked account' do before do sender.touch(:silenced_at) subject.perform @@ -116,7 +118,7 @@ RSpec.describe ActivityPub::Activity::Follow do end end - context 'unlocked account muting the sender' do + context 'with an unlocked account muting the sender' do before do recipient.mute!(sender) subject.perform @@ -131,7 +133,7 @@ RSpec.describe ActivityPub::Activity::Follow do end end - context 'locked account' do + context 'when locked account' do before do recipient.update(locked: true) subject.perform @@ -152,7 +154,7 @@ RSpec.describe ActivityPub::Activity::Follow do sender.follow_requests.create!(target_account: recipient, uri: 'bar') end - context 'silenced account following an unlocked account' do + context 'when silenced account following an unlocked account' do before do sender.touch(:silenced_at) subject.perform @@ -168,7 +170,7 @@ RSpec.describe ActivityPub::Activity::Follow do end end - context 'locked account' do + context 'when locked account' do before do recipient.update(locked: true) subject.perform diff --git a/spec/lib/activitypub/activity/like_spec.rb b/spec/lib/activitypub/activity/like_spec.rb index b69615a9d..640d61ab3 100644 --- a/spec/lib/activitypub/activity/like_spec.rb +++ b/spec/lib/activitypub/activity/like_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Like do diff --git a/spec/lib/activitypub/activity/move_spec.rb b/spec/lib/activitypub/activity/move_spec.rb index c468fdeff..f3973c70c 100644 --- a/spec/lib/activitypub/activity/move_spec.rb +++ b/spec/lib/activitypub/activity/move_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Move do @@ -24,7 +26,7 @@ RSpec.describe ActivityPub::Activity::Move do stub_request(:post, old_account.inbox_url).to_return(status: 200) stub_request(:post, new_account.inbox_url).to_return(status: 200) - service_stub = double + service_stub = instance_double(ActivityPub::FetchRemoteAccountService) allow(ActivityPub::FetchRemoteAccountService).to receive(:new).and_return(service_stub) allow(service_stub).to receive(:call).and_return(returned_account) end diff --git a/spec/lib/activitypub/activity/reject_spec.rb b/spec/lib/activitypub/activity/reject_spec.rb index fed4cd8cd..0a4243cd1 100644 --- a/spec/lib/activitypub/activity/reject_spec.rb +++ b/spec/lib/activitypub/activity/reject_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Reject do @@ -25,7 +27,7 @@ RSpec.describe ActivityPub::Activity::Reject do describe '#perform' do subject { described_class.new(json, sender) } - context 'rejecting a pending follow request by target' do + context 'when rejecting a pending follow request by target' do before do Fabricate(:follow_request, account: recipient, target_account: sender) subject.perform @@ -40,7 +42,7 @@ RSpec.describe ActivityPub::Activity::Reject do end end - context 'rejecting a pending follow request by uri' do + context 'when rejecting a pending follow request by uri' do before do Fabricate(:follow_request, account: recipient, target_account: sender, uri: 'bar') subject.perform @@ -55,7 +57,7 @@ RSpec.describe ActivityPub::Activity::Reject do end end - context 'rejecting a pending follow request by uri only' do + context 'when rejecting a pending follow request by uri only' do let(:object_json) { 'bar' } before do @@ -72,7 +74,7 @@ RSpec.describe ActivityPub::Activity::Reject do end end - context 'rejecting an existing follow relationship by target' do + context 'when rejecting an existing follow relationship by target' do before do Fabricate(:follow, account: recipient, target_account: sender) subject.perform @@ -87,7 +89,7 @@ RSpec.describe ActivityPub::Activity::Reject do end end - context 'rejecting an existing follow relationship by uri' do + context 'when rejecting an existing follow relationship by uri' do before do Fabricate(:follow, account: recipient, target_account: sender, uri: 'bar') subject.perform @@ -102,7 +104,7 @@ RSpec.describe ActivityPub::Activity::Reject do end end - context 'rejecting an existing follow relationship by uri only' do + context 'when rejecting an existing follow relationship by uri only' do let(:object_json) { 'bar' } before do @@ -120,7 +122,9 @@ RSpec.describe ActivityPub::Activity::Reject do end end - context 'given a relay' do + context 'when given a relay' do + subject { described_class.new(json, sender) } + let!(:relay) { Fabricate(:relay, state: :pending, follow_activity_id: 'https://abc-123/456') } let(:json) do @@ -138,8 +142,6 @@ RSpec.describe ActivityPub::Activity::Reject do }.with_indifferent_access end - subject { described_class.new(json, sender) } - it 'marks the relay as rejected' do subject.perform expect(relay.reload.rejected?).to be true diff --git a/spec/lib/activitypub/activity/remove_spec.rb b/spec/lib/activitypub/activity/remove_spec.rb index 4209dfde2..fc12aec8c 100644 --- a/spec/lib/activitypub/activity/remove_spec.rb +++ b/spec/lib/activitypub/activity/remove_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Remove do diff --git a/spec/lib/activitypub/activity/undo_spec.rb b/spec/lib/activitypub/activity/undo_spec.rb index c0309e49d..58e71fc4e 100644 --- a/spec/lib/activitypub/activity/undo_spec.rb +++ b/spec/lib/activitypub/activity/undo_spec.rb @@ -1,6 +1,10 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Undo do + subject { described_class.new(json, sender) } + let(:sender) { Fabricate(:account, domain: 'example.com') } let(:json) do @@ -13,8 +17,6 @@ RSpec.describe ActivityPub::Activity::Undo do }.with_indifferent_access end - subject { described_class.new(json, sender) } - describe '#perform' do context 'with Announce' do let(:status) { Fabricate(:status) } @@ -29,7 +31,7 @@ RSpec.describe ActivityPub::Activity::Undo do } end - context do + context 'when not atomUri' do before do Fabricate(:status, reblog: status, account: sender, uri: 'bar') end diff --git a/spec/lib/activitypub/activity/update_spec.rb b/spec/lib/activitypub/activity/update_spec.rb index 4cd853af2..87e96d2d1 100644 --- a/spec/lib/activitypub/activity/update_spec.rb +++ b/spec/lib/activitypub/activity/update_spec.rb @@ -1,24 +1,42 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Activity::Update do - let!(:sender) { Fabricate(:account) } - - before do - sender.update!(uri: ActivityPub::TagManager.instance.uri_for(sender)) - end - subject { described_class.new(json, sender) } + let!(:sender) { Fabricate(:account, domain: 'example.com', inbox_url: 'https://example.com/foo/inbox', outbox_url: 'https://example.com/foo/outbox') } + describe '#perform' do context 'with an Actor object' do - let(:modified_sender) do - sender.tap do |modified_sender| - modified_sender.display_name = 'Totally modified now' - end - end - let(:actor_json) do - ActiveModelSerializers::SerializableResource.new(modified_sender, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter).as_json + { + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', + { + manuallyApprovesFollowers: 'as:manuallyApprovesFollowers', + toot: 'http://joinmastodon.org/ns#', + featured: { '@id': 'toot:featured', '@type': '@id' }, + featuredTags: { '@id': 'toot:featuredTags', '@type': '@id' }, + }, + ], + id: sender.uri, + type: 'Person', + following: 'https://example.com/users/dfsdf/following', + followers: 'https://example.com/users/dfsdf/followers', + inbox: sender.inbox_url, + outbox: sender.outbox_url, + featured: 'https://example.com/users/dfsdf/featured', + featuredTags: 'https://example.com/users/dfsdf/tags', + preferredUsername: sender.username, + name: 'Totally modified now', + publicKey: { + id: "#{sender.uri}#main-key", + owner: sender.uri, + publicKeyPem: sender.public_key, + }, + } end let(:json) do @@ -26,7 +44,7 @@ RSpec.describe ActivityPub::Activity::Update do '@context': 'https://www.w3.org/ns/activitystreams', id: 'foo', type: 'Update', - actor: ActivityPub::TagManager.instance.uri_for(sender), + actor: sender.uri, object: actor_json, }.with_indifferent_access end @@ -36,6 +54,7 @@ RSpec.describe ActivityPub::Activity::Update do stub_request(:get, actor_json[:followers]).to_return(status: 404) stub_request(:get, actor_json[:following]).to_return(status: 404) stub_request(:get, actor_json[:featured]).to_return(status: 404) + stub_request(:get, actor_json[:featuredTags]).to_return(status: 404) subject.perform end @@ -47,17 +66,17 @@ RSpec.describe ActivityPub::Activity::Update do context 'with a Question object' do let!(:at_time) { Time.now.utc } - let!(:status) { Fabricate(:status, account: sender, poll: Poll.new(account: sender, options: %w(Bar Baz), cached_tallies: [0, 0], expires_at: at_time + 5.days)) } + let!(:status) { Fabricate(:status, uri: 'https://example.com/statuses/poll', account: sender, poll: Poll.new(account: sender, options: %w(Bar Baz), cached_tallies: [0, 0], expires_at: at_time + 5.days)) } let(:json) do { '@context': 'https://www.w3.org/ns/activitystreams', id: 'foo', type: 'Update', - actor: ActivityPub::TagManager.instance.uri_for(sender), + actor: sender.uri, object: { type: 'Question', - id: ActivityPub::TagManager.instance.uri_for(status), + id: status.uri, content: 'Foo', endTime: (at_time + 5.days).iso8601, oneOf: [ diff --git a/spec/lib/activitypub/adapter_spec.rb b/spec/lib/activitypub/adapter_spec.rb index ea03797aa..f9f8b8dce 100644 --- a/spec/lib/activitypub/adapter_spec.rb +++ b/spec/lib/activitypub/adapter_spec.rb @@ -1,50 +1,60 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Adapter do - class TestObject < ActiveModelSerializers::Model - attributes :foo - end - - class TestWithBasicContextSerializer < ActivityPub::Serializer - attributes :foo - end - - class TestWithNamedContextSerializer < ActivityPub::Serializer - context :security - attributes :foo - end - - class TestWithNestedNamedContextSerializer < ActivityPub::Serializer - attributes :foo - - has_one :virtual_object, key: :baz, serializer: TestWithNamedContextSerializer - - def virtual_object - object + before do + test_object_class = Class.new(ActiveModelSerializers::Model) do + attributes :foo end - end + stub_const('TestObject', test_object_class) - class TestWithContextExtensionSerializer < ActivityPub::Serializer - context_extensions :sensitive - attributes :foo - end - - class TestWithNestedContextExtensionSerializer < ActivityPub::Serializer - context_extensions :manually_approves_followers - attributes :foo - - has_one :virtual_object, key: :baz, serializer: TestWithContextExtensionSerializer - - def virtual_object - object + test_with_basic_context_serializer = Class.new(ActivityPub::Serializer) do + attributes :foo end + stub_const('TestWithBasicContextSerializer', test_with_basic_context_serializer) + + test_with_named_context_serializer = Class.new(ActivityPub::Serializer) do + context :security + attributes :foo + end + stub_const('TestWithNamedContextSerializer', test_with_named_context_serializer) + + test_with_nested_named_context_serializer = Class.new(ActivityPub::Serializer) do + attributes :foo + + has_one :virtual_object, key: :baz, serializer: TestWithNamedContextSerializer + + def virtual_object + object + end + end + stub_const('TestWithNestedNamedContextSerializer', test_with_nested_named_context_serializer) + + test_with_context_extension_serializer = Class.new(ActivityPub::Serializer) do + context_extensions :sensitive + attributes :foo + end + stub_const('TestWithContextExtensionSerializer', test_with_context_extension_serializer) + + test_with_nested_context_extension_serializer = Class.new(ActivityPub::Serializer) do + context_extensions :manually_approves_followers + attributes :foo + + has_one :virtual_object, key: :baz, serializer: TestWithContextExtensionSerializer + + def virtual_object + object + end + end + stub_const('TestWithNestedContextExtensionSerializer', test_with_nested_context_extension_serializer) end describe '#serializable_hash' do - let(:serializer_class) {} - subject { ActiveModelSerializers::SerializableResource.new(TestObject.new(foo: 'bar'), serializer: serializer_class, adapter: described_class).as_json } + let(:serializer_class) {} + context 'when serializer defines no context' do let(:serializer_class) { TestWithBasicContextSerializer } diff --git a/spec/lib/activitypub/dereferencer_spec.rb b/spec/lib/activitypub/dereferencer_spec.rb index e50b497c7..11078de86 100644 --- a/spec/lib/activitypub/dereferencer_spec.rb +++ b/spec/lib/activitypub/dereferencer_spec.rb @@ -1,14 +1,16 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::Dereferencer do describe '#object' do + subject { described_class.new(uri, permitted_origin: permitted_origin, signature_actor: signature_actor).object } + let(:object) { { '@context': 'https://www.w3.org/ns/activitystreams', id: 'https://example.com/foo', type: 'Note', content: 'Hoge' } } let(:permitted_origin) { 'https://example.com' } let(:signature_actor) { nil } let(:uri) { nil } - subject { described_class.new(uri, permitted_origin: permitted_origin, signature_actor: signature_actor).object } - before do stub_request(:get, 'https://example.com/foo').to_return(body: Oj.dump(object), headers: { 'Content-Type' => 'application/activity+json' }) end diff --git a/spec/lib/activitypub/linked_data_signature_spec.rb b/spec/lib/activitypub/linked_data_signature_spec.rb index d55a7c7fa..e821cee6b 100644 --- a/spec/lib/activitypub/linked_data_signature_spec.rb +++ b/spec/lib/activitypub/linked_data_signature_spec.rb @@ -1,9 +1,13 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::LinkedDataSignature do include JsonLdHelper - let!(:sender) { Fabricate(:account, uri: 'http://example.com/alice') } + subject { described_class.new(json) } + + let!(:sender) { Fabricate(:account, uri: 'http://example.com/alice', domain: 'example.com') } let(:raw_json) do { @@ -14,8 +18,6 @@ RSpec.describe ActivityPub::LinkedDataSignature do let(:json) { raw_json.merge('signature' => signature) } - subject { described_class.new(json) } - before do stub_jsonld_contexts! end @@ -36,6 +38,40 @@ RSpec.describe ActivityPub::LinkedDataSignature do end end + context 'when local account record is missing a public key' do + let(:raw_signature) do + { + 'creator' => 'http://example.com/alice', + 'created' => '2017-09-23T20:21:34Z', + } + end + + let(:signature) { raw_signature.merge('type' => 'RsaSignature2017', 'signatureValue' => sign(sender, raw_signature, raw_json)) } + + let(:service_stub) { instance_double(ActivityPub::FetchRemoteKeyService) } + + before do + # Ensure signature is computed with the old key + signature + + # Unset key + old_key = sender.public_key + sender.update!(private_key: '', public_key: '') + + allow(ActivityPub::FetchRemoteKeyService).to receive(:new).and_return(service_stub) + + allow(service_stub).to receive(:call).with('http://example.com/alice') do + sender.update!(public_key: old_key) + sender + end + end + + it 'fetches key and returns creator' do + expect(subject.verify_actor!).to eq sender + expect(service_stub).to have_received(:call).with('http://example.com/alice').once + end + end + context 'when signature is missing' do let(:signature) { nil } diff --git a/spec/lib/activitypub/parser/status_parser_spec.rb b/spec/lib/activitypub/parser/status_parser_spec.rb new file mode 100644 index 000000000..5d9f008db --- /dev/null +++ b/spec/lib/activitypub/parser/status_parser_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe ActivityPub::Parser::StatusParser do + subject { described_class.new(json) } + + let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor') } + let(:follower) { Fabricate(:account, username: 'bob') } + + let(:json) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: [ActivityPub::TagManager.instance.uri_for(sender), '#foo'].join, + type: 'Create', + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: object_json, + }.with_indifferent_access + end + + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), 'post1'].join('/'), + type: 'Note', + to: [ + 'https://www.w3.org/ns/activitystreams#Public', + ActivityPub::TagManager.instance.uri_for(follower), + ], + content: '@bob lorem ipsum', + contentMap: { + EN: '@bob lorem ipsum', + }, + published: 1.hour.ago.utc.iso8601, + updated: 1.hour.ago.utc.iso8601, + tag: { + type: 'Mention', + href: ActivityPub::TagManager.instance.uri_for(follower), + }, + } + end + + it 'correctly parses status' do + expect(subject).to have_attributes( + text: '@bob lorem ipsum', + uri: [ActivityPub::TagManager.instance.uri_for(sender), 'post1'].join('/'), + reply: false, + language: :en + ) + end +end diff --git a/spec/lib/activitypub/tag_manager_spec.rb b/spec/lib/activitypub/tag_manager_spec.rb index 606a1de2e..55e9b4bb5 100644 --- a/spec/lib/activitypub/tag_manager_spec.rb +++ b/spec/lib/activitypub/tag_manager_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::TagManager do @@ -110,6 +112,14 @@ RSpec.describe ActivityPub::TagManager do expect(subject.cc(status)).to include(subject.uri_for(foo)) expect(subject.cc(status)).to_not include(subject.uri_for(alice)) end + + it 'returns poster of reblogged post, if reblog' do + bob = Fabricate(:account, username: 'bob', domain: 'example.com', inbox_url: 'http://example.com/bob') + alice = Fabricate(:account, username: 'alice') + status = Fabricate(:status, visibility: :public, account: bob) + reblog = Fabricate(:status, visibility: :public, account: alice, reblog: status) + expect(subject.cc(reblog)).to include(subject.uri_for(bob)) + end end describe '#local_uri?' do @@ -137,7 +147,7 @@ RSpec.describe ActivityPub::TagManager do end it 'returns the remote account by matching URI without fragment part' do - account = Fabricate(:account, uri: 'https://example.com/123') + account = Fabricate(:account, uri: 'https://example.com/123', domain: 'example.com') expect(subject.uri_to_resource('https://example.com/123#456', Account)).to eq account end diff --git a/spec/lib/admin/metrics/dimension/instance_accounts_dimension_spec.rb b/spec/lib/admin/metrics/dimension/instance_accounts_dimension_spec.rb new file mode 100644 index 000000000..106717f97 --- /dev/null +++ b/spec/lib/admin/metrics/dimension/instance_accounts_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::InstanceAccountsDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/instance_languages_dimension_spec.rb b/spec/lib/admin/metrics/dimension/instance_languages_dimension_spec.rb new file mode 100644 index 000000000..f9f6430ca --- /dev/null +++ b/spec/lib/admin/metrics/dimension/instance_languages_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::InstanceLanguagesDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/languages_dimension_spec.rb b/spec/lib/admin/metrics/dimension/languages_dimension_spec.rb new file mode 100644 index 000000000..1722c4c61 --- /dev/null +++ b/spec/lib/admin/metrics/dimension/languages_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::LanguagesDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/servers_dimension_spec.rb b/spec/lib/admin/metrics/dimension/servers_dimension_spec.rb new file mode 100644 index 000000000..7e2bb9ac0 --- /dev/null +++ b/spec/lib/admin/metrics/dimension/servers_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::ServersDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/software_versions_dimension_spec.rb b/spec/lib/admin/metrics/dimension/software_versions_dimension_spec.rb new file mode 100644 index 000000000..ee1491733 --- /dev/null +++ b/spec/lib/admin/metrics/dimension/software_versions_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::SoftwareVersionsDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/sources_dimension_spec.rb b/spec/lib/admin/metrics/dimension/sources_dimension_spec.rb new file mode 100644 index 000000000..d6b581a9b --- /dev/null +++ b/spec/lib/admin/metrics/dimension/sources_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::SourcesDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/space_usage_dimension_spec.rb b/spec/lib/admin/metrics/dimension/space_usage_dimension_spec.rb new file mode 100644 index 000000000..65d04cfed --- /dev/null +++ b/spec/lib/admin/metrics/dimension/space_usage_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::SpaceUsageDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/tag_languages_dimension_spec.rb b/spec/lib/admin/metrics/dimension/tag_languages_dimension_spec.rb new file mode 100644 index 000000000..721d24fa1 --- /dev/null +++ b/spec/lib/admin/metrics/dimension/tag_languages_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::TagLanguagesDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/tag_servers_dimension_spec.rb b/spec/lib/admin/metrics/dimension/tag_servers_dimension_spec.rb new file mode 100644 index 000000000..305471681 --- /dev/null +++ b/spec/lib/admin/metrics/dimension/tag_servers_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::TagServersDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/active_users_measure_spec.rb b/spec/lib/admin/metrics/measure/active_users_measure_spec.rb new file mode 100644 index 000000000..55164ed88 --- /dev/null +++ b/spec/lib/admin/metrics/measure/active_users_measure_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::ActiveUsersMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/instance_accounts_measure_spec.rb b/spec/lib/admin/metrics/measure/instance_accounts_measure_spec.rb new file mode 100644 index 000000000..8e414963f --- /dev/null +++ b/spec/lib/admin/metrics/measure/instance_accounts_measure_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::InstanceAccountsMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let(:domain) { 'example.com' } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + + let(:params) { ActionController::Parameters.new(domain: domain) } + + before do + Fabricate(:account, domain: domain, created_at: 1.year.ago) + Fabricate(:account, domain: domain, created_at: 1.month.ago) + Fabricate(:account, domain: domain) + + Fabricate(:account, domain: "foo.#{domain}", created_at: 1.year.ago) + Fabricate(:account, domain: "foo.#{domain}") + Fabricate(:account, domain: "bar.#{domain}") + end + + describe 'total' do + context 'without include_subdomains' do + it 'returns the expected number of accounts' do + expect(measure.total).to eq 3 + end + end + + context 'with include_subdomains' do + let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') } + + it 'returns the expected number of accounts' do + expect(measure.total).to eq 6 + end + end + end + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/instance_followers_measure_spec.rb b/spec/lib/admin/metrics/measure/instance_followers_measure_spec.rb new file mode 100644 index 000000000..c627e6ced --- /dev/null +++ b/spec/lib/admin/metrics/measure/instance_followers_measure_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::InstanceFollowersMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let(:domain) { 'example.com' } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + + let(:params) { ActionController::Parameters.new(domain: domain) } + + before do + local_account = Fabricate(:account) + + Fabricate(:account, domain: domain).follow!(local_account) + Fabricate(:account, domain: domain).follow!(local_account) + Fabricate(:account, domain: domain) + + Fabricate(:account, domain: "foo.#{domain}").follow!(local_account) + Fabricate(:account, domain: "foo.#{domain}").follow!(local_account) + Fabricate(:account, domain: "bar.#{domain}") + end + + describe 'total' do + context 'without include_subdomains' do + it 'returns the expected number of accounts' do + expect(measure.total).to eq 2 + end + end + + context 'with include_subdomains' do + let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') } + + it 'returns the expected number of accounts' do + expect(measure.total).to eq 4 + end + end + end + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/instance_follows_measure_spec.rb b/spec/lib/admin/metrics/measure/instance_follows_measure_spec.rb new file mode 100644 index 000000000..42f33dfc3 --- /dev/null +++ b/spec/lib/admin/metrics/measure/instance_follows_measure_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::InstanceFollowsMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let(:domain) { 'example.com' } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + + let(:params) { ActionController::Parameters.new(domain: domain) } + + before do + local_account = Fabricate(:account) + + local_account.follow!(Fabricate(:account, domain: domain)) + local_account.follow!(Fabricate(:account, domain: domain)) + Fabricate(:account, domain: domain) + + local_account.follow!(Fabricate(:account, domain: "foo.#{domain}")) + local_account.follow!(Fabricate(:account, domain: "foo.#{domain}")) + Fabricate(:account, domain: "bar.#{domain}") + end + + describe 'total' do + context 'without include_subdomains' do + it 'returns the expected number of accounts' do + expect(measure.total).to eq 2 + end + end + + context 'with include_subdomains' do + let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') } + + it 'returns the expected number of accounts' do + expect(measure.total).to eq 4 + end + end + end + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/instance_media_attachments_measure_spec.rb b/spec/lib/admin/metrics/measure/instance_media_attachments_measure_spec.rb new file mode 100644 index 000000000..c103307f9 --- /dev/null +++ b/spec/lib/admin/metrics/measure/instance_media_attachments_measure_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let(:domain) { 'example.com' } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + + let(:params) { ActionController::Parameters.new(domain: domain) } + + let(:remote_account) { Fabricate(:account, domain: domain) } + let(:remote_account_on_subdomain) { Fabricate(:account, domain: "foo.#{domain}") } + + before do + remote_account.media_attachments.create!(file: attachment_fixture('attachment.jpg')) + remote_account_on_subdomain.media_attachments.create!(file: attachment_fixture('attachment.jpg')) + end + + describe 'total' do + context 'without include_subdomains' do + it 'returns the expected number of accounts' do + expected_total = remote_account.media_attachments.sum(:file_file_size) + remote_account.media_attachments.sum(:thumbnail_file_size) + expect(measure.total).to eq expected_total + end + end + + context 'with include_subdomains' do + let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') } + + it 'returns the expected number of accounts' do + expected_total = [remote_account, remote_account_on_subdomain].sum do |account| + account.media_attachments.sum(:file_file_size) + account.media_attachments.sum(:thumbnail_file_size) + end + + expect(measure.total).to eq expected_total + end + end + end + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/instance_reports_measure_spec.rb b/spec/lib/admin/metrics/measure/instance_reports_measure_spec.rb new file mode 100644 index 000000000..62fcf84ac --- /dev/null +++ b/spec/lib/admin/metrics/measure/instance_reports_measure_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::InstanceReportsMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let(:domain) { 'example.com' } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + + let(:params) { ActionController::Parameters.new(domain: domain) } + + before do + Fabricate(:report, target_account: Fabricate(:account, domain: domain)) + Fabricate(:report, target_account: Fabricate(:account, domain: domain)) + + Fabricate(:report, target_account: Fabricate(:account, domain: "foo.#{domain}")) + Fabricate(:report, target_account: Fabricate(:account, domain: "foo.#{domain}")) + Fabricate(:report, target_account: Fabricate(:account, domain: "bar.#{domain}")) + end + + describe 'total' do + context 'without include_subdomains' do + it 'returns the expected number of accounts' do + expect(measure.total).to eq 2 + end + end + + context 'with include_subdomains' do + let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') } + + it 'returns the expected number of accounts' do + expect(measure.total).to eq 5 + end + end + end + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/instance_statuses_measure_spec.rb b/spec/lib/admin/metrics/measure/instance_statuses_measure_spec.rb new file mode 100644 index 000000000..df4cfe207 --- /dev/null +++ b/spec/lib/admin/metrics/measure/instance_statuses_measure_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::InstanceStatusesMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let(:domain) { 'example.com' } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + + let(:params) { ActionController::Parameters.new(domain: domain) } + + before do + Fabricate(:status, account: Fabricate(:account, domain: domain)) + Fabricate(:status, account: Fabricate(:account, domain: domain)) + + Fabricate(:status, account: Fabricate(:account, domain: "foo.#{domain}")) + Fabricate(:status, account: Fabricate(:account, domain: "foo.#{domain}")) + Fabricate(:status, account: Fabricate(:account, domain: "bar.#{domain}")) + end + + describe 'total' do + context 'without include_subdomains' do + it 'returns the expected number of accounts' do + expect(measure.total).to eq 2 + end + end + + context 'with include_subdomains' do + let(:params) { ActionController::Parameters.new(domain: domain, include_subdomains: 'true') } + + it 'returns the expected number of accounts' do + expect(measure.total).to eq 5 + end + end + end + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/interactions_measure_spec.rb b/spec/lib/admin/metrics/measure/interactions_measure_spec.rb new file mode 100644 index 000000000..e98c83059 --- /dev/null +++ b/spec/lib/admin/metrics/measure/interactions_measure_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::InteractionsMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/new_users_measure_spec.rb b/spec/lib/admin/metrics/measure/new_users_measure_spec.rb new file mode 100644 index 000000000..fe82f8219 --- /dev/null +++ b/spec/lib/admin/metrics/measure/new_users_measure_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::NewUsersMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/opened_reports_measure_spec.rb b/spec/lib/admin/metrics/measure/opened_reports_measure_spec.rb new file mode 100644 index 000000000..deed64ae8 --- /dev/null +++ b/spec/lib/admin/metrics/measure/opened_reports_measure_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::OpenedReportsMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/resolved_reports_measure_spec.rb b/spec/lib/admin/metrics/measure/resolved_reports_measure_spec.rb new file mode 100644 index 000000000..cb98df2dc --- /dev/null +++ b/spec/lib/admin/metrics/measure/resolved_reports_measure_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::ResolvedReportsMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/tag_accounts_measure_spec.rb b/spec/lib/admin/metrics/measure/tag_accounts_measure_spec.rb new file mode 100644 index 000000000..938b67afa --- /dev/null +++ b/spec/lib/admin/metrics/measure/tag_accounts_measure_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::TagAccountsMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let!(:tag) { Fabricate(:tag) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:params) { ActionController::Parameters.new(id: tag.id) } + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/tag_servers_measure_spec.rb b/spec/lib/admin/metrics/measure/tag_servers_measure_spec.rb new file mode 100644 index 000000000..e09a2b04e --- /dev/null +++ b/spec/lib/admin/metrics/measure/tag_servers_measure_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::TagServersMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let!(:tag) { Fabricate(:tag) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:params) { ActionController::Parameters.new(id: tag.id) } + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb b/spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb new file mode 100644 index 000000000..869e93744 --- /dev/null +++ b/spec/lib/admin/metrics/measure/tag_uses_measure_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Measure::TagUsesMeasure do + subject(:measure) { described_class.new(start_at, end_at, params) } + + let!(:tag) { Fabricate(:tag) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:params) { ActionController::Parameters.new(id: tag.id) } + + describe '#data' do + it 'runs data query without error' do + expect { measure.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/system_check/base_check_spec.rb b/spec/lib/admin/system_check/base_check_spec.rb new file mode 100644 index 000000000..fdd9f6b6c --- /dev/null +++ b/spec/lib/admin/system_check/base_check_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::SystemCheck::BaseCheck do + subject(:check) { described_class.new(user) } + + let(:user) { Fabricate(:user) } + + describe 'skip?' do + it 'returns false' do + expect(check.skip?).to be false + end + end + + describe 'pass?' do + it 'raises not implemented error' do + expect { check.pass? }.to raise_error(NotImplementedError) + end + end + + describe 'message' do + it 'raises not implemented error' do + expect { check.message }.to raise_error(NotImplementedError) + end + end +end diff --git a/spec/lib/admin/system_check/database_schema_check_spec.rb b/spec/lib/admin/system_check/database_schema_check_spec.rb new file mode 100644 index 000000000..db1dcb52f --- /dev/null +++ b/spec/lib/admin/system_check/database_schema_check_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::SystemCheck::DatabaseSchemaCheck do + subject(:check) { described_class.new(user) } + + let(:user) { Fabricate(:user) } + + it_behaves_like 'a check available to devops users' + + describe 'pass?' do + context 'when database needs migration' do + before do + context = instance_double(ActiveRecord::MigrationContext, needs_migration?: true) + allow(ActiveRecord::Base.connection).to receive(:migration_context).and_return(context) + end + + it 'returns false' do + expect(check.pass?).to be false + end + end + + context 'when database does not need migration' do + before do + context = instance_double(ActiveRecord::MigrationContext, needs_migration?: false) + allow(ActiveRecord::Base.connection).to receive(:migration_context).and_return(context) + end + + it 'returns true' do + expect(check.pass?).to be true + end + end + end + + describe 'message' do + it 'sends class name symbol to message instance' do + allow(Admin::SystemCheck::Message).to receive(:new).with(:database_schema_check) + + check.message + + expect(Admin::SystemCheck::Message).to have_received(:new).with(:database_schema_check) + end + end +end diff --git a/spec/lib/admin/system_check/elasticsearch_check_spec.rb b/spec/lib/admin/system_check/elasticsearch_check_spec.rb new file mode 100644 index 000000000..a885640ce --- /dev/null +++ b/spec/lib/admin/system_check/elasticsearch_check_spec.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::SystemCheck::ElasticsearchCheck do + subject(:check) { described_class.new(user) } + + let(:user) { Fabricate(:user) } + + it_behaves_like 'a check available to devops users' + + describe 'pass?' do + context 'when chewy is enabled' do + before do + allow(Chewy).to receive(:enabled?).and_return(true) + allow(Chewy.client.cluster).to receive(:health).and_return({ 'status' => 'green', 'number_of_nodes' => 1 }) + allow(Chewy.client.indices).to receive_messages(get_mapping: { + AccountsIndex.index_name => AccountsIndex.mappings_hash.deep_stringify_keys, + StatusesIndex.index_name => StatusesIndex.mappings_hash.deep_stringify_keys, + PublicStatusesIndex.index_name => PublicStatusesIndex.mappings_hash.deep_stringify_keys, + InstancesIndex.index_name => InstancesIndex.mappings_hash.deep_stringify_keys, + TagsIndex.index_name => TagsIndex.mappings_hash.deep_stringify_keys, + }, get_settings: { + 'chewy_specifications' => { + 'settings' => { + 'index' => { + 'number_of_replicas' => 0, + }, + }, + }, + }) + end + + context 'when running version is present and high enough' do + before do + allow(Chewy.client).to receive(:info) + .and_return({ 'version' => { 'number' => '999.99.9' } }) + end + + it 'returns true' do + expect(check.pass?).to be true + end + end + + context 'when running version is present and too low' do + context 'when compatible version is too low' do + before do + allow(Chewy.client).to receive(:info) + .and_return({ 'version' => { 'number' => '1.2.3', 'minimum_wire_compatibility_version' => '1.0' } }) + end + + it 'returns false' do + expect(check.pass?).to be false + end + end + + context 'when compatible version is high enough' do + before do + allow(Chewy.client).to receive(:info) + .and_return({ 'version' => { 'number' => '1.2.3', 'minimum_wire_compatibility_version' => '99.9' } }) + end + + it 'returns true' do + expect(check.pass?).to be true + end + end + end + + context 'when running version is missing' do + before { stub_elasticsearch_error } + + it 'returns false' do + expect(check.pass?).to be false + end + end + end + + context 'when chewy is not enabled' do + before { allow(Chewy).to receive(:enabled?).and_return(false) } + + it 'returns true' do + expect(check.pass?).to be true + end + end + end + + describe 'message' do + before do + allow(Chewy).to receive(:enabled?).and_return(true) + allow(Chewy.client.cluster).to receive(:health).and_return({ 'status' => 'green', 'number_of_nodes' => 1 }) + allow(Chewy.client.indices).to receive(:get_mapping).and_return({ + AccountsIndex.index_name => AccountsIndex.mappings_hash.deep_stringify_keys, + StatusesIndex.index_name => StatusesIndex.mappings_hash.deep_stringify_keys, + PublicStatusesIndex.index_name => PublicStatusesIndex.mappings_hash.deep_stringify_keys, + InstancesIndex.index_name => InstancesIndex.mappings_hash.deep_stringify_keys, + TagsIndex.index_name => TagsIndex.mappings_hash.deep_stringify_keys, + }) + end + + context 'when running version is present' do + before { allow(Chewy.client).to receive(:info).and_return({ 'version' => { 'number' => '1.2.3' } }) } + + it 'sends class name symbol to message instance' do + allow(Admin::SystemCheck::Message).to receive(:new) + .with(:elasticsearch_version_check, anything) + + check.message + + expect(Admin::SystemCheck::Message).to have_received(:new) + .with(:elasticsearch_version_check, 'Elasticsearch 1.2.3 is running while 7.x is required') + end + end + + context 'when running version is missing' do + before { stub_elasticsearch_error } + + it 'sends class name symbol to message instance' do + allow(Admin::SystemCheck::Message).to receive(:new) + .with(:elasticsearch_running_check) + + check.message + + expect(Admin::SystemCheck::Message).to have_received(:new) + .with(:elasticsearch_running_check) + end + end + end + + def stub_elasticsearch_error + client = instance_double(Elasticsearch::Transport::Client) + allow(client).to receive(:info).and_raise(Elasticsearch::Transport::Transport::Error) + allow(Chewy).to receive(:client).and_return(client) + end +end diff --git a/spec/lib/admin/system_check/media_privacy_check_spec.rb b/spec/lib/admin/system_check/media_privacy_check_spec.rb new file mode 100644 index 000000000..316bf1215 --- /dev/null +++ b/spec/lib/admin/system_check/media_privacy_check_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::SystemCheck::MediaPrivacyCheck do + subject(:check) { described_class.new(user) } + + let(:user) { Fabricate(:user) } + + it_behaves_like 'a check available to devops users' + + describe 'pass?' do + context 'when the media cannot be listed' do + before do + stub_request(:get, /ngrok.io/).to_return(status: 200, body: 'a list of no files') + end + + it 'returns true' do + expect(check.pass?).to be true + end + end + end + + describe 'message' do + it 'sends values to message instance' do + allow(Admin::SystemCheck::Message).to receive(:new).with(nil, nil, nil, true) + + check.message + + expect(Admin::SystemCheck::Message).to have_received(:new).with(nil, nil, nil, true) + end + end +end diff --git a/spec/lib/admin/system_check/message_spec.rb b/spec/lib/admin/system_check/message_spec.rb new file mode 100644 index 000000000..c0671f345 --- /dev/null +++ b/spec/lib/admin/system_check/message_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::SystemCheck::Message do + subject(:check) { described_class.new(:key_value, :value_value, :action_value, :critical_value) } + + it 'providers readers when initialized' do + expect(check.key).to eq :key_value + expect(check.value).to eq :value_value + expect(check.action).to eq :action_value + expect(check.critical).to eq :critical_value + end +end diff --git a/spec/lib/admin/system_check/rules_check_spec.rb b/spec/lib/admin/system_check/rules_check_spec.rb new file mode 100644 index 000000000..fb3293fb2 --- /dev/null +++ b/spec/lib/admin/system_check/rules_check_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::SystemCheck::RulesCheck do + subject(:check) { described_class.new(user) } + + let(:user) { Fabricate(:user) } + + describe 'skip?' do + context 'when user can manage rules' do + before { allow(user).to receive(:can?).with(:manage_rules).and_return(true) } + + it 'returns false' do + expect(check.skip?).to be false + end + end + + context 'when user cannot manage rules' do + before { allow(user).to receive(:can?).with(:manage_rules).and_return(false) } + + it 'returns true' do + expect(check.skip?).to be true + end + end + end + + describe 'pass?' do + context 'when there is not a kept rule' do + it 'returns false' do + expect(check.pass?).to be false + end + end + + context 'when there is a kept rule' do + before { Fabricate(:rule) } + + it 'returns true' do + expect(check.pass?).to be true + end + end + end + + describe 'message' do + it 'sends class name symbol to message instance' do + allow(Admin::SystemCheck::Message).to receive(:new).with(:rules_check, nil, '/admin/rules') + + check.message + + expect(Admin::SystemCheck::Message).to have_received(:new).with(:rules_check, nil, '/admin/rules') + end + end +end diff --git a/spec/lib/admin/system_check/sidekiq_process_check_spec.rb b/spec/lib/admin/system_check/sidekiq_process_check_spec.rb new file mode 100644 index 000000000..9bd9daddf --- /dev/null +++ b/spec/lib/admin/system_check/sidekiq_process_check_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::SystemCheck::SidekiqProcessCheck do + subject(:check) { described_class.new(user) } + + let(:user) { Fabricate(:user) } + + it_behaves_like 'a check available to devops users' + + describe 'pass?' do + context 'when missing queues is empty' do + before do + process_set = instance_double(Sidekiq::ProcessSet, reduce: []) + allow(Sidekiq::ProcessSet).to receive(:new).and_return(process_set) + end + + it 'returns true' do + expect(check.pass?).to be true + end + end + + context 'when missing queues is not empty' do + before do + process_set = instance_double(Sidekiq::ProcessSet, reduce: [:something]) + allow(Sidekiq::ProcessSet).to receive(:new).and_return(process_set) + end + + it 'returns false' do + expect(check.pass?).to be false + end + end + end + + describe 'message' do + it 'sends values to message instance' do + allow(Admin::SystemCheck::Message).to receive(:new).with(:sidekiq_process_check, 'default, push, mailers, pull, scheduler, ingress') + + check.message + + expect(Admin::SystemCheck::Message).to have_received(:new).with(:sidekiq_process_check, 'default, push, mailers, pull, scheduler, ingress') + end + end +end diff --git a/spec/lib/admin/system_check/software_version_check_spec.rb b/spec/lib/admin/system_check/software_version_check_spec.rb new file mode 100644 index 000000000..de4335fc5 --- /dev/null +++ b/spec/lib/admin/system_check/software_version_check_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::SystemCheck::SoftwareVersionCheck do + include RoutingHelper + + subject(:check) { described_class.new(user) } + + let(:user) { Fabricate(:user) } + + describe 'skip?' do + context 'when user cannot view devops' do + before { allow(user).to receive(:can?).with(:view_devops).and_return(false) } + + it 'returns true' do + expect(check.skip?).to be true + end + end + + context 'when user can view devops' do + before { allow(user).to receive(:can?).with(:view_devops).and_return(true) } + + it 'returns false' do + expect(check.skip?).to be false + end + + context 'when checks are disabled' do + around do |example| + ClimateControl.modify UPDATE_CHECK_URL: '' do + example.run + end + end + + it 'returns true' do + expect(check.skip?).to be true + end + end + end + end + + describe 'pass?' do + context 'when there is no known update' do + it 'returns true' do + expect(check.pass?).to be true + end + end + + context 'when there is a non-urgent major release' do + before do + Fabricate(:software_update, version: '99.99.99', type: 'major', urgent: false) + end + + it 'returns true' do + expect(check.pass?).to be true + end + end + + context 'when there is an urgent major release' do + before do + Fabricate(:software_update, version: '99.99.99', type: 'major', urgent: true) + end + + it 'returns false' do + expect(check.pass?).to be false + end + end + + context 'when there is an urgent minor release' do + before do + Fabricate(:software_update, version: '99.99.99', type: 'minor', urgent: true) + end + + it 'returns false' do + expect(check.pass?).to be false + end + end + + context 'when there is an urgent patch release' do + before do + Fabricate(:software_update, version: '99.99.99', type: 'patch', urgent: true) + end + + it 'returns false' do + expect(check.pass?).to be false + end + end + + context 'when there is a non-urgent patch release' do + before do + Fabricate(:software_update, version: '99.99.99', type: 'patch', urgent: false) + end + + it 'returns false' do + expect(check.pass?).to be false + end + end + end + + describe 'message' do + context 'when there is a non-urgent patch release pending' do + before do + Fabricate(:software_update, version: '99.99.99', type: 'patch', urgent: false) + end + + it 'sends class name symbol to message instance' do + allow(Admin::SystemCheck::Message).to receive(:new) + .with(:software_version_patch_check, anything, anything) + + check.message + + expect(Admin::SystemCheck::Message).to have_received(:new) + .with(:software_version_patch_check, nil, admin_software_updates_path) + end + end + + context 'when there is an urgent patch release pending' do + before do + Fabricate(:software_update, version: '99.99.99', type: 'patch', urgent: true) + end + + it 'sends class name symbol to message instance' do + allow(Admin::SystemCheck::Message).to receive(:new) + .with(:software_version_critical_check, anything, anything, anything) + + check.message + + expect(Admin::SystemCheck::Message).to have_received(:new) + .with(:software_version_critical_check, nil, admin_software_updates_path, true) + end + end + end +end diff --git a/spec/lib/admin/system_check_spec.rb b/spec/lib/admin/system_check_spec.rb new file mode 100644 index 000000000..30048fd3a --- /dev/null +++ b/spec/lib/admin/system_check_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::SystemCheck do + let(:user) { Fabricate(:user) } + + describe 'perform' do + let(:result) { described_class.perform(user) } + + it 'runs all the checks' do + expect(result).to be_an(Array) + end + end +end diff --git a/spec/lib/cache_buster_spec.rb b/spec/lib/cache_buster_spec.rb new file mode 100644 index 000000000..84085608e --- /dev/null +++ b/spec/lib/cache_buster_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CacheBuster do + subject { described_class.new(secret_header: secret_header, secret: secret, http_method: http_method) } + + let(:secret_header) { nil } + let(:secret) { nil } + let(:http_method) { nil } + + let(:purge_url) { 'https://example.com/test_purge' } + + describe '#bust' do + shared_examples 'makes_request' do + it 'makes an HTTP purging request' do + method = http_method&.to_sym || :get + stub_request(method, purge_url).to_return(status: 200) + + subject.bust(purge_url) + + test_request = a_request(method, purge_url) + + test_request = test_request.with(headers: { secret_header => secret }) if secret && secret_header + + expect(test_request).to have_been_made.once + end + end + + context 'when using default options' do + include_examples 'makes_request' + end + + context 'when specifying a secret header' do + let(:secret_header) { 'X-Purge-Secret' } + let(:secret) { SecureRandom.hex(20) } + + include_examples 'makes_request' + end + + context 'when specifying a PURGE method' do + let(:http_method) { 'purge' } + + context 'when not using headers' do + include_examples 'makes_request' + end + + context 'when specifying a secret header' do + let(:secret_header) { 'X-Purge-Secret' } + let(:secret) { SecureRandom.hex(20) } + + include_examples 'makes_request' + end + end + end +end diff --git a/spec/lib/connection_pool/shared_connection_pool_spec.rb b/spec/lib/connection_pool/shared_connection_pool_spec.rb index 114464558..a2fe75f74 100644 --- a/spec/lib/connection_pool/shared_connection_pool_spec.rb +++ b/spec/lib/connection_pool/shared_connection_pool_spec.rb @@ -3,22 +3,24 @@ require 'rails_helper' describe ConnectionPool::SharedConnectionPool do - class MiniConnection - attr_reader :site + subject { described_class.new(size: 5, timeout: 5) { |site| mini_connection_class.new(site) } } - def initialize(site) - @site = site + let(:mini_connection_class) do + Class.new do + attr_reader :site + + def initialize(site) + @site = site + end end end - subject { described_class.new(size: 5, timeout: 5) { |site| MiniConnection.new(site) } } - describe '#with' do it 'runs a block with a connection' do block_run = false subject.with('foo') do |connection| - expect(connection).to be_a MiniConnection + expect(connection).to be_a mini_connection_class block_run = true end diff --git a/spec/lib/connection_pool/shared_timed_stack_spec.rb b/spec/lib/connection_pool/shared_timed_stack_spec.rb index f680c5966..04d550eec 100644 --- a/spec/lib/connection_pool/shared_timed_stack_spec.rb +++ b/spec/lib/connection_pool/shared_timed_stack_spec.rb @@ -3,30 +3,32 @@ require 'rails_helper' describe ConnectionPool::SharedTimedStack do - class MiniConnection - attr_reader :site + subject { described_class.new(5) { |site| mini_connection_class.new(site) } } - def initialize(site) - @site = site + let(:mini_connection_class) do + Class.new do + attr_reader :site + + def initialize(site) + @site = site + end end end - subject { described_class.new(5) { |site| MiniConnection.new(site) } } - describe '#push' do it 'keeps the connection in the stack' do - subject.push(MiniConnection.new('foo')) + subject.push(mini_connection_class.new('foo')) expect(subject.size).to eq 1 end end describe '#pop' do it 'returns a connection' do - expect(subject.pop('foo')).to be_a MiniConnection + expect(subject.pop('foo')).to be_a mini_connection_class end it 'returns the same connection that was pushed in' do - connection = MiniConnection.new('foo') + connection = mini_connection_class.new('foo') subject.push(connection) expect(subject.pop('foo')).to be connection end @@ -36,8 +38,8 @@ describe ConnectionPool::SharedTimedStack do end it 'repurposes a connection for a different site when maximum amount is reached' do - 5.times { subject.push(MiniConnection.new('foo')) } - expect(subject.pop('bar')).to be_a MiniConnection + 5.times { subject.push(mini_connection_class.new('foo')) } + expect(subject.pop('bar')).to be_a mini_connection_class end end @@ -47,14 +49,14 @@ describe ConnectionPool::SharedTimedStack do end it 'returns false when there are connections on the stack' do - subject.push(MiniConnection.new('foo')) + subject.push(mini_connection_class.new('foo')) expect(subject.empty?).to be false end end describe '#size' do it 'returns the number of connections on the stack' do - 2.times { subject.push(MiniConnection.new('foo')) } + 2.times { subject.push(mini_connection_class.new('foo')) } expect(subject.size).to eq 2 end end diff --git a/spec/lib/emoji_formatter_spec.rb b/spec/lib/emoji_formatter_spec.rb index e1747bdd9..e5accfbb0 100644 --- a/spec/lib/emoji_formatter_spec.rb +++ b/spec/lib/emoji_formatter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe EmojiFormatter do @@ -12,7 +14,7 @@ RSpec.describe EmojiFormatter do let(:emojis) { [emoji] } - context 'given text that is not marked as html-safe' do + context 'when given text that is not marked as html-safe' do let(:text) { 'Foo' } it 'raises an argument error' do @@ -20,35 +22,35 @@ RSpec.describe EmojiFormatter do end end - context 'given text with an emoji shortcode at the start' do + context 'when given text with an emoji shortcode at the start' do let(:text) { preformat_text(':coolcat: Beep boop') } it 'converts the shortcode to an image tag' do - is_expected.to match(/:coolcat:Foo bar

' + expect(subject).to eq '

Foo bar

' end end context 'when remote' do let(:local) { false } - context 'given plain text' do + context 'when given plain text' do let(:text) { 'Beep boop' } it 'keeps the plain text' do - is_expected.to include 'Beep boop' + expect(subject).to include 'Beep boop' end end - context 'given text containing script tags' do + context 'when given text containing script tags' do let(:text) { '' } it 'strips the scripts' do - is_expected.to_not include '' + expect(subject).to_not include '' end end - context 'given text containing malicious classes' do + context 'when given text containing malicious classes' do let(:text) { 'Show more' } it 'strips the malicious classes' do - is_expected.to_not include 'status__content__spoiler-link' + expect(subject).to_not include 'status__content__spoiler-link' end end end diff --git a/spec/lib/importer/accounts_index_importer_spec.rb b/spec/lib/importer/accounts_index_importer_spec.rb new file mode 100644 index 000000000..73f9bce39 --- /dev/null +++ b/spec/lib/importer/accounts_index_importer_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Importer::AccountsIndexImporter do + describe 'import!' do + let(:pool) { Concurrent::FixedThreadPool.new(5) } + let(:importer) { described_class.new(batch_size: 123, executor: pool) } + + before { Fabricate(:account) } + + it 'indexes relevant accounts' do + expect { importer.import! }.to update_index(AccountsIndex) + end + end +end diff --git a/spec/lib/importer/base_importer_spec.rb b/spec/lib/importer/base_importer_spec.rb new file mode 100644 index 000000000..78e9a869b --- /dev/null +++ b/spec/lib/importer/base_importer_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Importer::BaseImporter do + describe 'import!' do + let(:pool) { Concurrent::FixedThreadPool.new(5) } + let(:importer) { described_class.new(batch_size: 123, executor: pool) } + + it 'raises an error' do + expect { importer.import! }.to raise_error(NotImplementedError) + end + end +end diff --git a/spec/lib/importer/public_statuses_index_importer_spec.rb b/spec/lib/importer/public_statuses_index_importer_spec.rb new file mode 100644 index 000000000..bc7c038a9 --- /dev/null +++ b/spec/lib/importer/public_statuses_index_importer_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Importer::PublicStatusesIndexImporter do + describe 'import!' do + let(:pool) { Concurrent::FixedThreadPool.new(5) } + let(:importer) { described_class.new(batch_size: 123, executor: pool) } + + before { Fabricate(:status, account: Fabricate(:account, indexable: true)) } + + it 'indexes relevant statuses' do + expect { importer.import! }.to update_index(PublicStatusesIndex) + end + end +end diff --git a/spec/lib/importer/statuses_index_importer_spec.rb b/spec/lib/importer/statuses_index_importer_spec.rb new file mode 100644 index 000000000..d5e1c9f2c --- /dev/null +++ b/spec/lib/importer/statuses_index_importer_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Importer::StatusesIndexImporter do + describe 'import!' do + let(:pool) { Concurrent::FixedThreadPool.new(5) } + let(:importer) { described_class.new(batch_size: 123, executor: pool) } + + before { Fabricate(:status) } + + it 'indexes relevant statuses' do + expect { importer.import! }.to update_index(StatusesIndex) + end + end +end diff --git a/spec/lib/importer/tags_index_importer_spec.rb b/spec/lib/importer/tags_index_importer_spec.rb new file mode 100644 index 000000000..348990c01 --- /dev/null +++ b/spec/lib/importer/tags_index_importer_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Importer::TagsIndexImporter do + describe 'import!' do + let(:pool) { Concurrent::FixedThreadPool.new(5) } + let(:importer) { described_class.new(batch_size: 123, executor: pool) } + + before { Fabricate(:tag) } + + it 'indexes relevant tags' do + expect { importer.import! }.to update_index(TagsIndex) + end + end +end diff --git a/spec/lib/link_details_extractor_spec.rb b/spec/lib/link_details_extractor_spec.rb index 7ea867c61..8c485cef2 100644 --- a/spec/lib/link_details_extractor_spec.rb +++ b/spec/lib/link_details_extractor_spec.rb @@ -1,33 +1,33 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe LinkDetailsExtractor do - let(:original_url) { '' } - let(:html) { '' } - let(:html_charset) { nil } + subject { described_class.new(original_url, html, nil) } - subject { described_class.new(original_url, html, html_charset) } + let(:original_url) { 'https://example.com/dog.html?tracking=123' } describe '#canonical_url' do - let(:original_url) { 'https://foo.com/article?bar=baz123' } + let(:html) { "" } - context 'when canonical URL points to another host' do - let(:html) { '' } + context 'when canonical URL points to the same host' do + let(:url) { 'https://example.com/dog.html' } it 'ignores the canonical URLs' do - expect(subject.canonical_url).to eq original_url + expect(subject.canonical_url).to eq 'https://example.com/dog.html' end end - context 'when canonical URL points to the same host' do - let(:html) { '' } + context 'when canonical URL points to another host' do + let(:url) { 'https://different.example.net/dog.html' } it 'ignores the canonical URLs' do - expect(subject.canonical_url).to eq 'https://foo.com/article' + expect(subject.canonical_url).to eq original_url end end context 'when canonical URL is set to "null"' do - let(:html) { '' } + let(:url) { 'null' } it 'ignores the canonical URLs' do expect(subject.canonical_url).to eq original_url @@ -35,124 +35,267 @@ RSpec.describe LinkDetailsExtractor do end end + context 'when only basic metadata is present' do + let(:html) { <<~HTML } + + + + Man bites dog + + + + HTML + + describe '#title' do + it 'returns the title from title tag' do + expect(subject.title).to eq 'Man bites dog' + end + end + + describe '#description' do + it 'returns the description from meta tag' do + expect(subject.description).to eq "A dog's tale" + end + end + + describe '#language' do + it 'returns the language from lang attribute' do + expect(subject.language).to eq 'en' + end + end + end + context 'when structured data is present' do - let(:original_url) { 'https://example.com/page.html' } - - context 'and is wrapped in CDATA tags' do - let(:html) { <<-HTML } - - - - - - - HTML + let(:ld_json) do + { + '@context' => 'https://schema.org', + '@type' => 'NewsArticle', + 'headline' => 'Man bites dog', + 'description' => "A dog's tale", + 'datePublished' => '2022-01-31T19:53:00+00:00', + 'author' => { + '@type' => 'Organization', + 'name' => 'Charlie Brown', + }, + 'publisher' => { + '@type' => 'NewsMediaOrganization', + 'name' => 'Pet News', + 'url' => 'https://example.com', + }, + 'inLanguage' => { + name: 'English', + alternateName: 'en', + }, + }.to_json + end + shared_examples 'structured data' do describe '#title' do it 'returns the title from structured data' do - expect(subject.title).to eq 'Foo' + expect(subject.title).to eq 'Man bites dog' end end describe '#description' do it 'returns the description from structured data' do - expect(subject.description).to eq 'Bar' + expect(subject.description).to eq "A dog's tale" end end - describe '#provider_name' do - it 'returns the provider name from structured data' do - expect(subject.provider_name).to eq 'Baz' + describe '#published_at' do + it 'returns the publicaton time from structured data' do + expect(subject.published_at).to eq '2022-01-31T19:53:00+00:00' end end describe '#author_name' do it 'returns the author name from structured data' do - expect(subject.author_name).to eq 'Hoge' + expect(subject.author_name).to eq 'Charlie Brown' + end + end + + describe '#provider_name' do + it 'returns the provider name from structured data' do + expect(subject.provider_name).to eq 'Pet News' + end + end + + describe '#language' do + it 'returns the language from structured data' do + expect(subject.language).to eq 'en' end end end - context 'but the first tag is invalid JSON' do - let(:html) { <<-HTML } - - - - - - - + context 'when is wrapped in CDATA tags' do + let(:html) { <<~HTML } + + + + + + HTML - describe '#title' do - it 'returns the title from structured data' do - expect(subject.title).to eq 'Foo' - end - end + include_examples 'structured data' + end - describe '#description' do - it 'returns the description from structured data' do - expect(subject.description).to eq 'Bar' - end - end + context 'with the first tag is invalid JSON' do + let(:html) { <<~HTML } + + + + + + + + HTML - describe '#provider_name' do - it 'returns the provider name from structured data' do - expect(subject.provider_name).to eq 'Baz' - end - end + include_examples 'structured data' + end - describe '#author_name' do - it 'returns the author name from structured data' do - expect(subject.author_name).to eq 'Hoge' - end + context 'with preceding block of unsupported LD+JSON' do + let(:html) { <<~HTML } + + + + + + + + HTML + + include_examples 'structured data' + end + + context 'with unsupported in same block LD+JSON' do + let(:html) { <<~HTML } + + + + + + + HTML + + include_examples 'structured data' + end + end + + context 'when Open Graph protocol data is present' do + let(:html) { <<~HTML } + + + + + + + + + + + + + + + HTML + + describe '#canonical_url' do + it 'returns the URL from Open Graph protocol data' do + expect(subject.canonical_url).to eq 'https://example.com/dog.html' + end + end + + describe '#title' do + it 'returns the title from Open Graph protocol data' do + expect(subject.title).to eq 'Man bites dog' + end + end + + describe '#description' do + it 'returns the description from Open Graph protocol data' do + expect(subject.description).to eq "A dog's tale" + end + end + + describe '#published_at' do + it 'returns the publicaton time from Open Graph protocol data' do + expect(subject.published_at).to eq '2022-01-31T19:53:00+00:00' + end + end + + describe '#author_name' do + it 'returns the author name from Open Graph protocol data' do + expect(subject.author_name).to eq 'Charlie Brown' + end + end + + describe '#language' do + it 'returns the language from Open Graph protocol data' do + expect(subject.language).to eq 'en' + end + end + + describe '#image' do + it 'returns the image from Open Graph protocol data' do + expect(subject.image).to eq 'https://example.com/snoopy.jpg' + end + end + + describe '#image:alt' do + it 'returns the image description from Open Graph protocol data' do + expect(subject.image_alt).to eq 'A good boy' + end + end + + describe '#provider_name' do + it 'returns the provider name from Open Graph protocol data' do + expect(subject.provider_name).to eq 'Pet News' end end end diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb new file mode 100644 index 000000000..a263d673d --- /dev/null +++ b/spec/lib/mastodon/cli/accounts_spec.rb @@ -0,0 +1,1364 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/accounts' + +describe Mastodon::CLI::Accounts do + let(:cli) { described_class.new } + + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end + + describe '#create' do + shared_examples 'a new user with given email address and username' do + it 'creates a new user with the specified email address' do + cli.invoke(:create, arguments, options) + + expect(User.find_by(email: options[:email])).to be_present + end + + it 'creates a new local account with the specified username' do + cli.invoke(:create, arguments, options) + + expect(Account.find_local('tootctl_username')).to be_present + end + + it 'returns "OK" and newly generated password' do + allow(SecureRandom).to receive(:hex).and_return('test_password') + + expect { cli.invoke(:create, arguments, options) }.to output( + a_string_including("OK\nNew password: test_password") + ).to_stdout + end + end + + context 'when required USERNAME and --email are provided' do + let(:arguments) { ['tootctl_username'] } + + context 'with USERNAME and --email only' do + let(:options) { { email: 'tootctl@example.com' } } + + it_behaves_like 'a new user with given email address and username' + + context 'with invalid --email value' do + let(:options) { { email: 'invalid' } } + + it 'exits with an error message' do + expect { cli.invoke(:create, arguments, options) }.to output( + a_string_including('Failure/Error: email') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + + context 'with --confirmed option' do + let(:options) { { email: 'tootctl@example.com', confirmed: true } } + + it_behaves_like 'a new user with given email address and username' + + it 'creates a new user with confirmed status' do + cli.invoke(:create, arguments, options) + + user = User.find_by(email: options[:email]) + + expect(user.confirmed?).to be(true) + end + end + + context 'with --approve option' do + let(:options) { { email: 'tootctl@example.com', approve: true } } + + before do + Form::AdminSettings.new(registrations_mode: 'approved').save + end + + it_behaves_like 'a new user with given email address and username' + + it 'creates a new user with approved status' do + cli.invoke(:create, arguments, options) + + user = User.find_by(email: options[:email]) + + expect(user.approved?).to be(true) + end + end + + context 'with --role option' do + context 'when role exists' do + let(:default_role) { Fabricate(:user_role) } + let(:options) { { email: 'tootctl@example.com', role: default_role.name } } + + it_behaves_like 'a new user with given email address and username' + + it 'creates a new user and assigns the specified role' do + cli.invoke(:create, arguments, options) + + role = User.find_by(email: options[:email])&.role + + expect(role.name).to eq(default_role.name) + end + end + + context 'when role does not exist' do + let(:options) { { email: 'tootctl@example.com', role: '404' } } + + it 'exits with an error message indicating the role name was not found' do + expect { cli.invoke(:create, arguments, options) }.to output( + a_string_including('Cannot find user role with that name') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + + context 'with --reattach option' do + context "when account's user is present" do + let(:options) { { email: 'tootctl_new@example.com', reattach: true } } + let(:user) { Fabricate.build(:user, email: 'tootctl@example.com') } + + before do + Fabricate(:account, username: 'tootctl_username', user: user) + end + + it 'returns an error message indicating the username is already taken' do + expect { cli.invoke(:create, arguments, options) }.to output( + a_string_including("The chosen username is currently in use\nUse --force to reattach it anyway and delete the other user") + ).to_stdout + end + + context 'with --force option' do + let(:options) { { email: 'tootctl_new@example.com', reattach: true, force: true } } + + it 'reattaches the account to the new user and deletes the previous user' do + cli.invoke(:create, arguments, options) + + user = Account.find_local('tootctl_username')&.user + + expect(user.email).to eq(options[:email]) + end + end + end + + context "when account's user is not present" do + let(:options) { { email: 'tootctl@example.com', reattach: true } } + + before do + Fabricate(:account, username: 'tootctl_username', user: nil) + end + + it_behaves_like 'a new user with given email address and username' + end + end + end + + context 'when required --email option is not provided' do + let(:arguments) { ['tootctl_username'] } + + it 'raises a required argument missing error (Thor::RequiredArgumentMissingError)' do + expect { cli.invoke(:create, arguments) } + .to raise_error(Thor::RequiredArgumentMissingError) + end + end + end + + describe '#modify' do + context 'when the given username is not found' do + let(:arguments) { ['non_existent_username'] } + + it 'exits with an error message indicating the user was not found' do + expect { cli.invoke(:modify, arguments) }.to output( + a_string_including('No user with such username') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when the given username is found' do + let(:user) { Fabricate(:user) } + let(:arguments) { [user.account.username] } + + context 'when no option is provided' do + it 'returns a successful message' do + expect { cli.invoke(:modify, arguments) }.to output( + a_string_including('OK') + ).to_stdout + end + + it 'does not modify the user' do + cli.invoke(:modify, arguments) + + expect(user).to eq(user.reload) + end + end + + context 'with --role option' do + context 'when the given role is not found' do + let(:options) { { role: '404' } } + + it 'exits with an error message indicating the role was not found' do + expect { cli.invoke(:modify, arguments, options) }.to output( + a_string_including('Cannot find user role with that name') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when the given role is found' do + let(:default_role) { Fabricate(:user_role) } + let(:options) { { role: default_role.name } } + + it "updates the user's role to the specified role" do + cli.invoke(:modify, arguments, options) + + role = user.reload.role + + expect(role.name).to eq(default_role.name) + end + end + end + + context 'with --remove-role option' do + let(:options) { { remove_role: true } } + let(:role) { Fabricate(:user_role) } + let(:user) { Fabricate(:user, role: role) } + + it "removes the user's role successfully" do + cli.invoke(:modify, arguments, options) + + role = user.reload.role + + expect(role.name).to be_empty + end + end + + context 'with --email option' do + let(:user) { Fabricate(:user, email: 'old_email@email.com') } + let(:options) { { email: 'new_email@email.com' } } + + it "sets the user's unconfirmed email to the provided email address" do + cli.invoke(:modify, arguments, options) + + expect(user.reload.unconfirmed_email).to eq(options[:email]) + end + + it "does not update the user's original email address" do + cli.invoke(:modify, arguments, options) + + expect(user.reload.email).to eq('old_email@email.com') + end + + context 'with --confirm option' do + let(:user) { Fabricate(:user, email: 'old_email@email.com', confirmed_at: nil) } + let(:options) { { email: 'new_email@email.com', confirm: true } } + + it "updates the user's email address to the provided email" do + cli.invoke(:modify, arguments, options) + + expect(user.reload.email).to eq(options[:email]) + end + + it "sets the user's email address as confirmed" do + cli.invoke(:modify, arguments, options) + + expect(user.reload.confirmed?).to be(true) + end + end + end + + context 'with --confirm option' do + let(:user) { Fabricate(:user, confirmed_at: nil) } + let(:options) { { confirm: true } } + + it "confirms the user's email address" do + cli.invoke(:modify, arguments, options) + + expect(user.reload.confirmed?).to be(true) + end + end + + context 'with --approve option' do + let(:user) { Fabricate(:user, approved: false) } + let(:options) { { approve: true } } + + before do + Form::AdminSettings.new(registrations_mode: 'approved').save + end + + it 'approves the user' do + expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.approved }.from(false).to(true) + end + end + + context 'with --disable option' do + let(:user) { Fabricate(:user, disabled: false) } + let(:options) { { disable: true } } + + it 'disables the user' do + expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.disabled }.from(false).to(true) + end + end + + context 'with --enable option' do + let(:user) { Fabricate(:user, disabled: true) } + let(:options) { { enable: true } } + + it 'enables the user' do + expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.disabled }.from(true).to(false) + end + end + + context 'with --reset-password option' do + let(:options) { { reset_password: true } } + + it 'returns a new password for the user' do + allow(SecureRandom).to receive(:hex).and_return('new_password') + + expect { cli.invoke(:modify, arguments, options) }.to output( + a_string_including('new_password') + ).to_stdout + end + end + + context 'with --disable-2fa option' do + let(:user) { Fabricate(:user, otp_required_for_login: true) } + let(:options) { { disable_2fa: true } } + + it 'disables the two-factor authentication for the user' do + expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.otp_required_for_login }.from(true).to(false) + end + end + + context 'when provided data is invalid' do + let(:user) { Fabricate(:user) } + let(:options) { { email: 'invalid' } } + + it 'exits with an error message' do + expect { cli.invoke(:modify, arguments, options) }.to output( + a_string_including('Failure/Error: email') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + end + + describe '#delete' do + let(:account) { Fabricate(:account) } + let(:arguments) { [account.username] } + let(:options) { { email: account.user.email } } + let(:delete_account_service) { instance_double(DeleteAccountService) } + + before do + allow(DeleteAccountService).to receive(:new).and_return(delete_account_service) + allow(delete_account_service).to receive(:call) + end + + context 'when both username and --email are provided' do + it 'exits with an error message indicating that only one should be used' do + expect { cli.invoke(:delete, arguments, options) }.to output( + a_string_including('Use username or --email, not both') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when neither username nor --email are provided' do + it 'exits with an error message indicating that no username was provided' do + expect { cli.invoke(:delete) }.to output( + a_string_including('No username provided') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when username is provided' do + it 'deletes the specified user successfully' do + cli.invoke(:delete, arguments) + + expect(delete_account_service).to have_received(:call).with(account, reserve_email: false).once + end + + context 'with --dry-run option' do + let(:options) { { dry_run: true } } + + it 'does not delete the specified user' do + cli.invoke(:delete, arguments, options) + + expect(delete_account_service).to_not have_received(:call).with(account, reserve_email: false) + end + + it 'outputs a successful message in dry run mode' do + expect { cli.invoke(:delete, arguments, options) }.to output( + a_string_including('OK (DRY RUN)') + ).to_stdout + end + end + + context 'when the given username is not found' do + let(:arguments) { ['non_existent_username'] } + + it 'exits with an error message indicating that no user was found' do + expect { cli.invoke(:delete, arguments) }.to output( + a_string_including('No user with such username') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + + context 'when --email is provided' do + it 'deletes the specified user successfully' do + cli.invoke(:delete, nil, options) + + expect(delete_account_service).to have_received(:call).with(account, reserve_email: false).once + end + + context 'with --dry-run option' do + let(:options) { { email: account.user.email, dry_run: true } } + + it 'does not delete the user' do + cli.invoke(:delete, nil, options) + + expect(delete_account_service).to_not have_received(:call).with(account, reserve_email: false) + end + + it 'outputs a successful message in dry run mode' do + expect { cli.invoke(:delete, nil, options) }.to output( + a_string_including('OK (DRY RUN)') + ).to_stdout + end + end + + context 'when the given email address is not found' do + let(:options) { { email: '404@example.com' } } + + it 'exits with an error message indicating that no user was found' do + expect { cli.invoke(:delete, nil, options) }.to output( + a_string_including('No user with such email') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + end + + describe '#approve' do + let(:total_users) { 10 } + + before do + Form::AdminSettings.new(registrations_mode: 'approved').save + Fabricate.times(total_users, :user) + end + + context 'with --all option' do + it 'approves all pending registrations' do + cli.invoke(:approve, nil, all: true) + + expect(User.pluck(:approved).all?(true)).to be(true) + end + end + + context 'with --number option' do + context 'when the number is positive' do + let(:options) { { number: 3 } } + + it 'approves the earliest n pending registrations' do + cli.invoke(:approve, nil, options) + + n_earliest_pending_registrations = User.order(created_at: :asc).first(options[:number]) + + expect(n_earliest_pending_registrations.all?(&:approved?)).to be(true) + end + + it 'does not approve the remaining pending registrations' do + cli.invoke(:approve, nil, options) + + pending_registrations = User.order(created_at: :asc).last(total_users - options[:number]) + + expect(pending_registrations.all?(&:approved?)).to be(false) + end + end + + context 'when the number is negative' do + it 'exits with an error message indicating that the number must be positive' do + expect { cli.invoke(:approve, nil, number: -1) }.to output( + a_string_including('Number must be positive') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when the given number is greater than the number of users' do + let(:options) { { number: total_users * 2 } } + + it 'approves all users' do + cli.invoke(:approve, nil, options) + + expect(User.pluck(:approved).all?(true)).to be(true) + end + + it 'does not raise any error' do + expect { cli.invoke(:approve, nil, options) } + .to_not raise_error + end + end + end + + context 'with username argument' do + context 'when the given username is found' do + let(:user) { User.last } + let(:arguments) { [user.account.username] } + + it 'approves the specified user successfully' do + cli.invoke(:approve, arguments) + + expect(user.reload.approved?).to be(true) + end + end + + context 'when the given username is not found' do + let(:arguments) { ['non_existent_username'] } + + it 'exits with an error message indicating that no such account was found' do + expect { cli.invoke(:approve, arguments) }.to output( + a_string_including('No such account') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + end + + describe '#follow' do + context 'when the given username is not found' do + let(:arguments) { ['non_existent_username'] } + + it 'exits with an error message indicating that no account with the given username was found' do + expect { cli.invoke(:follow, arguments) }.to output( + a_string_including('No such account') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when the given username is found' do + let!(:target_account) { Fabricate(:account) } + let!(:follower_bob) { Fabricate(:account, username: 'bob') } + let!(:follower_rony) { Fabricate(:account, username: 'rony') } + let!(:follower_charles) { Fabricate(:account, username: 'charles') } + let(:follow_service) { instance_double(FollowService, call: nil) } + let(:scope) { Account.local.without_suspended } + + before do + allow(cli).to receive(:parallelize_with_progress).and_yield(follower_bob) + .and_yield(follower_rony) + .and_yield(follower_charles) + .and_return([3, nil]) + allow(FollowService).to receive(:new).and_return(follow_service) + end + + it 'makes all local accounts follow the target account' do + cli.follow(target_account.username) + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(follow_service).to have_received(:call).with(follower_bob, target_account, any_args).once + expect(follow_service).to have_received(:call).with(follower_rony, target_account, any_args).once + expect(follow_service).to have_received(:call).with(follower_charles, target_account, any_args).once + end + + it 'displays a successful message' do + expect { cli.follow(target_account.username) }.to output( + a_string_including('OK, followed target from 3 accounts') + ).to_stdout + end + end + end + + describe '#unfollow' do + context 'when the given username is not found' do + let(:arguments) { ['non_existent_username'] } + + it 'exits with an error message indicating that no account with the given username was found' do + expect { cli.invoke(:unfollow, arguments) }.to output( + a_string_including('No such account') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when the given username is found' do + let!(:target_account) { Fabricate(:account) } + let!(:follower_chris) { Fabricate(:account, username: 'chris') } + let!(:follower_rambo) { Fabricate(:account, username: 'rambo') } + let!(:follower_ana) { Fabricate(:account, username: 'ana') } + let(:unfollow_service) { instance_double(UnfollowService, call: nil) } + let(:scope) { target_account.followers.local } + + before do + accounts = [follower_chris, follower_rambo, follower_ana] + accounts.each { |account| target_account.follow!(account) } + allow(cli).to receive(:parallelize_with_progress).and_yield(follower_chris) + .and_yield(follower_rambo) + .and_yield(follower_ana) + .and_return([3, nil]) + allow(UnfollowService).to receive(:new).and_return(unfollow_service) + end + + it 'makes all local accounts unfollow the target account' do + cli.unfollow(target_account.username) + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(unfollow_service).to have_received(:call).with(follower_chris, target_account).once + expect(unfollow_service).to have_received(:call).with(follower_rambo, target_account).once + expect(unfollow_service).to have_received(:call).with(follower_ana, target_account).once + end + + it 'displays a successful message' do + expect { cli.unfollow(target_account.username) }.to output( + a_string_including('OK, unfollowed target from 3 accounts') + ).to_stdout + end + end + end + + describe '#backup' do + context 'when the given username is not found' do + let(:arguments) { ['non_existent_username'] } + + it 'exits with an error message indicating that there is no such account' do + expect { cli.invoke(:backup, arguments) }.to output( + a_string_including('No user with such username') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when the given username is found' do + let(:account) { Fabricate(:account) } + let(:user) { account.user } + let(:arguments) { [account.username] } + + it 'creates a new backup for the specified user' do + expect { cli.invoke(:backup, arguments) }.to change { user.backups.count }.by(1) + end + + it 'creates a backup job' do + allow(BackupWorker).to receive(:perform_async) + + cli.invoke(:backup, arguments) + latest_backup = user.backups.last + + expect(BackupWorker).to have_received(:perform_async).with(latest_backup.id).once + end + + it 'displays a successful message' do + expect { cli.invoke(:backup, arguments) }.to output( + a_string_including('OK') + ).to_stdout + end + end + end + + describe '#refresh' do + context 'with --all option' do + let!(:local_account) { Fabricate(:account, domain: nil) } + let!(:remote_account_example_com) { Fabricate(:account, domain: 'example.com') } + let!(:account_example_net) { Fabricate(:account, domain: 'example.net') } + let(:scope) { Account.remote } + + before do + allow(cli).to receive(:parallelize_with_progress).and_yield(remote_account_example_com) + .and_yield(account_example_net) + .and_return([2, nil]) + cli.options = { all: true } + end + + it 'refreshes the avatar for all remote accounts' do + allow(remote_account_example_com).to receive(:reset_avatar!) + allow(account_example_net).to receive(:reset_avatar!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(remote_account_example_com).to have_received(:reset_avatar!).once + expect(account_example_net).to have_received(:reset_avatar!).once + end + + it 'does not refresh avatar for local accounts' do + allow(local_account).to receive(:reset_avatar!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(local_account).to_not have_received(:reset_avatar!) + end + + it 'refreshes the header for all remote accounts' do + allow(remote_account_example_com).to receive(:reset_header!) + allow(account_example_net).to receive(:reset_header!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(remote_account_example_com).to have_received(:reset_header!).once + expect(account_example_net).to have_received(:reset_header!).once + end + + it 'does not refresh the header for local accounts' do + allow(local_account).to receive(:reset_header!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(local_account).to_not have_received(:reset_header!) + end + + it 'displays a successful message' do + expect { cli.refresh }.to output( + a_string_including('Refreshed 2 accounts') + ).to_stdout + end + + context 'with --dry-run option' do + before do + cli.options = { all: true, dry_run: true } + end + + it 'does not refresh the avatar for any account' do + allow(local_account).to receive(:reset_avatar!) + allow(remote_account_example_com).to receive(:reset_avatar!) + allow(account_example_net).to receive(:reset_avatar!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(local_account).to_not have_received(:reset_avatar!) + expect(remote_account_example_com).to_not have_received(:reset_avatar!) + expect(account_example_net).to_not have_received(:reset_avatar!) + end + + it 'does not refresh the header for any account' do + allow(local_account).to receive(:reset_header!) + allow(remote_account_example_com).to receive(:reset_header!) + allow(account_example_net).to receive(:reset_header!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(local_account).to_not have_received(:reset_header!) + expect(remote_account_example_com).to_not have_received(:reset_header!) + expect(account_example_net).to_not have_received(:reset_header!) + end + + it 'displays a successful message with (DRY RUN)' do + expect { cli.refresh }.to output( + a_string_including('Refreshed 2 accounts (DRY RUN)') + ).to_stdout + end + end + end + + context 'with a list of accts' do + let!(:account_example_com_a) { Fabricate(:account, domain: 'example.com') } + let!(:account_example_com_b) { Fabricate(:account, domain: 'example.com') } + let!(:account_example_net) { Fabricate(:account, domain: 'example.net') } + let(:arguments) { [account_example_com_a.acct, account_example_com_b.acct] } + + before do + allow(Account).to receive(:find_remote).with(account_example_com_a.username, account_example_com_a.domain).and_return(account_example_com_a) + allow(Account).to receive(:find_remote).with(account_example_com_b.username, account_example_com_b.domain).and_return(account_example_com_b) + allow(Account).to receive(:find_remote).with(account_example_net.username, account_example_net.domain).and_return(account_example_net) + end + + it 'resets the avatar for the specified accounts' do + allow(account_example_com_a).to receive(:reset_avatar!) + allow(account_example_com_b).to receive(:reset_avatar!) + + cli.refresh(*arguments) + + expect(account_example_com_a).to have_received(:reset_avatar!).once + expect(account_example_com_b).to have_received(:reset_avatar!).once + end + + it 'does not reset the avatar for unspecified accounts' do + allow(account_example_net).to receive(:reset_avatar!) + + cli.refresh(*arguments) + + expect(account_example_net).to_not have_received(:reset_avatar!) + end + + it 'resets the header for the specified accounts' do + allow(account_example_com_a).to receive(:reset_header!) + allow(account_example_com_b).to receive(:reset_header!) + + cli.refresh(*arguments) + + expect(account_example_com_a).to have_received(:reset_header!).once + expect(account_example_com_b).to have_received(:reset_header!).once + end + + it 'does not reset the header for unspecified accounts' do + allow(account_example_net).to receive(:reset_header!) + + cli.refresh(*arguments) + + expect(account_example_net).to_not have_received(:reset_header!) + end + + context 'when an UnexpectedResponseError is raised' do + it 'displays a failure message' do + allow(account_example_com_a).to receive(:reset_avatar!).and_raise(Mastodon::UnexpectedResponseError) + + expect { cli.refresh(*arguments) } + .to output( + a_string_including("Account failed: #{account_example_com_a.username}@#{account_example_com_a.domain}") + ).to_stdout + end + end + + context 'when a specified account is not found' do + it 'exits with an error message' do + allow(Account).to receive(:find_remote).with(account_example_com_b.username, account_example_com_b.domain).and_return(nil) + + expect { cli.refresh(*arguments) }.to output( + a_string_including('No such account') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'with --dry-run option' do + before do + cli.options = { dry_run: true } + end + + it 'does not refresh the avatar for any account' do + allow(account_example_com_a).to receive(:reset_avatar!) + allow(account_example_com_b).to receive(:reset_avatar!) + + cli.refresh(*arguments) + + expect(account_example_com_a).to_not have_received(:reset_avatar!) + expect(account_example_com_b).to_not have_received(:reset_avatar!) + end + + it 'does not refresh the header for any account' do + allow(account_example_com_a).to receive(:reset_header!) + allow(account_example_com_b).to receive(:reset_header!) + + cli.refresh(*arguments) + + expect(account_example_com_a).to_not have_received(:reset_header!) + expect(account_example_com_b).to_not have_received(:reset_header!) + end + end + end + + context 'with --domain option' do + let!(:account_example_com_a) { Fabricate(:account, domain: 'example.com') } + let!(:account_example_com_b) { Fabricate(:account, domain: 'example.com') } + let!(:account_example_net) { Fabricate(:account, domain: 'example.net') } + let(:domain) { 'example.com' } + let(:scope) { Account.remote.where(domain: domain) } + + before do + allow(cli).to receive(:parallelize_with_progress).and_yield(account_example_com_a) + .and_yield(account_example_com_b) + .and_return([2, nil]) + + cli.options = { domain: domain } + end + + it 'refreshes the avatar for all accounts on specified domain' do + allow(account_example_com_a).to receive(:reset_avatar!) + allow(account_example_com_b).to receive(:reset_avatar!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(account_example_com_a).to have_received(:reset_avatar!).once + expect(account_example_com_b).to have_received(:reset_avatar!).once + end + + it 'does not refresh the avatar for accounts outside specified domain' do + allow(account_example_net).to receive(:reset_avatar!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(account_example_net).to_not have_received(:reset_avatar!) + end + + it 'refreshes the header for all accounts on specified domain' do + allow(account_example_com_a).to receive(:reset_header!) + allow(account_example_com_b).to receive(:reset_header!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope) + expect(account_example_com_a).to have_received(:reset_header!).once + expect(account_example_com_b).to have_received(:reset_header!).once + end + + it 'does not refresh the header for accounts outside specified domain' do + allow(account_example_net).to receive(:reset_header!) + + cli.refresh + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(account_example_net).to_not have_received(:reset_header!) + end + end + + context 'when neither a list of accts nor options are provided' do + it 'exits with an error message' do + expect { cli.refresh }.to output( + a_string_including('No account(s) given') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + + describe '#rotate' do + context 'when neither username nor --all option are given' do + it 'exits with an error message' do + expect { cli.rotate }.to output( + a_string_including('No account(s) given') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when a username is given' do + let(:account) { Fabricate(:account) } + + it 'correctly rotates keys for the specified account' do + old_private_key = account.private_key + old_public_key = account.public_key + + cli.rotate(account.username) + account.reload + + expect(account.private_key).to_not eq(old_private_key) + expect(account.public_key).to_not eq(old_public_key) + end + + it 'broadcasts the new keys for the specified account' do + allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in) + + cli.rotate(account.username) + + expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once + end + + context 'when the given username is not found' do + it 'exits with an error message when the specified username is not found' do + expect { cli.rotate('non_existent_username') }.to output( + a_string_including('No such account') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + + context 'when --all option is provided' do + let(:accounts) { Fabricate.times(3, :account) } + let(:options) { { all: true } } + + before do + allow(Account).to receive(:local).and_return(Account.where(id: accounts.map(&:id))) + cli.options = { all: true } + end + + it 'correctly rotates keys for all local accounts' do + old_private_keys = accounts.map(&:private_key) + old_public_keys = accounts.map(&:public_key) + + cli.rotate + accounts.each(&:reload) + + expect(accounts.map(&:private_key)).to_not eq(old_private_keys) + expect(accounts.map(&:public_key)).to_not eq(old_public_keys) + end + + it 'broadcasts the new keys for each account' do + allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in) + + cli.rotate + + accounts.each do |account| + expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once + end + end + end + end + + describe '#merge' do + shared_examples 'an account not found' do |acct| + it 'exits with an error message indicating that there is no such account' do + expect { cli.invoke(:merge, arguments) }.to output( + a_string_including("No such account (#{acct})") + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when "from_account" is not found' do + let(:to_account) { Fabricate(:account, domain: 'example.com') } + let(:arguments) { ['non_existent_username@domain.com', "#{to_account.username}@#{to_account.domain}"] } + + it_behaves_like 'an account not found', 'non_existent_username@domain.com' + end + + context 'when "from_account" is a local account' do + let(:from_account) { Fabricate(:account, domain: nil, username: 'bob') } + let(:to_account) { Fabricate(:account, domain: 'example.com') } + let(:arguments) { [from_account.username, "#{to_account.username}@#{to_account.domain}"] } + + it_behaves_like 'an account not found', 'bob' + end + + context 'when "to_account" is not found' do + let(:from_account) { Fabricate(:account, domain: 'example.com') } + let(:arguments) { ["#{from_account.username}@#{from_account.domain}", 'non_existent_username'] } + + it_behaves_like 'an account not found', 'non_existent_username' + end + + context 'when "to_account" is local' do + let(:from_account) { Fabricate(:account, domain: 'example.com') } + let(:to_account) { Fabricate(:account, domain: nil, username: 'bob') } + let(:arguments) do + ["#{from_account.username}@#{from_account.domain}", "#{to_account.username}@#{to_account.domain}"] + end + + it_behaves_like 'an account not found', 'bob@' + end + + context 'when "from_account" and "to_account" public keys do not match' do + let(:from_account) { instance_double(Account, username: 'bob', domain: 'example1.com', local?: false, public_key: 'from_account') } + let(:to_account) { instance_double(Account, username: 'bob', domain: 'example2.com', local?: false, public_key: 'to_account') } + let(:arguments) do + ["#{from_account.username}@#{from_account.domain}", "#{to_account.username}@#{to_account.domain}"] + end + + before do + allow(Account).to receive(:find_remote).with(from_account.username, from_account.domain).and_return(from_account) + allow(Account).to receive(:find_remote).with(to_account.username, to_account.domain).and_return(to_account) + end + + it 'exits with an error message indicating that the accounts do not have the same pub key' do + expect { cli.invoke(:merge, arguments) }.to output( + a_string_including("Accounts don't have the same public key, might not be duplicates!\nOverride with --force") + ).to_stdout + .and raise_error(SystemExit) + end + + context 'with --force option' do + let(:options) { { force: true } } + + before do + allow(to_account).to receive(:merge_with!) + allow(from_account).to receive(:destroy) + end + + it 'merges "from_account" into "to_account"' do + cli.invoke(:merge, arguments, options) + + expect(to_account).to have_received(:merge_with!).with(from_account).once + end + + it 'deletes "from_account"' do + cli.invoke(:merge, arguments, options) + + expect(from_account).to have_received(:destroy).once + end + end + end + + context 'when "from_account" and "to_account" public keys match' do + let(:from_account) { instance_double(Account, username: 'bob', domain: 'example1.com', local?: false, public_key: 'pub_key') } + let(:to_account) { instance_double(Account, username: 'bob', domain: 'example2.com', local?: false, public_key: 'pub_key') } + let(:arguments) do + ["#{from_account.username}@#{from_account.domain}", "#{to_account.username}@#{to_account.domain}"] + end + + before do + allow(Account).to receive(:find_remote).with(from_account.username, from_account.domain).and_return(from_account) + allow(Account).to receive(:find_remote).with(to_account.username, to_account.domain).and_return(to_account) + allow(to_account).to receive(:merge_with!) + allow(from_account).to receive(:destroy) + end + + it 'merges "from_account" into "to_account"' do + cli.invoke(:merge, arguments) + + expect(to_account).to have_received(:merge_with!).with(from_account).once + end + + it 'deletes "from_account"' do + cli.invoke(:merge, arguments) + + expect(from_account).to have_received(:destroy) + end + end + end + + describe '#cull' do + let(:delete_account_service) { instance_double(DeleteAccountService, call: nil) } + let!(:tom) { Fabricate(:account, updated_at: 30.days.ago, username: 'tom', uri: 'https://example.com/users/tom', domain: 'example.com') } + let!(:bob) { Fabricate(:account, updated_at: 30.days.ago, last_webfingered_at: nil, username: 'bob', uri: 'https://example.org/users/bob', domain: 'example.org') } + let!(:gon) { Fabricate(:account, updated_at: 15.days.ago, last_webfingered_at: 15.days.ago, username: 'gon', uri: 'https://example.net/users/gon', domain: 'example.net') } + let!(:ana) { Fabricate(:account, username: 'ana', uri: 'https://example.com/users/ana', domain: 'example.com') } + let!(:tales) { Fabricate(:account, updated_at: 10.days.ago, last_webfingered_at: nil, username: 'tales', uri: 'https://example.net/users/tales', domain: 'example.net') } + + before do + allow(DeleteAccountService).to receive(:new).and_return(delete_account_service) + end + + context 'when no domain is specified' do + let(:scope) { Account.remote.where(protocol: :activitypub).partitioned } + + before do + allow(cli).to receive(:parallelize_with_progress).and_yield(tom) + .and_yield(bob) + .and_yield(gon) + .and_yield(ana) + .and_yield(tales) + .and_return([5, 3]) + stub_request(:head, 'https://example.org/users/bob').to_return(status: 404) + stub_request(:head, 'https://example.net/users/gon').to_return(status: 410) + stub_request(:head, 'https://example.net/users/tales').to_return(status: 200) + end + + it 'deletes all inactive remote accounts that longer exist in the origin server' do + cli.cull + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(delete_account_service).to have_received(:call).with(bob, reserve_username: false).once + expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once + end + + it 'does not delete any active remote account that still exists in the origin server' do + cli.cull + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(delete_account_service).to_not have_received(:call).with(tom, reserve_username: false) + expect(delete_account_service).to_not have_received(:call).with(ana, reserve_username: false) + expect(delete_account_service).to_not have_received(:call).with(tales, reserve_username: false) + end + + it 'touches inactive remote accounts that have not been deleted' do + allow(tales).to receive(:touch) + + cli.cull + + expect(tales).to have_received(:touch).once + end + + it 'displays the summary correctly' do + expect { cli.cull }.to output( + a_string_including('Visited 5 accounts, removed 3') + ).to_stdout + end + end + + context 'when a domain is specified' do + let(:domain) { 'example.net' } + let(:scope) { Account.remote.where(protocol: :activitypub, domain: domain).partitioned } + + before do + allow(cli).to receive(:parallelize_with_progress).and_yield(gon) + .and_yield(tales) + .and_return([2, 2]) + stub_request(:head, 'https://example.net/users/gon').to_return(status: 410) + stub_request(:head, 'https://example.net/users/tales').to_return(status: 404) + end + + it 'deletes inactive remote accounts that longer exist in the specified domain' do + cli.cull(domain) + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once + expect(delete_account_service).to have_received(:call).with(tales, reserve_username: false).once + end + + it 'displays the summary correctly' do + expect { cli.cull }.to output( + a_string_including('Visited 2 accounts, removed 2') + ).to_stdout + end + end + + context 'when a domain is unavailable' do + shared_examples 'an unavailable domain' do + before do + allow(cli).to receive(:parallelize_with_progress).and_yield(tales).and_return([1, 0]) + end + + it 'skips accounts from the unavailable domain' do + cli.cull + + expect(delete_account_service).to_not have_received(:call).with(tales, reserve_username: false) + end + + it 'displays the summary correctly' do + expect { cli.cull }.to output( + a_string_including("Visited 1 accounts, removed 0\nThe following domains were not available during the check:\n example.net") + ).to_stdout + end + end + + context 'when a connection timeout occurs' do + before do + stub_request(:head, 'https://example.net/users/tales').to_timeout + end + + it_behaves_like 'an unavailable domain' + end + + context 'when a connection error occurs' do + before do + stub_request(:head, 'https://example.net/users/tales').to_raise(HTTP::ConnectionError) + end + + it_behaves_like 'an unavailable domain' + end + + context 'when an ssl error occurs' do + before do + stub_request(:head, 'https://example.net/users/tales').to_raise(OpenSSL::SSL::SSLError) + end + + it_behaves_like 'an unavailable domain' + end + + context 'when a private network address error occurs' do + before do + stub_request(:head, 'https://example.net/users/tales').to_raise(Mastodon::PrivateNetworkAddressError) + end + + it_behaves_like 'an unavailable domain' + end + end + end + + describe '#reset_relationships' do + let(:target_account) { Fabricate(:account) } + let(:arguments) { [target_account.username] } + + context 'when no option is given' do + it 'exits with an error message indicating that at least one option is required' do + expect { cli.invoke(:reset_relationships, arguments) }.to output( + a_string_including('Please specify either --follows or --followers, or both') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when the given username is not found' do + let(:arguments) { ['non_existent_username'] } + + it 'exits with an error message indicating that there is no such account' do + expect { cli.invoke(:reset_relationships, arguments, follows: true) }.to output( + a_string_including('No such account') + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when the given username is found' do + let(:total_relationships) { 10 } + let!(:accounts) { Fabricate.times(total_relationships, :account) } + + context 'with --follows option' do + let(:options) { { follows: true } } + + before do + accounts.each { |account| target_account.follow!(account) } + end + + it 'resets all "following" relationships from the target account' do + cli.invoke(:reset_relationships, arguments, options) + + expect(target_account.reload.following).to be_empty + end + + it 'calls BootstrapTimelineWorker once to rebuild the timeline' do + allow(BootstrapTimelineWorker).to receive(:perform_async) + + cli.invoke(:reset_relationships, arguments, options) + + expect(BootstrapTimelineWorker).to have_received(:perform_async).with(target_account.id).once + end + + it 'displays a successful message' do + expect { cli.invoke(:reset_relationships, arguments, options) }.to output( + a_string_including("Processed #{total_relationships} relationships") + ).to_stdout + end + end + + context 'with --followers option' do + let(:options) { { followers: true } } + + before do + accounts.each { |account| account.follow!(target_account) } + end + + it 'resets all "followers" relationships from the target account' do + cli.invoke(:reset_relationships, arguments, options) + + expect(target_account.reload.followers).to be_empty + end + + it 'displays a successful message' do + expect { cli.invoke(:reset_relationships, arguments, options) }.to output( + a_string_including("Processed #{total_relationships} relationships") + ).to_stdout + end + end + + context 'with --follows and --followers options' do + let(:options) { { followers: true, follows: true } } + + before do + accounts.first(6).each { |account| account.follow!(target_account) } + accounts.last(4).each { |account| target_account.follow!(account) } + end + + it 'resets all "followers" relationships from the target account' do + cli.invoke(:reset_relationships, arguments, options) + + expect(target_account.reload.followers).to be_empty + end + + it 'resets all "following" relationships from the target account' do + cli.invoke(:reset_relationships, arguments, options) + + expect(target_account.reload.following).to be_empty + end + + it 'calls BootstrapTimelineWorker once to rebuild the timeline' do + allow(BootstrapTimelineWorker).to receive(:perform_async) + + cli.invoke(:reset_relationships, arguments, options) + + expect(BootstrapTimelineWorker).to have_received(:perform_async).with(target_account.id).once + end + + it 'displays a successful message' do + expect { cli.invoke(:reset_relationships, arguments, options) }.to output( + a_string_including("Processed #{total_relationships} relationships") + ).to_stdout + end + end + end + end +end diff --git a/spec/lib/mastodon/cli/cache_spec.rb b/spec/lib/mastodon/cli/cache_spec.rb new file mode 100644 index 000000000..3ab42dc8c --- /dev/null +++ b/spec/lib/mastodon/cli/cache_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/cache' + +describe Mastodon::CLI::Cache do + let(:cli) { described_class.new } + + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end + + describe '#clear' do + before { allow(Rails.cache).to receive(:clear) } + + it 'clears the Rails cache' do + expect { cli.invoke(:clear) }.to output( + a_string_including('OK') + ).to_stdout + expect(Rails.cache).to have_received(:clear) + end + end + + describe '#recount' do + context 'with the `accounts` argument' do + let(:arguments) { ['accounts'] } + let(:account_stat) { Fabricate(:account_stat) } + + before do + account_stat.update(statuses_count: 123) + end + + it 're-calculates account records in the cache' do + expect { cli.invoke(:recount, arguments) }.to output( + a_string_including('OK') + ).to_stdout + + expect(account_stat.reload.statuses_count).to be_zero + end + end + + context 'with the `statuses` argument' do + let(:arguments) { ['statuses'] } + let(:status_stat) { Fabricate(:status_stat) } + + before do + status_stat.update(replies_count: 123) + end + + it 're-calculates account records in the cache' do + expect { cli.invoke(:recount, arguments) }.to output( + a_string_including('OK') + ).to_stdout + + expect(status_stat.reload.replies_count).to be_zero + end + end + + context 'with an unknown type' do + let(:arguments) { ['other-type'] } + + it 'Exits with an error message' do + expect { cli.invoke(:recount, arguments) }.to output( + a_string_including('Unknown') + ).to_stdout.and raise_error(SystemExit) + end + end + end +end diff --git a/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb b/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb new file mode 100644 index 000000000..eb57a3cd1 --- /dev/null +++ b/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/canonical_email_blocks' + +describe Mastodon::CLI::CanonicalEmailBlocks do + let(:cli) { described_class.new } + + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end + + describe '#find' do + let(:arguments) { ['user@example.com'] } + + context 'when a block is present' do + before { Fabricate(:canonical_email_block, email: 'user@example.com') } + + it 'announces the presence of the block' do + expect { cli.invoke(:find, arguments) }.to output( + a_string_including('user@example.com is blocked') + ).to_stdout + end + end + + context 'when a block is not present' do + it 'announces the absence of the block' do + expect { cli.invoke(:find, arguments) }.to output( + a_string_including('user@example.com is not blocked') + ).to_stdout + end + end + end + + describe '#remove' do + let(:arguments) { ['user@example.com'] } + + context 'when a block is present' do + before { Fabricate(:canonical_email_block, email: 'user@example.com') } + + it 'removes the block' do + expect { cli.invoke(:remove, arguments) }.to output( + a_string_including('Unblocked user@example.com') + ).to_stdout + + expect(CanonicalEmailBlock.matching_email('user@example.com')).to be_empty + end + end + + context 'when a block is not present' do + it 'announces the absence of the block' do + expect { cli.invoke(:remove, arguments) }.to output( + a_string_including('user@example.com is not blocked') + ).to_stdout + end + end + end +end diff --git a/spec/lib/mastodon/cli/domains_spec.rb b/spec/lib/mastodon/cli/domains_spec.rb new file mode 100644 index 000000000..ea58845c0 --- /dev/null +++ b/spec/lib/mastodon/cli/domains_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/domains' + +describe Mastodon::CLI::Domains do + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end +end diff --git a/spec/lib/mastodon/cli/email_domain_blocks_spec.rb b/spec/lib/mastodon/cli/email_domain_blocks_spec.rb new file mode 100644 index 000000000..333ae3f2b --- /dev/null +++ b/spec/lib/mastodon/cli/email_domain_blocks_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/email_domain_blocks' + +describe Mastodon::CLI::EmailDomainBlocks do + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end +end diff --git a/spec/lib/mastodon/cli/emoji_spec.rb b/spec/lib/mastodon/cli/emoji_spec.rb new file mode 100644 index 000000000..9b5865372 --- /dev/null +++ b/spec/lib/mastodon/cli/emoji_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/emoji' + +describe Mastodon::CLI::Emoji do + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end +end diff --git a/spec/lib/mastodon/cli/feeds_spec.rb b/spec/lib/mastodon/cli/feeds_spec.rb new file mode 100644 index 000000000..030f08721 --- /dev/null +++ b/spec/lib/mastodon/cli/feeds_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/feeds' + +describe Mastodon::CLI::Feeds do + let(:cli) { described_class.new } + + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end + + describe '#build' do + before { Fabricate(:account) } + + context 'with --all option' do + let(:options) { { all: true } } + + it 'regenerates feeds for all accounts' do + expect { cli.invoke(:build, [], options) }.to output( + a_string_including('Regenerated feeds') + ).to_stdout + end + end + + context 'with a username' do + before { Fabricate(:account, username: 'alice') } + + let(:arguments) { ['alice'] } + + it 'regenerates feeds for the account' do + expect { cli.invoke(:build, arguments) }.to output( + a_string_including('OK') + ).to_stdout + end + end + + context 'with invalid username' do + let(:arguments) { ['invalid-username'] } + + it 'displays an error and exits' do + expect { cli.invoke(:build, arguments) }.to output( + a_string_including('No such account') + ).to_stdout.and raise_error(SystemExit) + end + end + end + + describe '#clear' do + before do + allow(redis).to receive(:del).with(key_namespace) + end + + it 'clears the redis `feed:*` namespace' do + expect { cli.invoke(:clear) }.to output( + a_string_including('OK') + ).to_stdout + + expect(redis).to have_received(:del).with(key_namespace).once + end + + def key_namespace + redis.keys('feed:*') + end + end +end diff --git a/spec/lib/mastodon/cli/ip_blocks_spec.rb b/spec/lib/mastodon/cli/ip_blocks_spec.rb new file mode 100644 index 000000000..030d9fcb1 --- /dev/null +++ b/spec/lib/mastodon/cli/ip_blocks_spec.rb @@ -0,0 +1,298 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/ip_blocks' + +describe Mastodon::CLI::IpBlocks do + let(:cli) { described_class.new } + + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end + + describe '#add' do + let(:ip_list) do + [ + '192.0.2.1', + '172.16.0.1', + '192.0.2.0/24', + '172.16.0.0/16', + '10.0.0.0/8', + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'fe80::1', + '::1', + '2001:0db8::/32', + 'fe80::/10', + '::/128', + ] + end + let(:options) { { severity: 'no_access' } } + + shared_examples 'ip address blocking' do + it 'blocks all specified IP addresses' do + cli.invoke(:add, ip_list, options) + + blocked_ip_addresses = IpBlock.where(ip: ip_list).pluck(:ip) + expected_ip_addresses = ip_list.map { |ip| IPAddr.new(ip) } + + expect(blocked_ip_addresses).to match_array(expected_ip_addresses) + end + + it 'sets the severity for all blocked IP addresses' do + cli.invoke(:add, ip_list, options) + + blocked_ips_severity = IpBlock.where(ip: ip_list).pluck(:severity).all?(options[:severity]) + + expect(blocked_ips_severity).to be(true) + end + + it 'displays a success message with a summary' do + expect { cli.invoke(:add, ip_list, options) }.to output( + a_string_including("Added #{ip_list.size}, skipped 0, failed 0") + ).to_stdout + end + end + + context 'with valid IP addresses' do + include_examples 'ip address blocking' + end + + context 'when a specified IP address is already blocked' do + let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: options[:severity]) } + + it 'skips the already blocked IP address' do + allow(IpBlock).to receive(:new).and_call_original + + cli.invoke(:add, ip_list, options) + + expect(IpBlock).to_not have_received(:new).with(ip: ip_list.last) + end + + it 'displays the correct summary' do + expect { cli.invoke(:add, ip_list, options) }.to output( + a_string_including("#{ip_list.last} is already blocked\nAdded #{ip_list.size - 1}, skipped 1, failed 0") + ).to_stdout + end + + context 'with --force option' do + let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: 'no_access') } + let(:options) { { severity: 'sign_up_requires_approval', force: true } } + + it 'overwrites the existing IP block record' do + expect { cli.invoke(:add, ip_list, options) } + .to change { blocked_ip.reload.severity } + .from('no_access') + .to('sign_up_requires_approval') + end + + include_examples 'ip address blocking' + end + end + + context 'when a specified IP address is invalid' do + let(:ip_list) { ['320.15.175.0', '9.5.105.255', '0.0.0.0'] } + + it 'displays the correct summary' do + expect { cli.invoke(:add, ip_list, options) }.to output( + a_string_including("#{ip_list.first} is invalid\nAdded #{ip_list.size - 1}, skipped 0, failed 1") + ).to_stdout + end + end + + context 'with --comment option' do + let(:options) { { severity: 'no_access', comment: 'Spam' } } + + include_examples 'ip address blocking' + end + + context 'with --duration option' do + let(:options) { { severity: 'no_access', duration: 10.days } } + + include_examples 'ip address blocking' + end + + context 'with "sign_up_requires_approval" severity' do + let(:options) { { severity: 'sign_up_requires_approval' } } + + include_examples 'ip address blocking' + end + + context 'with "sign_up_block" severity' do + let(:options) { { severity: 'sign_up_block' } } + + include_examples 'ip address blocking' + end + + context 'when a specified IP address fails to be blocked' do + let(:ip_address) { '127.0.0.1' } + let(:ip_block) { instance_double(IpBlock, ip: ip_address, save: false) } + + before do + allow(IpBlock).to receive(:new).and_return(ip_block) + allow(ip_block).to receive(:severity=) + allow(ip_block).to receive(:expires_in=) + end + + it 'displays an error message' do + expect { cli.invoke(:add, [ip_address], options) } + .to output( + a_string_including("#{ip_address} could not be saved") + ).to_stdout + end + end + + context 'when no IP address is provided' do + it 'exits with an error message' do + expect { cli.add }.to output( + a_string_including('No IP(s) given') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + + describe '#remove' do + context 'when removing exact matches' do + let(:ip_list) do + [ + '192.0.2.1', + '172.16.0.1', + '192.0.2.0/24', + '172.16.0.0/16', + '10.0.0.0/8', + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'fe80::1', + '::1', + '2001:0db8::/32', + 'fe80::/10', + '::/128', + ] + end + + before do + ip_list.each { |ip| IpBlock.create(ip: ip, severity: :no_access) } + end + + it 'removes exact IP blocks' do + cli.invoke(:remove, ip_list) + + expect(IpBlock.where(ip: ip_list)).to_not exist + end + + it 'displays success message with a summary' do + expect { cli.invoke(:remove, ip_list) }.to output( + a_string_including("Removed #{ip_list.size}, skipped 0") + ).to_stdout + end + end + + context 'with --force option' do + let!(:first_ip_range_block) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) } + let!(:second_ip_range_block) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) } + let!(:third_ip_range_block) { IpBlock.create(ip: '172.16.0.0/20', severity: :no_access) } + let(:arguments) { ['192.168.0.5', '10.0.1.50'] } + let(:options) { { force: true } } + + it 'removes blocks for IP ranges that cover given IP(s)' do + cli.invoke(:remove, arguments, options) + + expect(IpBlock.where(id: [first_ip_range_block.id, second_ip_range_block.id])).to_not exist + end + + it 'does not remove other IP ranges' do + cli.invoke(:remove, arguments, options) + + expect(IpBlock.where(id: third_ip_range_block.id)).to exist + end + end + + context 'when a specified IP address is not blocked' do + let(:unblocked_ip) { '192.0.2.1' } + + it 'skips the IP address' do + expect { cli.invoke(:remove, [unblocked_ip]) }.to output( + a_string_including("#{unblocked_ip} is not yet blocked") + ).to_stdout + end + + it 'displays the summary correctly' do + expect { cli.invoke(:remove, [unblocked_ip]) }.to output( + a_string_including('Removed 0, skipped 1') + ).to_stdout + end + end + + context 'when a specified IP address is invalid' do + let(:invalid_ip) { '320.15.175.0' } + + it 'skips the invalid IP address' do + expect { cli.invoke(:remove, [invalid_ip]) }.to output( + a_string_including("#{invalid_ip} is invalid") + ).to_stdout + end + + it 'displays the summary correctly' do + expect { cli.invoke(:remove, [invalid_ip]) }.to output( + a_string_including('Removed 0, skipped 1') + ).to_stdout + end + end + + context 'when no IP address is provided' do + it 'exits with an error message' do + expect { cli.remove }.to output( + a_string_including('No IP(s) given') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + + describe '#export' do + let(:first_ip_range_block) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) } + let(:second_ip_range_block) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) } + let(:third_ip_range_block) { IpBlock.create(ip: '127.0.0.1', severity: :sign_up_block) } + + context 'when --format option is set to "plain"' do + let(:options) { { format: 'plain' } } + + it 'exports blocked IPs with "no_access" severity in plain format' do + expect { cli.invoke(:export, nil, options) }.to output( + a_string_including("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") + ).to_stdout + end + + it 'does not export bloked IPs with different severities' do + expect { cli.invoke(:export, nil, options) }.to_not output( + a_string_including("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}") + ).to_stdout + end + end + + context 'when --format option is set to "nginx"' do + let(:options) { { format: 'nginx' } } + + it 'exports blocked IPs with "no_access" severity in plain format' do + expect { cli.invoke(:export, nil, options) }.to output( + a_string_including("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};") + ).to_stdout + end + + it 'does not export bloked IPs with different severities' do + expect { cli.invoke(:export, nil, options) }.to_not output( + a_string_including("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};") + ).to_stdout + end + end + + context 'when --format option is not provided' do + it 'exports blocked IPs in plain format by default' do + expect { cli.export }.to output( + a_string_including("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}") + ).to_stdout + end + end + end +end diff --git a/spec/lib/mastodon/cli/main_spec.rb b/spec/lib/mastodon/cli/main_spec.rb new file mode 100644 index 000000000..e3709afe3 --- /dev/null +++ b/spec/lib/mastodon/cli/main_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/main' + +describe Mastodon::CLI::Main do + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end + + describe 'version' do + it 'returns the Mastodon version' do + expect { described_class.new.invoke(:version) }.to output( + a_string_including(Mastodon::Version.to_s) + ).to_stdout + end + end +end diff --git a/spec/lib/mastodon/cli/maintenance_spec.rb b/spec/lib/mastodon/cli/maintenance_spec.rb new file mode 100644 index 000000000..12cd9ca8a --- /dev/null +++ b/spec/lib/mastodon/cli/maintenance_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/maintenance' + +describe Mastodon::CLI::Maintenance do + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end +end diff --git a/spec/lib/mastodon/cli/media_spec.rb b/spec/lib/mastodon/cli/media_spec.rb new file mode 100644 index 000000000..29f7d424a --- /dev/null +++ b/spec/lib/mastodon/cli/media_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/media' + +describe Mastodon::CLI::Media do + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end +end diff --git a/spec/lib/mastodon/cli/preview_cards_spec.rb b/spec/lib/mastodon/cli/preview_cards_spec.rb new file mode 100644 index 000000000..b4b018b3b --- /dev/null +++ b/spec/lib/mastodon/cli/preview_cards_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/preview_cards' + +describe Mastodon::CLI::PreviewCards do + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end +end diff --git a/spec/lib/mastodon/cli/search_spec.rb b/spec/lib/mastodon/cli/search_spec.rb new file mode 100644 index 000000000..d5cae5bf4 --- /dev/null +++ b/spec/lib/mastodon/cli/search_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/search' + +describe Mastodon::CLI::Search do + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end +end diff --git a/spec/lib/mastodon/cli/settings_spec.rb b/spec/lib/mastodon/cli/settings_spec.rb new file mode 100644 index 000000000..ae58e74e5 --- /dev/null +++ b/spec/lib/mastodon/cli/settings_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/settings' + +describe Mastodon::CLI::Settings do + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end + + describe 'subcommand "registrations"' do + let(:cli) { Mastodon::CLI::Registrations.new } + + before do + Setting.registrations_mode = nil + end + + describe '#open' do + it 'changes "registrations_mode" to "open"' do + expect { cli.open }.to change(Setting, :registrations_mode).from(nil).to('open') + end + + it 'displays success message' do + expect { cli.open }.to output( + a_string_including('OK') + ).to_stdout + end + end + + describe '#approved' do + it 'changes "registrations_mode" to "approved"' do + expect { cli.approved }.to change(Setting, :registrations_mode).from(nil).to('approved') + end + + it 'displays success message' do + expect { cli.approved }.to output( + a_string_including('OK') + ).to_stdout + end + + context 'with --require-reason' do + before do + cli.options = { require_reason: true } + end + + it 'changes "registrations_mode" to "approved"' do + expect { cli.approved }.to change(Setting, :registrations_mode).from(nil).to('approved') + end + + it 'sets "require_invite_text" to "true"' do + expect { cli.approved }.to change(Setting, :require_invite_text).from(false).to(true) + end + end + end + + describe '#close' do + it 'changes "registrations_mode" to "none"' do + expect { cli.close }.to change(Setting, :registrations_mode).from(nil).to('none') + end + + it 'displays success message' do + expect { cli.close }.to output( + a_string_including('OK') + ).to_stdout + end + end + end +end diff --git a/spec/lib/mastodon/cli/statuses_spec.rb b/spec/lib/mastodon/cli/statuses_spec.rb new file mode 100644 index 000000000..2430a8841 --- /dev/null +++ b/spec/lib/mastodon/cli/statuses_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/statuses' + +describe Mastodon::CLI::Statuses do + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end +end diff --git a/spec/lib/mastodon/cli/upgrade_spec.rb b/spec/lib/mastodon/cli/upgrade_spec.rb new file mode 100644 index 000000000..9e0ab9d06 --- /dev/null +++ b/spec/lib/mastodon/cli/upgrade_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/cli/upgrade' + +describe Mastodon::CLI::Upgrade do + describe '.exit_on_failure?' do + it 'returns true' do + expect(described_class.exit_on_failure?).to be true + end + end +end diff --git a/spec/lib/mastodon/migration_warning_spec.rb b/spec/lib/mastodon/migration_warning_spec.rb new file mode 100644 index 000000000..4adf0837a --- /dev/null +++ b/spec/lib/mastodon/migration_warning_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/migration_warning' + +describe Mastodon::MigrationWarning do + describe 'migration_duration_warning' do + before do + allow(migration).to receive(:valid_environment?).and_return(true) + allow(migration).to receive(:sleep).with(1) + end + + let(:migration) { Class.new(ActiveRecord::Migration[6.1]).extend(described_class) } + + context 'with the default message' do + it 'warns about long migrations' do + expectation = expect { migration.migration_duration_warning } + + expectation.to output(/interrupt this migration/).to_stdout + expectation.to output(/Continuing in 5/).to_stdout + end + end + + context 'with an additional message' do + it 'warns about long migrations' do + expectation = expect { migration.migration_duration_warning('Get ready for it') } + + expectation.to output(/interrupt this migration/).to_stdout + expectation.to output(/Get ready for it/).to_stdout + expectation.to output(/Continuing in 5/).to_stdout + end + end + end +end diff --git a/spec/lib/ostatus/tag_manager_spec.rb b/spec/lib/ostatus/tag_manager_spec.rb index 31195bae2..0e20f26c7 100644 --- a/spec/lib/ostatus/tag_manager_spec.rb +++ b/spec/lib/ostatus/tag_manager_spec.rb @@ -5,65 +5,65 @@ require 'rails_helper' describe OStatus::TagManager do describe '#unique_tag' do it 'returns a unique tag' do - expect(OStatus::TagManager.instance.unique_tag(Time.utc(2000), 12, 'Status')).to eq 'tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status' + expect(described_class.instance.unique_tag(Time.utc(2000), 12, 'Status')).to eq 'tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status' end end describe '#unique_tag_to_local_id' do it 'returns the ID part' do - expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status', 'Status')).to eql '12' + expect(described_class.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Status', 'Status')).to eql '12' end it 'returns nil if it is not local id' do - expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:remote,2000-01-01:objectId=12:objectType=Status', 'Status')).to eq nil + expect(described_class.instance.unique_tag_to_local_id('tag:remote,2000-01-01:objectId=12:objectType=Status', 'Status')).to be_nil end it 'returns nil if it is not expected type' do - expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Block', 'Status')).to eq nil + expect(described_class.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectId=12:objectType=Block', 'Status')).to be_nil end it 'returns nil if it does not have object ID' do - expect(OStatus::TagManager.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectType=Status', 'Status')).to eq nil + expect(described_class.instance.unique_tag_to_local_id('tag:cb6e6126.ngrok.io,2000-01-01:objectType=Status', 'Status')).to be_nil end end describe '#local_id?' do it 'returns true for a local ID' do - expect(OStatus::TagManager.instance.local_id?('tag:cb6e6126.ngrok.io;objectId=12:objectType=Status')).to be true + expect(described_class.instance.local_id?('tag:cb6e6126.ngrok.io;objectId=12:objectType=Status')).to be true end it 'returns false for a foreign ID' do - expect(OStatus::TagManager.instance.local_id?('tag:foreign.tld;objectId=12:objectType=Status')).to be false + expect(described_class.instance.local_id?('tag:foreign.tld;objectId=12:objectType=Status')).to be false end end describe '#uri_for' do - subject { OStatus::TagManager.instance.uri_for(target) } + subject { described_class.instance.uri_for(target) } - context 'comment object' do + context 'with comment object' do let(:target) { Fabricate(:status, created_at: '2000-01-01T00:00:00Z', reply: true) } it 'returns the unique tag for status' do expect(target.object_type).to eq :comment - is_expected.to eq target.uri + expect(subject).to eq target.uri end end - context 'note object' do + context 'with note object' do let(:target) { Fabricate(:status, created_at: '2000-01-01T00:00:00Z', reply: false, thread: nil) } it 'returns the unique tag for status' do expect(target.object_type).to eq :note - is_expected.to eq target.uri + expect(subject).to eq target.uri end end - context 'person object' do + context 'when person object' do let(:target) { Fabricate(:account, username: 'alice') } it 'returns the URL for account' do expect(target.object_type).to eq :person - is_expected.to eq 'https://cb6e6126.ngrok.io/users/alice' + expect(subject).to eq 'https://cb6e6126.ngrok.io/users/alice' end end end diff --git a/spec/lib/plain_text_formatter_spec.rb b/spec/lib/plain_text_formatter_spec.rb index 81e4ae286..80b3c331a 100644 --- a/spec/lib/plain_text_formatter_spec.rb +++ b/spec/lib/plain_text_formatter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe PlainTextFormatter do @@ -8,7 +10,7 @@ RSpec.describe PlainTextFormatter do let(:status) { Fabricate(:status, text: '

a text by a nerd who uses an HTML tag in text

', uri: nil) } it 'returns the raw text' do - is_expected.to eq '

a text by a nerd who uses an HTML tag in text

' + expect(subject).to eq '

a text by a nerd who uses an HTML tag in text

' end end diff --git a/spec/lib/request_pool_spec.rb b/spec/lib/request_pool_spec.rb index 4a144d7c7..f179e6ca9 100644 --- a/spec/lib/request_pool_spec.rb +++ b/spec/lib/request_pool_spec.rb @@ -33,7 +33,7 @@ describe RequestPool do subject - threads = 20.times.map do |i| + threads = Array.new(20) do |_i| Thread.new do 20.times do subject.with('http://example.com') do |http_client| @@ -48,16 +48,25 @@ describe RequestPool do expect(subject.size).to be > 1 end - it 'closes idle connections' do - stub_request(:get, 'http://example.com/').to_return(status: 200, body: 'Hello!') - - subject.with('http://example.com') do |http_client| - http_client.get('/').flush + context 'with an idle connection' do + before do + stub_const('RequestPool::MAX_IDLE_TIME', 1) # Lower idle time limit to 1 seconds + stub_const('RequestPool::REAPER_FREQUENCY', 0.1) # Run reaper every 0.1 seconds + stub_request(:get, 'http://example.com/').to_return(status: 200, body: 'Hello!') end - expect(subject.size).to eq 1 - sleep RequestPool::MAX_IDLE_TIME + 30 + 1 - expect(subject.size).to eq 0 + it 'closes the connections' do + subject.with('http://example.com') do |http_client| + http_client.get('/').flush + end + + expect { reaper_observes_idle_timeout }.to change(subject, :size).from(1).to(0) + end + + def reaper_observes_idle_timeout + # One full idle period and 2 reaper cycles more + sleep RequestPool::MAX_IDLE_TIME + (RequestPool::REAPER_FREQUENCY * 2) + end end end end diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb index 5eccf3201..f0861376b 100644 --- a/spec/lib/request_spec.rb +++ b/spec/lib/request_spec.rb @@ -4,7 +4,7 @@ require 'rails_helper' require 'securerandom' describe Request do - subject { Request.new(:get, 'http://example.com') } + subject { described_class.new(:get, 'http://example.com') } describe '#headers' do it 'returns user agent' do @@ -43,29 +43,29 @@ describe Request do before { stub_request(:get, 'http://example.com') } it 'executes a HTTP request' do - expect { |block| subject.perform &block }.to yield_control + expect { |block| subject.perform(&block) }.to yield_control expect(a_request(:get, 'http://example.com')).to have_been_made.once end it 'executes a HTTP request when the first address is private' do - resolver = double + resolver = instance_double(Resolv::DNS) allow(resolver).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:4860:4860::8844)) allow(resolver).to receive(:timeouts=).and_return(nil) allow(Resolv::DNS).to receive(:open).and_yield(resolver) - expect { |block| subject.perform &block }.to yield_control + expect { |block| subject.perform(&block) }.to yield_control expect(a_request(:get, 'http://example.com')).to have_been_made.once end it 'sets headers' do - expect { |block| subject.perform &block }.to yield_control + expect { |block| subject.perform(&block) }.to yield_control expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made end it 'closes underlying connection' do expect_any_instance_of(HTTP::Client).to receive(:close) - expect { |block| subject.perform &block }.to yield_control + expect { |block| subject.perform(&block) }.to yield_control end it 'returns response which implements body_with_limit' do @@ -83,7 +83,7 @@ describe Request do end it 'raises Mastodon::ValidationError' do - resolver = double + resolver = instance_double(Resolv::DNS) allow(resolver).to receive(:getaddresses).with('example.com').and_return(%w(0.0.0.0 2001:db8::face)) allow(resolver).to receive(:timeouts=).and_return(nil) @@ -97,12 +97,12 @@ describe Request do describe "response's body_with_limit method" do it 'rejects body more than 1 megabyte by default' do stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes)) - expect { subject.perform { |response| response.body_with_limit } }.to raise_error Mastodon::LengthValidationError + expect { subject.perform(&:body_with_limit) }.to raise_error Mastodon::LengthValidationError end it 'accepts body less than 1 megabyte by default' do stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.kilobytes)) - expect { subject.perform { |response| response.body_with_limit } }.not_to raise_error + expect { subject.perform(&:body_with_limit) }.to_not raise_error end it 'rejects body by given size' do @@ -112,12 +112,17 @@ describe Request do it 'rejects too large chunked body' do stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Transfer-Encoding' => 'chunked' }) - expect { subject.perform { |response| response.body_with_limit } }.to raise_error Mastodon::LengthValidationError + expect { subject.perform(&:body_with_limit) }.to raise_error Mastodon::LengthValidationError end it 'rejects too large monolithic body' do stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Content-Length' => 2.megabytes }) - expect { subject.perform { |response| response.body_with_limit } }.to raise_error Mastodon::LengthValidationError + expect { subject.perform(&:body_with_limit) }.to raise_error Mastodon::LengthValidationError + end + + it 'truncates large monolithic body' do + stub_request(:any, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes), headers: { 'Content-Length' => 2.megabytes }) + expect(subject.perform { |response| response.truncated_body.bytesize }).to be < 2.megabytes end it 'uses binary encoding if Content-Type does not tell encoding' do diff --git a/spec/lib/sanitize_config_spec.rb b/spec/lib/sanitize_config_spec.rb index 747d81158..73636934e 100644 --- a/spec/lib/sanitize_config_spec.rb +++ b/spec/lib/sanitize_config_spec.rb @@ -6,24 +6,12 @@ describe Sanitize::Config do describe '::MASTODON_STRICT' do subject { Sanitize::Config::MASTODON_STRICT } - it 'converts h1 to p' do - expect(Sanitize.fragment('

Foo

', subject)).to eq '

Foo

' + it 'keeps ul' do + expect(Sanitize.fragment('

Check out:

', subject)).to eq '

Check out:

' end - it 'converts ul to p' do - expect(Sanitize.fragment('

Check out:

', subject)).to eq '

Check out:

Foo
Bar

' - end - - it 'converts p inside ul' do - expect(Sanitize.fragment('', subject)).to eq '

Foo
Bar
Baz

' - end - - it 'converts ul inside ul' do - expect(Sanitize.fragment('', subject)).to eq '

Foo
Bar
Baz

' - end - - it 'keep links in lists' do - expect(Sanitize.fragment('

Check out:

', subject)).to eq '

Check out:

joinmastodon.org
Bar

' + it 'keeps start and reversed attributes of ol' do + expect(Sanitize.fragment('

Check out:

  1. Foo
  2. Bar
', subject)).to eq '

Check out:

  1. Foo
  2. Bar
' end it 'removes a without href' do @@ -38,8 +26,28 @@ describe Sanitize::Config do expect(Sanitize.fragment('Test', subject)).to eq 'Test' end + it 'does not re-interpret HTML when removing unsupported links' do + expect(Sanitize.fragment('Test<a href="https://example.com">test</a>', subject)).to eq 'Test<a href="https://example.com">test</a>' + end + it 'keeps a with href' do expect(Sanitize.fragment('Test', subject)).to eq 'Test' end + + it 'keeps a with translate="no"' do + expect(Sanitize.fragment('Test', subject)).to eq 'Test' + end + + it 'removes "translate" attribute with invalid value' do + expect(Sanitize.fragment('Test', subject)).to eq 'Test' + end + + it 'removes a with unparsable href' do + expect(Sanitize.fragment('Test', subject)).to eq 'Test' + end + + it 'keeps a with supported scheme and no host' do + expect(Sanitize.fragment('Test', subject)).to eq 'Test' + end end end diff --git a/spec/lib/scope_transformer_spec.rb b/spec/lib/scope_transformer_spec.rb index e5a992144..8a9c7cf96 100644 --- a/spec/lib/scope_transformer_spec.rb +++ b/spec/lib/scope_transformer_spec.rb @@ -20,67 +20,67 @@ describe ScopeTransformer do end end - context 'for scope "read"' do + context 'with scope "read"' do let(:input) { 'read' } it_behaves_like 'a scope', nil, 'all', 'read' end - context 'for scope "write"' do + context 'with scope "write"' do let(:input) { 'write' } it_behaves_like 'a scope', nil, 'all', 'write' end - context 'for scope "follow"' do + context 'with scope "follow"' do let(:input) { 'follow' } it_behaves_like 'a scope', nil, 'follow', 'read/write' end - context 'for scope "crypto"' do + context 'with scope "crypto"' do let(:input) { 'crypto' } it_behaves_like 'a scope', nil, 'crypto', 'read/write' end - context 'for scope "push"' do + context 'with scope "push"' do let(:input) { 'push' } it_behaves_like 'a scope', nil, 'push', 'read/write' end - context 'for scope "admin:read"' do + context 'with scope "admin:read"' do let(:input) { 'admin:read' } it_behaves_like 'a scope', 'admin', 'all', 'read' end - context 'for scope "admin:write"' do + context 'with scope "admin:write"' do let(:input) { 'admin:write' } it_behaves_like 'a scope', 'admin', 'all', 'write' end - context 'for scope "admin:read:accounts"' do + context 'with scope "admin:read:accounts"' do let(:input) { 'admin:read:accounts' } it_behaves_like 'a scope', 'admin', 'accounts', 'read' end - context 'for scope "admin:write:accounts"' do + context 'with scope "admin:write:accounts"' do let(:input) { 'admin:write:accounts' } it_behaves_like 'a scope', 'admin', 'accounts', 'write' end - context 'for scope "read:accounts"' do + context 'with scope "read:accounts"' do let(:input) { 'read:accounts' } it_behaves_like 'a scope', nil, 'accounts', 'read' end - context 'for scope "write:accounts"' do + context 'with scope "write:accounts"' do let(:input) { 'write:accounts' } it_behaves_like 'a scope', nil, 'accounts', 'write' diff --git a/spec/lib/search_query_parser_spec.rb b/spec/lib/search_query_parser_spec.rb new file mode 100644 index 000000000..66b0e8f9e --- /dev/null +++ b/spec/lib/search_query_parser_spec.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'parslet/rig/rspec' + +describe SearchQueryParser do + let(:parser) { described_class.new } + + context 'with term' do + it 'consumes "hello"' do + expect(parser.term).to parse('hello') + end + end + + context 'with prefix' do + it 'consumes "foo:"' do + expect(parser.prefix).to parse('foo:') + end + end + + context 'with operator' do + it 'consumes "+"' do + expect(parser.operator).to parse('+') + end + + it 'consumes "-"' do + expect(parser.operator).to parse('-') + end + end + + context 'with shortcode' do + it 'consumes ":foo:"' do + expect(parser.shortcode).to parse(':foo:') + end + end + + context 'with phrase' do + it 'consumes "hello world"' do + expect(parser.phrase).to parse('"hello world"') + end + end + + context 'with clause' do + it 'consumes "foo"' do + expect(parser.clause).to parse('foo') + end + + it 'consumes "-foo"' do + expect(parser.clause).to parse('-foo') + end + + it 'consumes "foo:bar"' do + expect(parser.clause).to parse('foo:bar') + end + + it 'consumes "-foo:bar"' do + expect(parser.clause).to parse('-foo:bar') + end + + it 'consumes \'foo:"hello world"\'' do + expect(parser.clause).to parse('foo:"hello world"') + end + + it 'consumes \'-foo:"hello world"\'' do + expect(parser.clause).to parse('-foo:"hello world"') + end + + it 'consumes "foo:"' do + expect(parser.clause).to parse('foo:') + end + + it 'consumes \'"\'' do + expect(parser.clause).to parse('"') + end + end + + context 'with query' do + it 'consumes "hello -world"' do + expect(parser.query).to parse('hello -world') + end + + it 'consumes \'foo "hello world"\'' do + expect(parser.query).to parse('foo "hello world"') + end + + it 'consumes "foo:bar hello"' do + expect(parser.query).to parse('foo:bar hello') + end + + it 'consumes \'"hello" world "\'' do + expect(parser.query).to parse('"hello" world "') + end + + it 'consumes "foo:bar bar: hello"' do + expect(parser.query).to parse('foo:bar bar: hello') + end + end +end diff --git a/spec/lib/search_query_transformer_spec.rb b/spec/lib/search_query_transformer_spec.rb new file mode 100644 index 000000000..5817e3d1d --- /dev/null +++ b/spec/lib/search_query_transformer_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe SearchQueryTransformer do + subject { described_class.new.apply(parser, current_account: account) } + + let(:account) { Fabricate(:account) } + let(:parser) { SearchQueryParser.new.parse(query) } + + context 'with "hello world"' do + let(:query) { 'hello world' } + + it 'transforms clauses' do + expect(subject.send(:must_clauses).map(&:term)).to match_array %w(hello world) + expect(subject.send(:must_not_clauses)).to be_empty + expect(subject.send(:filter_clauses)).to be_empty + end + end + + context 'with "hello -world"' do + let(:query) { 'hello -world' } + + it 'transforms clauses' do + expect(subject.send(:must_clauses).map(&:term)).to match_array %w(hello) + expect(subject.send(:must_not_clauses).map(&:term)).to match_array %w(world) + expect(subject.send(:filter_clauses)).to be_empty + end + end + + context 'with "hello is:reply"' do + let(:query) { 'hello is:reply' } + + it 'transforms clauses' do + expect(subject.send(:must_clauses).map(&:term)).to match_array %w(hello) + expect(subject.send(:must_not_clauses)).to be_empty + expect(subject.send(:filter_clauses).map(&:term)).to match_array %w(reply) + end + end + + context 'with "foo: bar"' do + let(:query) { 'foo: bar' } + + it 'transforms clauses' do + expect(subject.send(:must_clauses).map(&:term)).to match_array %w(foo bar) + expect(subject.send(:must_not_clauses)).to be_empty + expect(subject.send(:filter_clauses)).to be_empty + end + end + + context 'with "foo:bar"' do + let(:query) { 'foo:bar' } + + it 'transforms clauses' do + expect(subject.send(:must_clauses).map(&:term)).to contain_exactly('foo bar') + expect(subject.send(:must_not_clauses)).to be_empty + expect(subject.send(:filter_clauses)).to be_empty + end + end + + context 'with \'"hello world"\'' do + let(:query) { '"hello world"' } + + it 'transforms clauses' do + expect(subject.send(:must_clauses).map(&:phrase)).to contain_exactly('hello world') + expect(subject.send(:must_not_clauses)).to be_empty + expect(subject.send(:filter_clauses)).to be_empty + end + end + + context 'with \'before:"2022-01-01 23:00"\'' do + let(:query) { 'before:"2022-01-01 23:00"' } + + it 'transforms clauses' do + expect(subject.send(:must_clauses)).to be_empty + expect(subject.send(:must_not_clauses)).to be_empty + expect(subject.send(:filter_clauses).map(&:term)).to contain_exactly(lt: '2022-01-01 23:00', time_zone: 'UTC') + end + end +end diff --git a/spec/lib/settings/extend_spec.rb b/spec/lib/settings/extend_spec.rb deleted file mode 100644 index 83ced4230..000000000 --- a/spec/lib/settings/extend_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Settings::Extend do - class User - include Settings::Extend - end - - describe '#settings' do - it 'sets @settings as an instance of Settings::ScopedSettings' do - user = Fabricate(:user) - expect(user.settings).to be_kind_of Settings::ScopedSettings - end - end -end diff --git a/spec/lib/settings/scoped_settings_spec.rb b/spec/lib/settings/scoped_settings_spec.rb deleted file mode 100644 index 3faf3f41e..000000000 --- a/spec/lib/settings/scoped_settings_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Settings::ScopedSettings do - let(:object) { Fabricate(:user) } - let(:scoped_setting) { described_class.new(object) } - let(:val) { 'whatever' } - let(:methods) { %i(auto_play_gif expand_usernames default_sensitive unfollow_modal boost_modal delete_modal reduce_motion system_font_ui noindex theme) } - - describe '.initialize' do - it 'sets @object' do - scoped_setting = described_class.new(object) - expect(scoped_setting.instance_variable_get(:@object)).to be object - end - end - - describe '#method_missing' do - it 'sets scoped_setting.method_name = val' do - methods.each do |key| - scoped_setting.send("#{key}=", val) - expect(scoped_setting.send(key)).to eq val - end - end - end - - describe '#[]= and #[]' do - it 'sets [key] = val' do - methods.each do |key| - scoped_setting[key] = val - expect(scoped_setting[key]).to eq val - end - end - end -end diff --git a/spec/lib/status_cache_hydrator_spec.rb b/spec/lib/status_cache_hydrator_spec.rb index 5c78de711..5b80ccb97 100644 --- a/spec/lib/status_cache_hydrator_spec.rb +++ b/spec/lib/status_cache_hydrator_spec.rb @@ -44,7 +44,7 @@ describe StatusCacheHydrator do let(:reblog) { Fabricate(:status) } let(:status) { Fabricate(:status, reblog: reblog) } - context 'that has been favourited' do + context 'when it has been favourited' do before do FavouriteService.new.call(account, reblog) end @@ -54,7 +54,7 @@ describe StatusCacheHydrator do end end - context 'that has been reblogged' do + context 'when it has been reblogged' do before do ReblogService.new.call(account, reblog) end @@ -64,7 +64,7 @@ describe StatusCacheHydrator do end end - context 'that has been pinned' do + context 'when it has been pinned' do let(:reblog) { Fabricate(:status, account: account) } before do @@ -76,7 +76,7 @@ describe StatusCacheHydrator do end end - context 'that has been followed tags' do + context 'when it has been followed tags' do let(:followed_tag) { Fabricate(:tag) } before do @@ -90,7 +90,7 @@ describe StatusCacheHydrator do end end - context 'that has a poll authored by the user' do + context 'when it has a poll authored by the user' do let(:poll) { Fabricate(:poll, account: account) } let(:reblog) { Fabricate(:status, poll: poll, account: account) } @@ -99,7 +99,7 @@ describe StatusCacheHydrator do end end - context 'that has been voted in' do + context 'when it has been voted in' do let(:poll) { Fabricate(:poll, options: %w(Yellow Blue)) } let(:reblog) { Fabricate(:status, poll: poll) } @@ -112,7 +112,7 @@ describe StatusCacheHydrator do end end - context 'that matches account filters' do + context 'when it matches account filters' do let(:reblog) { Fabricate(:status, text: 'this toot is about that banned word') } before do diff --git a/spec/lib/status_filter_spec.rb b/spec/lib/status_filter_spec.rb index a851014d9..c994ad419 100644 --- a/spec/lib/status_filter_spec.rb +++ b/spec/lib/status_filter_spec.rb @@ -7,10 +7,10 @@ describe StatusFilter do let(:status) { Fabricate(:status) } context 'without an account' do - subject { described_class.new(status, nil) } + subject(:filter) { described_class.new(status, nil) } context 'when there are no connections' do - it { is_expected.not_to be_filtered } + it { is_expected.to_not be_filtered } end context 'when status account is silenced' do @@ -22,20 +22,21 @@ describe StatusFilter do end context 'when status policy does not allow show' do - before do - expect_any_instance_of(StatusPolicy).to receive(:show?).and_return(false) - end + it 'filters the status' do + allow_any_instance_of(StatusPolicy).to receive(:show?).and_return(false) - it { is_expected.to be_filtered } + expect(filter).to be_filtered + end end end context 'with real account' do + subject(:filter) { described_class.new(status, account) } + let(:account) { Fabricate(:account) } - subject { described_class.new(status, account) } context 'when there are no connections' do - it { is_expected.not_to be_filtered } + it { is_expected.to_not be_filtered } end context 'when status account is blocked' do @@ -72,11 +73,11 @@ describe StatusFilter do end context 'when status policy does not allow show' do - before do - expect_any_instance_of(StatusPolicy).to receive(:show?).and_return(false) - end + it 'filters the status' do + allow_any_instance_of(StatusPolicy).to receive(:show?).and_return(false) - it { is_expected.to be_filtered } + expect(filter).to be_filtered + end end end end diff --git a/spec/lib/status_finder_spec.rb b/spec/lib/status_finder_spec.rb index 61483f4bf..53f5039af 100644 --- a/spec/lib/status_finder_spec.rb +++ b/spec/lib/status_finder_spec.rb @@ -18,10 +18,13 @@ describe StatusFinder do it 'raises an error if action is not :show' do recognized = Rails.application.routes.recognize_path(url) - expect(recognized).to receive(:[]).with(:action).and_return(:create) - expect(Rails.application.routes).to receive(:recognize_path).with(url).and_return(recognized) + allow(recognized).to receive(:[]).with(:action).and_return(:create) + allow(Rails.application.routes).to receive(:recognize_path).with(url).and_return(recognized) expect { subject.status }.to raise_error(ActiveRecord::RecordNotFound) + + expect(Rails.application.routes).to have_received(:recognize_path) + expect(recognized).to have_received(:[]) end end diff --git a/spec/lib/status_reach_finder_spec.rb b/spec/lib/status_reach_finder_spec.rb index f0c22b165..7181717dc 100644 --- a/spec/lib/status_reach_finder_spec.rb +++ b/spec/lib/status_reach_finder_spec.rb @@ -4,14 +4,14 @@ require 'rails_helper' describe StatusReachFinder do describe '#inboxes' do - context 'for a local status' do + context 'with a local status' do + subject { described_class.new(status) } + let(:parent_status) { nil } let(:visibility) { :public } let(:alice) { Fabricate(:account, username: 'alice') } let(:status) { Fabricate(:status, account: alice, thread: parent_status, visibility: visibility) } - subject { described_class.new(status) } - context 'when it contains mentions of remote accounts' do let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') } @@ -71,10 +71,8 @@ describe StatusReachFinder do bob.statuses.create!(thread: status, text: 'Hoge') end - context do - it 'includes the inbox of the replier' do - expect(subject.inboxes).to include 'https://foo.bar/inbox' - end + it 'includes the inbox of the replier' do + expect(subject.inboxes).to include 'https://foo.bar/inbox' end context 'when status is not public' do @@ -90,10 +88,8 @@ describe StatusReachFinder do let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') } let(:parent_status) { Fabricate(:status, account: bob) } - context do - it 'includes the inbox of the replied-to account' do - expect(subject.inboxes).to include 'https://foo.bar/inbox' - end + it 'includes the inbox of the replied-to account' do + expect(subject.inboxes).to include 'https://foo.bar/inbox' end context 'when status is not public and replied-to account is not mentioned' do diff --git a/spec/lib/suspicious_sign_in_detector_spec.rb b/spec/lib/suspicious_sign_in_detector_spec.rb index 101a18aa0..9e64aff08 100644 --- a/spec/lib/suspicious_sign_in_detector_spec.rb +++ b/spec/lib/suspicious_sign_in_detector_spec.rb @@ -1,13 +1,15 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe SuspiciousSignInDetector do describe '#suspicious?' do - let(:user) { Fabricate(:user, current_sign_in_at: 1.day.ago) } - let(:request) { double(remote_ip: remote_ip) } - let(:remote_ip) { nil } - subject { described_class.new(user).suspicious?(request) } + let(:user) { Fabricate(:user, current_sign_in_at: 1.day.ago) } + let(:request) { instance_double(ActionDispatch::Request, remote_ip: remote_ip) } + let(:remote_ip) { nil } + context 'when user has 2FA enabled' do before do user.update!(otp_required_for_login: true) diff --git a/spec/lib/tag_manager_spec.rb b/spec/lib/tag_manager_spec.rb index cd9fb936c..38203a55f 100644 --- a/spec/lib/tag_manager_spec.rb +++ b/spec/lib/tag_manager_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe TagManager do @@ -14,15 +16,15 @@ RSpec.describe TagManager do end it 'returns true for nil' do - expect(TagManager.instance.local_domain?(nil)).to eq true + expect(described_class.instance.local_domain?(nil)).to be true end it 'returns true if the slash-stripped string equals to local domain' do - expect(TagManager.instance.local_domain?('DoMaIn.Example.com/')).to eq true + expect(described_class.instance.local_domain?('DoMaIn.Example.com/')).to be true end it 'returns false for irrelevant string' do - expect(TagManager.instance.local_domain?('DoMaIn.Example.com!')).to eq false + expect(described_class.instance.local_domain?('DoMaIn.Example.com!')).to be false end end @@ -39,25 +41,25 @@ RSpec.describe TagManager do end it 'returns true for nil' do - expect(TagManager.instance.web_domain?(nil)).to eq true + expect(described_class.instance.web_domain?(nil)).to be true end it 'returns true if the slash-stripped string equals to web domain' do - expect(TagManager.instance.web_domain?('DoMaIn.Example.com/')).to eq true + expect(described_class.instance.web_domain?('DoMaIn.Example.com/')).to be true end it 'returns false for string with irrelevant characters' do - expect(TagManager.instance.web_domain?('DoMaIn.Example.com!')).to eq false + expect(described_class.instance.web_domain?('DoMaIn.Example.com!')).to be false end end describe '#normalize_domain' do it 'returns nil if the given parameter is nil' do - expect(TagManager.instance.normalize_domain(nil)).to eq nil + expect(described_class.instance.normalize_domain(nil)).to be_nil end it 'returns normalized domain' do - expect(TagManager.instance.normalize_domain('DoMaIn.Example.com/')).to eq 'domain.example.com' + expect(described_class.instance.normalize_domain('DoMaIn.Example.com/')).to eq 'domain.example.com' end end @@ -70,17 +72,17 @@ RSpec.describe TagManager do it 'returns true if the normalized string with port is local URL' do Rails.configuration.x.web_domain = 'domain.example.com:42' - expect(TagManager.instance.local_url?('https://DoMaIn.Example.com:42/')).to eq true + expect(described_class.instance.local_url?('https://DoMaIn.Example.com:42/')).to be true end it 'returns true if the normalized string without port is local URL' do Rails.configuration.x.web_domain = 'domain.example.com' - expect(TagManager.instance.local_url?('https://DoMaIn.Example.com/')).to eq true + expect(described_class.instance.local_url?('https://DoMaIn.Example.com/')).to be true end it 'returns false for string with irrelevant characters' do Rails.configuration.x.web_domain = 'domain.example.com' - expect(TagManager.instance.local_url?('https://domain.example.net/')).to eq false + expect(described_class.instance.local_url?('https://domain.example.net/')).to be false end end end diff --git a/spec/lib/text_formatter_spec.rb b/spec/lib/text_formatter_spec.rb index 52a9d2498..8b922c018 100644 --- a/spec/lib/text_formatter_spec.rb +++ b/spec/lib/text_formatter_spec.rb @@ -1,127 +1,129 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe TextFormatter do describe '#to_s' do - let(:preloaded_accounts) { nil } - subject { described_class.new(text, preloaded_accounts: preloaded_accounts).to_s } - context 'given text containing plain text' do + let(:preloaded_accounts) { nil } + + context 'when given text containing plain text' do let(:text) { 'text' } it 'paragraphizes the text' do - is_expected.to eq '

text

' + expect(subject).to eq '

text

' end end - context 'given text containing line feeds' do + context 'when given text containing line feeds' do let(:text) { "line\nfeed" } it 'removes line feeds' do - is_expected.not_to include "\n" + expect(subject).to_not include "\n" end end - context 'given text containing linkable mentions' do + context 'when given text containing linkable mentions' do let(:preloaded_accounts) { [Fabricate(:account, username: 'alice')] } let(:text) { '@alice' } it 'creates a mention link' do - is_expected.to include '@alice' + expect(subject).to include '@alice' end end - context 'given text containing unlinkable mentions' do + context 'when given text containing unlinkable mentions' do let(:preloaded_accounts) { [] } let(:text) { '@alice' } it 'does not create a mention link' do - is_expected.to include '@alice' + expect(subject).to include '@alice' end end - context 'given a stand-alone medium URL' do + context 'when given a stand-alone medium URL' do let(:text) { 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4' } it 'matches the full URL' do - is_expected.to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"' + expect(subject).to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"' end end - context 'given a stand-alone google URL' do + context 'when given a stand-alone google URL' do let(:text) { 'http://google.com' } it 'matches the full URL' do - is_expected.to include 'href="http://google.com"' + expect(subject).to include 'href="http://google.com"' end end - context 'given a stand-alone URL with a newer TLD' do + context 'when given a stand-alone URL with a newer TLD' do let(:text) { 'http://example.gay' } it 'matches the full URL' do - is_expected.to include 'href="http://example.gay"' + expect(subject).to include 'href="http://example.gay"' end end - context 'given a stand-alone IDN URL' do + context 'when given a stand-alone IDN URL' do let(:text) { 'https://nic.みんな/' } it 'matches the full URL' do - is_expected.to include 'href="https://nic.みんな/"' + expect(subject).to include 'href="https://nic.みんな/"' end it 'has display URL' do - is_expected.to include 'nic.みんな/' + expect(subject).to include 'nic.みんな/' end end - context 'given a URL with a trailing period' do + context 'when given a URL with a trailing period' do let(:text) { 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ' } it 'matches the full URL but not the period' do - is_expected.to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"' + expect(subject).to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"' end end - context 'given a URL enclosed with parentheses' do + context 'when given a URL enclosed with parentheses' do let(:text) { '(http://google.com/)' } it 'matches the full URL but not the parentheses' do - is_expected.to include 'href="http://google.com/"' + expect(subject).to include 'href="http://google.com/"' end end - context 'given a URL with a trailing exclamation point' do + context 'when given a URL with a trailing exclamation point' do let(:text) { 'http://www.google.com!' } it 'matches the full URL but not the exclamation point' do - is_expected.to include 'href="http://www.google.com"' + expect(subject).to include 'href="http://www.google.com"' end end - context 'given a URL with a trailing single quote' do + context 'when given a URL with a trailing single quote' do let(:text) { "http://www.google.com'" } it 'matches the full URL but not the single quote' do - is_expected.to include 'href="http://www.google.com"' + expect(subject).to include 'href="http://www.google.com"' end end - context 'given a URL with a trailing angle bracket' do + context 'when given a URL with a trailing angle bracket' do let(:text) { 'http://www.google.com>' } it 'matches the full URL but not the angle bracket' do - is_expected.to include 'href="http://www.google.com"' + expect(subject).to include 'href="http://www.google.com"' end end - context 'given a URL with a query string' do + context 'when given a URL with a query string' do context 'with escaped unicode character' do let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' } it 'matches the full URL' do - is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink"' + expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink"' end end @@ -129,7 +131,7 @@ RSpec.describe TextFormatter do let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓&q=autolink' } it 'matches the full URL' do - is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&q=autolink"' + expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&q=autolink"' end end @@ -137,7 +139,7 @@ RSpec.describe TextFormatter do let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓' } it 'matches the full URL' do - is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"' + expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"' end end @@ -145,168 +147,168 @@ RSpec.describe TextFormatter do let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink' } it 'preserves escaped unicode characters' do - is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink"' + expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink"' end end end - context 'given a URL with parentheses in it' do + context 'when given a URL with parentheses in it' do let(:text) { 'https://en.wikipedia.org/wiki/Diaspora_(software)' } it 'matches the full URL' do - is_expected.to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"' + expect(subject).to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"' end end - context 'given a URL in quotation marks' do + context 'when given a URL in quotation marks' do let(:text) { '"https://example.com/"' } it 'does not match the quotation marks' do - is_expected.to include 'href="https://example.com/"' + expect(subject).to include 'href="https://example.com/"' end end - context 'given a URL in angle brackets' do + context 'when given a URL in angle brackets' do let(:text) { '' } it 'does not match the angle brackets' do - is_expected.to include 'href="https://example.com/"' + expect(subject).to include 'href="https://example.com/"' end end - context 'given a URL with Japanese path string' do + context 'when given a URL with Japanese path string' do let(:text) { 'https://ja.wikipedia.org/wiki/日本' } it 'matches the full URL' do - is_expected.to include 'href="https://ja.wikipedia.org/wiki/日本"' + expect(subject).to include 'href="https://ja.wikipedia.org/wiki/日本"' end end - context 'given a URL with Korean path string' do + context 'when given a URL with Korean path string' do let(:text) { 'https://ko.wikipedia.org/wiki/대한민국' } it 'matches the full URL' do - is_expected.to include 'href="https://ko.wikipedia.org/wiki/대한민국"' + expect(subject).to include 'href="https://ko.wikipedia.org/wiki/대한민국"' end end - context 'given a URL with a full-width space' do + context 'when given a URL with a full-width space' do let(:text) { 'https://example.com/ abc123' } it 'does not match the full-width space' do - is_expected.to include 'href="https://example.com/"' + expect(subject).to include 'href="https://example.com/"' end end - context 'given a URL in Japanese quotation marks' do + context 'when given a URL in Japanese quotation marks' do let(:text) { '「[https://example.org/」' } it 'does not match the quotation marks' do - is_expected.to include 'href="https://example.org/"' + expect(subject).to include 'href="https://example.org/"' end end - context 'given a URL with Simplified Chinese path string' do + context 'when given a URL with Simplified Chinese path string' do let(:text) { 'https://baike.baidu.com/item/中华人民共和国' } it 'matches the full URL' do - is_expected.to include 'href="https://baike.baidu.com/item/中华人民共和国"' + expect(subject).to include 'href="https://baike.baidu.com/item/中华人民共和国"' end end - context 'given a URL with Traditional Chinese path string' do + context 'when given a URL with Traditional Chinese path string' do let(:text) { 'https://zh.wikipedia.org/wiki/臺灣' } it 'matches the full URL' do - is_expected.to include 'href="https://zh.wikipedia.org/wiki/臺灣"' + expect(subject).to include 'href="https://zh.wikipedia.org/wiki/臺灣"' end end - context 'given a URL containing unsafe code (XSS attack, visible part)' do - let(:text) { %q{http://example.com/bb} } + context 'when given a URL containing unsafe code (XSS attack, visible part)' do + let(:text) { 'http://example.com/bb' } it 'does not include the HTML in the URL' do - is_expected.to include '"http://example.com/b"' + expect(subject).to include '"http://example.com/b"' end it 'escapes the HTML' do - is_expected.to include '<del>b</del>' + expect(subject).to include '<del>b</del>' end end - context 'given a URL containing unsafe code (XSS attack, invisible part)' do - let(:text) { %q{http://example.com/blahblahblahblah/a} } + context 'when given a URL containing unsafe code (XSS attack, invisible part)' do + let(:text) { 'http://example.com/blahblahblahblah/a' } it 'does not include the HTML in the URL' do - is_expected.to include '"http://example.com/blahblahblahblah/a"' + expect(subject).to include '"http://example.com/blahblahblahblah/a"' end it 'escapes the HTML' do - is_expected.to include '<script>alert("Hello")</script>' + expect(subject).to include '<script>alert("Hello")</script>' end end - context 'given text containing HTML code (script tag)' do + context 'when given text containing HTML code (script tag)' do let(:text) { '' } it 'escapes the HTML' do - is_expected.to include '

<script>alert("Hello")</script>

' + expect(subject).to include '

<script>alert("Hello")</script>

' end end - context 'given text containing HTML (XSS attack)' do + context 'when given text containing HTML (XSS attack)' do let(:text) { %q{} } it 'escapes the HTML' do - is_expected.to include '

<img src="javascript:alert('XSS');">

' + expect(subject).to include '

<img src="javascript:alert('XSS');">

' end end - context 'given an invalid URL' do + context 'when given an invalid URL' do let(:text) { 'http://www\.google\.com' } it 'outputs the raw URL' do - is_expected.to eq '

http://www\.google\.com

' + expect(subject).to eq '

http://www\.google\.com

' end end - context 'given text containing a hashtag' do + context 'when given text containing a hashtag' do let(:text) { '#hashtag' } it 'creates a hashtag link' do - is_expected.to include '/tags/hashtag" class="mention hashtag" rel="tag">#hashtag' + expect(subject).to include '/tags/hashtag" class="mention hashtag" rel="tag">#hashtag' end end - context 'given text containing a hashtag with Unicode chars' do + context 'when given text containing a hashtag with Unicode chars' do let(:text) { '#hashtagタグ' } it 'creates a hashtag link' do - is_expected.to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#hashtagタグ' + expect(subject).to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#hashtagタグ' end end - context 'given text with a stand-alone xmpp: URI' do + context 'when given text with a stand-alone xmpp: URI' do let(:text) { 'xmpp:user@instance.com' } it 'matches the full URI' do - is_expected.to include 'href="xmpp:user@instance.com"' + expect(subject).to include 'href="xmpp:user@instance.com"' end end - context 'given text with an xmpp: URI with a query-string' do + context 'when given text with an xmpp: URI with a query-string' do let(:text) { 'please join xmpp:muc@instance.com?join right now' } it 'matches the full URI' do - is_expected.to include 'href="xmpp:muc@instance.com?join"' + expect(subject).to include 'href="xmpp:muc@instance.com?join"' end end - context 'given text containing a magnet: URI' do + context 'when given text containing a magnet: URI' do let(:text) { 'wikipedia gives this example of a magnet uri: magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a' } it 'matches the full URI' do - is_expected.to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"' + expect(subject).to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"' end end end diff --git a/spec/lib/translation_service/deepl_spec.rb b/spec/lib/translation_service/deepl_spec.rb new file mode 100644 index 000000000..5a1d0f094 --- /dev/null +++ b/spec/lib/translation_service/deepl_spec.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe TranslationService::DeepL do + subject(:service) { described_class.new(plan, 'my-api-key') } + + let(:plan) { 'advanced' } + + before do + stub_request(:get, 'https://api.deepl.com/v2/languages?type=source').to_return( + body: '[{"language":"EN","name":"English"},{"language":"UK","name":"Ukrainian"}]' + ) + stub_request(:get, 'https://api.deepl.com/v2/languages?type=target').to_return( + body: '[{"language":"EN-GB","name":"English (British)"},{"language":"ZH","name":"Chinese"}]' + ) + end + + describe '#translate' do + it 'returns translation with specified source language' do + stub_request(:post, 'https://api.deepl.com/v2/translate') + .with(body: 'text=Hasta+la+vista&source_lang=ES&target_lang=en&tag_handling=html') + .to_return(body: '{"translations":[{"detected_source_language":"ES","text":"See you soon"}]}') + + translations = service.translate(['Hasta la vista'], 'es', 'en') + expect(translations.size).to eq 1 + + translation = translations.first + expect(translation.detected_source_language).to eq 'es' + expect(translation.provider).to eq 'DeepL.com' + expect(translation.text).to eq 'See you soon' + end + + it 'returns translation with auto-detected source language' do + stub_request(:post, 'https://api.deepl.com/v2/translate') + .with(body: 'text=Guten+Tag&source_lang&target_lang=en&tag_handling=html') + .to_return(body: '{"translations":[{"detected_source_language":"DE","text":"Good morning"}]}') + + translations = service.translate(['Guten Tag'], nil, 'en') + expect(translations.size).to eq 1 + + translation = translations.first + expect(translation.detected_source_language).to eq 'de' + expect(translation.provider).to eq 'DeepL.com' + expect(translation.text).to eq 'Good morning' + end + + it 'returns translation of multiple texts' do + stub_request(:post, 'https://api.deepl.com/v2/translate') + .with(body: 'text=Guten+Morgen&text=Gute+Nacht&source_lang=DE&target_lang=en&tag_handling=html') + .to_return(body: '{"translations":[{"detected_source_language":"DE","text":"Good morning"},{"detected_source_language":"DE","text":"Good night"}]}') + + translations = service.translate(['Guten Morgen', 'Gute Nacht'], 'de', 'en') + expect(translations.size).to eq 2 + + expect(translations.first.text).to eq 'Good morning' + expect(translations.last.text).to eq 'Good night' + end + end + + describe '#languages' do + it 'returns source languages' do + expect(service.languages.keys).to eq [nil, 'en', 'uk'] + end + + it 'returns target languages for each source language' do + expect(service.languages['en']).to eq %w(pt en-GB zh) + expect(service.languages['uk']).to eq %w(en pt en-GB zh) + end + + it 'returns target languages for auto-detection' do + expect(service.languages[nil]).to eq %w(en pt en-GB zh) + end + end + + describe '#request' do + before do + stub_request(:any, //) + # rubocop:disable Lint/EmptyBlock + service.send(:request, :get, '/v2/languages') { |res| } + # rubocop:enable Lint/EmptyBlock + end + + it 'uses paid plan base URL' do + expect(a_request(:get, 'https://api.deepl.com/v2/languages')).to have_been_made.once + end + + context 'with free plan' do + let(:plan) { 'free' } + + it 'uses free plan base URL' do + expect(a_request(:get, 'https://api-free.deepl.com/v2/languages')).to have_been_made.once + end + end + + it 'sends API key' do + expect(a_request(:get, 'https://api.deepl.com/v2/languages').with(headers: { Authorization: 'DeepL-Auth-Key my-api-key' })).to have_been_made.once + end + end +end diff --git a/spec/lib/translation_service/libre_translate_spec.rb b/spec/lib/translation_service/libre_translate_spec.rb new file mode 100644 index 000000000..90966a8eb --- /dev/null +++ b/spec/lib/translation_service/libre_translate_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe TranslationService::LibreTranslate do + subject(:service) { described_class.new('https://libretranslate.example.com', 'my-api-key') } + + before do + stub_request(:get, 'https://libretranslate.example.com/languages').to_return( + body: '[{"code": "en","name": "English","targets": ["de","en","es"]},{"code": "da","name": "Danish","targets": ["en","pt"]}]' + ) + end + + describe '#languages' do + subject(:languages) { service.languages } + + it 'returns source languages' do + expect(languages.keys).to eq ['en', 'da', nil] + end + + it 'returns target languages for each source language' do + expect(languages['en']).to eq %w(de es) + expect(languages['da']).to eq %w(en pt) + end + + it 'returns target languages for auto-detected language' do + expect(languages[nil]).to eq %w(de en es pt) + end + end + + describe '#translate' do + it 'returns translation with specified source language' do + stub_request(:post, 'https://libretranslate.example.com/translate') + .with(body: '{"q":["Hasta la vista"],"source":"es","target":"en","format":"html","api_key":"my-api-key"}') + .to_return(body: '{"translatedText": ["See you"]}') + + translations = service.translate(['Hasta la vista'], 'es', 'en') + expect(translations.size).to eq 1 + + translation = translations.first + expect(translation.detected_source_language).to be 'es' + expect(translation.provider).to eq 'LibreTranslate' + expect(translation.text).to eq 'See you' + end + + it 'returns translation with auto-detected source language' do + stub_request(:post, 'https://libretranslate.example.com/translate') + .with(body: '{"q":["Guten Morgen"],"source":"auto","target":"en","format":"html","api_key":"my-api-key"}') + .to_return(body: '{"detectedLanguage": [{"confidence": 92, "language": "de"}], "translatedText": ["Good morning"]}') + + translations = service.translate(['Guten Morgen'], nil, 'en') + expect(translations.size).to eq 1 + + translation = translations.first + expect(translation.detected_source_language).to eq 'de' + expect(translation.provider).to eq 'LibreTranslate' + expect(translation.text).to eq 'Good morning' + end + + it 'returns translation of multiple texts' do + stub_request(:post, 'https://libretranslate.example.com/translate') + .with(body: '{"q":["Guten Morgen","Gute Nacht"],"source":"de","target":"en","format":"html","api_key":"my-api-key"}') + .to_return(body: '{"translatedText": ["Good morning", "Good night"]}') + + translations = service.translate(['Guten Morgen', 'Gute Nacht'], 'de', 'en') + expect(translations.size).to eq 2 + + expect(translations.first.text).to eq 'Good morning' + expect(translations.last.text).to eq 'Good night' + end + end +end diff --git a/spec/lib/user_settings_decorator_spec.rb b/spec/lib/user_settings_decorator_spec.rb deleted file mode 100644 index 1bb44d380..000000000 --- a/spec/lib/user_settings_decorator_spec.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe UserSettingsDecorator do - describe 'update' do - let(:user) { Fabricate(:user) } - let(:settings) { described_class.new(user) } - - it 'updates the user settings value for email notifications' do - values = { 'notification_emails' => { 'follow' => '1' } } - - settings.update(values) - expect(user.settings['notification_emails']['follow']).to eq true - end - - it 'updates the user settings value for interactions' do - values = { 'interactions' => { 'must_be_follower' => '0' } } - - settings.update(values) - expect(user.settings['interactions']['must_be_follower']).to eq false - end - - it 'updates the user settings value for privacy' do - values = { 'setting_default_privacy' => 'public' } - - settings.update(values) - expect(user.settings['default_privacy']).to eq 'public' - end - - it 'updates the user settings value for sensitive' do - values = { 'setting_default_sensitive' => '1' } - - settings.update(values) - expect(user.settings['default_sensitive']).to eq true - end - - it 'updates the user settings value for unfollow modal' do - values = { 'setting_unfollow_modal' => '0' } - - settings.update(values) - expect(user.settings['unfollow_modal']).to eq false - end - - it 'updates the user settings value for boost modal' do - values = { 'setting_boost_modal' => '1' } - - settings.update(values) - expect(user.settings['boost_modal']).to eq true - end - - it 'updates the user settings value for delete toot modal' do - values = { 'setting_delete_modal' => '0' } - - settings.update(values) - expect(user.settings['delete_modal']).to eq false - end - - it 'updates the user settings value for gif auto play' do - values = { 'setting_auto_play_gif' => '0' } - - settings.update(values) - expect(user.settings['auto_play_gif']).to eq false - end - - it 'updates the user settings value for username expansion' do - values = { 'setting_expand_usernames' => '0'} - - settings.update(values) - expect(user.settings['expand_usernames'].to eq false) - end - - it 'updates the user settings value for system font in UI' do - values = { 'setting_system_font_ui' => '0' } - - settings.update(values) - expect(user.settings['system_font_ui']).to eq false - end - - it 'decoerces setting values before applying' do - values = { - 'setting_delete_modal' => 'false', - 'setting_boost_modal' => 'true', - } - - settings.update(values) - expect(user.settings['delete_modal']).to eq false - expect(user.settings['boost_modal']).to eq true - end - end -end diff --git a/spec/lib/vacuum/access_tokens_vacuum_spec.rb b/spec/lib/vacuum/access_tokens_vacuum_spec.rb index 0244c3449..54760c41b 100644 --- a/spec/lib/vacuum/access_tokens_vacuum_spec.rb +++ b/spec/lib/vacuum/access_tokens_vacuum_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Vacuum::AccessTokensVacuum do @@ -5,9 +7,11 @@ RSpec.describe Vacuum::AccessTokensVacuum do describe '#perform' do let!(:revoked_access_token) { Fabricate(:access_token, revoked_at: 1.minute.ago) } + let!(:expired_access_token) { Fabricate(:access_token, expires_in: 59.minutes.to_i, created_at: 1.hour.ago) } let!(:active_access_token) { Fabricate(:access_token) } let!(:revoked_access_grant) { Fabricate(:access_grant, revoked_at: 1.minute.ago) } + let!(:expired_access_grant) { Fabricate(:access_grant, expires_in: 59.minutes.to_i, created_at: 1.hour.ago) } let!(:active_access_grant) { Fabricate(:access_grant) } before do @@ -18,10 +22,18 @@ RSpec.describe Vacuum::AccessTokensVacuum do expect { revoked_access_token.reload }.to raise_error ActiveRecord::RecordNotFound end + it 'deletes expired access tokens' do + expect { expired_access_token.reload }.to raise_error ActiveRecord::RecordNotFound + end + it 'deletes revoked access grants' do expect { revoked_access_grant.reload }.to raise_error ActiveRecord::RecordNotFound end + it 'deletes expired access grants' do + expect { expired_access_grant.reload }.to raise_error ActiveRecord::RecordNotFound + end + it 'does not delete active access tokens' do expect { active_access_token.reload }.to_not raise_error end diff --git a/spec/lib/vacuum/backups_vacuum_spec.rb b/spec/lib/vacuum/backups_vacuum_spec.rb index 4e2de083f..867dbe402 100644 --- a/spec/lib/vacuum/backups_vacuum_spec.rb +++ b/spec/lib/vacuum/backups_vacuum_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Vacuum::BackupsVacuum do - let(:retention_period) { 7.days } - subject { described_class.new(retention_period) } + let(:retention_period) { 7.days } + describe '#perform' do let!(:expired_backup) { Fabricate(:backup, created_at: (retention_period + 1.day).ago) } let!(:current_backup) { Fabricate(:backup) } diff --git a/spec/lib/vacuum/feeds_vacuum_spec.rb b/spec/lib/vacuum/feeds_vacuum_spec.rb index 0aec26740..ede1e3c36 100644 --- a/spec/lib/vacuum/feeds_vacuum_spec.rb +++ b/spec/lib/vacuum/feeds_vacuum_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Vacuum::FeedsVacuum do diff --git a/spec/lib/vacuum/imports_vacuum_spec.rb b/spec/lib/vacuum/imports_vacuum_spec.rb new file mode 100644 index 000000000..1e0abc5e0 --- /dev/null +++ b/spec/lib/vacuum/imports_vacuum_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Vacuum::ImportsVacuum do + subject { described_class.new } + + let!(:old_unconfirmed) { Fabricate(:bulk_import, state: :unconfirmed, created_at: 2.days.ago) } + let!(:new_unconfirmed) { Fabricate(:bulk_import, state: :unconfirmed, created_at: 10.seconds.ago) } + let!(:recent_ongoing) { Fabricate(:bulk_import, state: :in_progress, created_at: 20.minutes.ago) } + let!(:recent_finished) { Fabricate(:bulk_import, state: :finished, created_at: 1.day.ago) } + let!(:old_finished) { Fabricate(:bulk_import, state: :finished, created_at: 2.months.ago) } + + describe '#perform' do + it 'cleans up the expected imports' do + expect { subject.perform }.to change { BulkImport.all.pluck(:id) }.from([old_unconfirmed, new_unconfirmed, recent_ongoing, recent_finished, old_finished].map(&:id)).to([new_unconfirmed, recent_ongoing, recent_finished].map(&:id)) + end + end +end diff --git a/spec/lib/vacuum/media_attachments_vacuum_spec.rb b/spec/lib/vacuum/media_attachments_vacuum_spec.rb index be8458d9b..3c17ecb00 100644 --- a/spec/lib/vacuum/media_attachments_vacuum_spec.rb +++ b/spec/lib/vacuum/media_attachments_vacuum_spec.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Vacuum::MediaAttachmentsVacuum do - let(:retention_period) { 7.days } - subject { described_class.new(retention_period) } + let(:retention_period) { 7.days } let(:remote_status) { Fabricate(:status, account: Fabricate(:account, domain: 'example.com')) } let(:local_status) { Fabricate(:status) } diff --git a/spec/lib/vacuum/preview_cards_vacuum_spec.rb b/spec/lib/vacuum/preview_cards_vacuum_spec.rb index 275f9ba92..c1b7f7e9c 100644 --- a/spec/lib/vacuum/preview_cards_vacuum_spec.rb +++ b/spec/lib/vacuum/preview_cards_vacuum_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Vacuum::PreviewCardsVacuum do - let(:retention_period) { 7.days } - subject { described_class.new(retention_period) } + let(:retention_period) { 7.days } + describe '#perform' do let!(:orphaned_preview_card) { Fabricate(:preview_card, created_at: 2.days.ago) } let!(:old_preview_card) { Fabricate(:preview_card, updated_at: (retention_period + 1.day).ago) } diff --git a/spec/lib/vacuum/statuses_vacuum_spec.rb b/spec/lib/vacuum/statuses_vacuum_spec.rb index 83f3c5c9f..d5c013950 100644 --- a/spec/lib/vacuum/statuses_vacuum_spec.rb +++ b/spec/lib/vacuum/statuses_vacuum_spec.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Vacuum::StatusesVacuum do + subject { described_class.new(retention_period) } + let(:retention_period) { 7.days } let(:remote_account) { Fabricate(:account, domain: 'example.com') } - subject { described_class.new(retention_period) } - describe '#perform' do let!(:remote_status_old) { Fabricate(:status, account: remote_account, created_at: (retention_period + 2.days).ago) } let!(:remote_status_recent) { Fabricate(:status, account: remote_account, created_at: (retention_period - 2.days).ago) } diff --git a/spec/lib/vacuum/system_keys_vacuum_spec.rb b/spec/lib/vacuum/system_keys_vacuum_spec.rb index 565892f02..84cae3041 100644 --- a/spec/lib/vacuum/system_keys_vacuum_spec.rb +++ b/spec/lib/vacuum/system_keys_vacuum_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Vacuum::SystemKeysVacuum do diff --git a/spec/lib/webfinger_resource_spec.rb b/spec/lib/webfinger_resource_spec.rb index 5c7f475d6..558a31892 100644 --- a/spec/lib/webfinger_resource_spec.rb +++ b/spec/lib/webfinger_resource_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe WebfingerResource do @@ -14,9 +16,9 @@ describe WebfingerResource do it 'raises with a route whose controller is not AccountsController' do resource = 'https://example.com/users/alice/other' - expect { - WebfingerResource.new(resource).username - }.to raise_error(ActiveRecord::RecordNotFound) + expect do + described_class.new(resource).username + end.to raise_error(ActiveRecord::RecordNotFound) end it 'raises with a route whose action is not show' do @@ -25,41 +27,42 @@ describe WebfingerResource do recognized = Rails.application.routes.recognize_path(resource) allow(recognized).to receive(:[]).with(:controller).and_return('accounts') allow(recognized).to receive(:[]).with(:username).and_return('alice') - expect(recognized).to receive(:[]).with(:action).and_return('create') + allow(recognized).to receive(:[]).with(:action).and_return('create') expect(Rails.application.routes).to receive(:recognize_path).with(resource).and_return(recognized).at_least(:once) - expect { - WebfingerResource.new(resource).username - }.to raise_error(ActiveRecord::RecordNotFound) + expect do + described_class.new(resource).username + end.to raise_error(ActiveRecord::RecordNotFound) + expect(recognized).to have_received(:[]).exactly(3).times end it 'raises with a string that doesnt start with URL' do resource = 'website for http://example.com/users/alice/other' - expect { - WebfingerResource.new(resource).username - }.to raise_error(WebfingerResource::InvalidRequest) + expect do + described_class.new(resource).username + end.to raise_error(WebfingerResource::InvalidRequest) end it 'finds the username in a valid https route' do resource = 'https://example.com/users/alice' - result = WebfingerResource.new(resource).username + result = described_class.new(resource).username expect(result).to eq 'alice' end it 'finds the username in a mixed case http route' do resource = 'HTTp://exAMPLe.com/users/alice' - result = WebfingerResource.new(resource).username + result = described_class.new(resource).username expect(result).to eq 'alice' end it 'finds the username in a valid http route' do resource = 'http://example.com/users/alice' - result = WebfingerResource.new(resource).username + result = described_class.new(resource).username expect(result).to eq 'alice' end end @@ -68,16 +71,16 @@ describe WebfingerResource do it 'raises on a non-local domain' do resource = 'user@remote-host.com' - expect { - WebfingerResource.new(resource).username - }.to raise_error(ActiveRecord::RecordNotFound) + expect do + described_class.new(resource).username + end.to raise_error(ActiveRecord::RecordNotFound) end it 'finds username for a local domain' do Rails.configuration.x.local_domain = 'example.com' resource = 'alice@example.com' - result = WebfingerResource.new(resource).username + result = described_class.new(resource).username expect(result).to eq 'alice' end @@ -85,7 +88,7 @@ describe WebfingerResource do Rails.configuration.x.web_domain = 'example.com' resource = 'alice@example.com' - result = WebfingerResource.new(resource).username + result = described_class.new(resource).username expect(result).to eq 'alice' end end @@ -94,24 +97,24 @@ describe WebfingerResource do it 'raises on a non-local domain' do resource = 'acct:user@remote-host.com' - expect { - WebfingerResource.new(resource).username - }.to raise_error(ActiveRecord::RecordNotFound) + expect do + described_class.new(resource).username + end.to raise_error(ActiveRecord::RecordNotFound) end it 'raises on a nonsense domain' do resource = 'acct:user@remote-host@remote-hostess.remote.local@remote' - expect { - WebfingerResource.new(resource).username - }.to raise_error(ActiveRecord::RecordNotFound) + expect do + described_class.new(resource).username + end.to raise_error(ActiveRecord::RecordNotFound) end it 'finds the username for a local account if the domain is the local one' do Rails.configuration.x.local_domain = 'example.com' resource = 'acct:alice@example.com' - result = WebfingerResource.new(resource).username + result = described_class.new(resource).username expect(result).to eq 'alice' end @@ -119,7 +122,7 @@ describe WebfingerResource do Rails.configuration.x.web_domain = 'example.com' resource = 'acct:alice@example.com' - result = WebfingerResource.new(resource).username + result = described_class.new(resource).username expect(result).to eq 'alice' end end @@ -128,9 +131,9 @@ describe WebfingerResource do it 'raises InvalidRequest' do resource = 'df/:dfkj' - expect { - WebfingerResource.new(resource).username - }.to raise_error(WebfingerResource::InvalidRequest) + expect do + described_class.new(resource).username + end.to raise_error(WebfingerResource::InvalidRequest) end end end diff --git a/spec/lib/webhooks/payload_renderer_spec.rb b/spec/lib/webhooks/payload_renderer_spec.rb new file mode 100644 index 000000000..074847c74 --- /dev/null +++ b/spec/lib/webhooks/payload_renderer_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Webhooks::PayloadRenderer do + subject(:renderer) { described_class.new(json) } + + let(:event) { Webhooks::EventPresenter.new(type, object) } + let(:payload) { ActiveModelSerializers::SerializableResource.new(event, serializer: REST::Admin::WebhookEventSerializer, scope: nil, scope_name: :current_user).as_json } + let(:json) { Oj.dump(payload) } + + describe '#render' do + context 'when event is account.approved' do + let(:type) { 'account.approved' } + let(:object) { Fabricate(:account, display_name: 'Foo"') } + + it 'renders event-related variables into template' do + expect(renderer.render('foo={{event}}')).to eq 'foo=account.approved' + end + + it 'renders event-specific variables into template' do + expect(renderer.render('foo={{object.username}}')).to eq "foo=#{object.username}" + end + + it 'escapes values for use in JSON' do + expect(renderer.render('foo={{object.account.display_name}}')).to eq 'foo=Foo\\"' + end + end + end +end diff --git a/spec/locales/i18n_spec.rb b/spec/locales/i18n_spec.rb new file mode 100644 index 000000000..cfce8e223 --- /dev/null +++ b/spec/locales/i18n_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'I18n' do + describe 'Pluralizing locale translations' do + subject { I18n.t('generic.validation_errors', count: 1) } + + context 'with the `en` locale which has `one` and `other` plural values' do + around do |example| + I18n.with_locale(:en) do + example.run + end + end + + it 'translates to `en` correctly and without error' do + expect { subject }.to_not raise_error + expect(subject).to match(/the error below/) + end + end + + context 'with the `my` locale which has only `other` plural value' do + around do |example| + I18n.with_locale(:my) do + example.run + end + end + + it 'translates to `my` correctly and without error' do + expect { subject }.to_not raise_error + expect(subject).to match(/1/) + end + end + end +end diff --git a/spec/mailers/admin_mailer_spec.rb b/spec/mailers/admin_mailer_spec.rb index 29fb586a3..423dce88a 100644 --- a/spec/mailers/admin_mailer_spec.rb +++ b/spec/mailers/admin_mailer_spec.rb @@ -2,12 +2,12 @@ require 'rails_helper' -RSpec.describe AdminMailer, type: :mailer do +RSpec.describe AdminMailer do describe '.new_report' do let(:sender) { Fabricate(:account, username: 'John') } let(:recipient) { Fabricate(:account, username: 'Mike') } let(:report) { Fabricate(:report, account: sender, target_account: recipient) } - let(:mail) { described_class.new_report(recipient, report) } + let(:mail) { described_class.with(recipient: recipient).new_report(report) } before do recipient.user.update(locale: :en) @@ -23,4 +23,108 @@ RSpec.describe AdminMailer, type: :mailer do expect(mail.body.encoded).to eq("Mike,\r\n\r\nJohn has reported Mike\r\n\r\nView: https://cb6e6126.ngrok.io/admin/reports/#{report.id}\r\n") end end + + describe '.new_appeal' do + let(:appeal) { Fabricate(:appeal) } + let(:recipient) { Fabricate(:account, username: 'Kurt') } + let(:mail) { described_class.with(recipient: recipient).new_appeal(appeal) } + + before do + recipient.user.update(locale: :en) + end + + it 'renders the headers' do + expect(mail.subject).to eq("#{appeal.account.username} is appealing a moderation decision on cb6e6126.ngrok.io") + expect(mail.to).to eq [recipient.user_email] + expect(mail.from).to eq ['notifications@localhost'] + end + + it 'renders the body' do + expect(mail.body.encoded).to match "#{appeal.account.username} is appealing a moderation decision by #{appeal.strike.account.username}" + end + end + + describe '.new_pending_account' do + let(:recipient) { Fabricate(:account, username: 'Barklums') } + let(:user) { Fabricate(:user) } + let(:mail) { described_class.with(recipient: recipient).new_pending_account(user) } + + before do + recipient.user.update(locale: :en) + end + + it 'renders the headers' do + expect(mail.subject).to eq("New account up for review on cb6e6126.ngrok.io (#{user.account.username})") + expect(mail.to).to eq [recipient.user_email] + expect(mail.from).to eq ['notifications@localhost'] + end + + it 'renders the body' do + expect(mail.body.encoded).to match 'The details of the new account are below. You can approve or reject this application.' + end + end + + describe '.new_trends' do + let(:recipient) { Fabricate(:account, username: 'Snurf') } + let(:links) { [] } + let(:statuses) { [] } + let(:tags) { [] } + let(:mail) { described_class.with(recipient: recipient).new_trends(links, tags, statuses) } + + before do + recipient.user.update(locale: :en) + end + + it 'renders the headers' do + expect(mail.subject).to eq('New trends up for review on cb6e6126.ngrok.io') + expect(mail.to).to eq [recipient.user_email] + expect(mail.from).to eq ['notifications@localhost'] + end + + it 'renders the body' do + expect(mail.body.encoded).to match 'The following items need a review before they can be displayed publicly' + end + end + + describe '.new_software_updates' do + let(:recipient) { Fabricate(:account, username: 'Bob') } + let(:mail) { described_class.with(recipient: recipient).new_software_updates } + + before do + recipient.user.update(locale: :en) + end + + it 'renders the headers' do + expect(mail.subject).to eq('New Mastodon versions are available for cb6e6126.ngrok.io!') + expect(mail.to).to eq [recipient.user_email] + expect(mail.from).to eq ['notifications@localhost'] + end + + it 'renders the body' do + expect(mail.body.encoded).to match 'New Mastodon versions have been released, you may want to update!' + end + end + + describe '.new_critical_software_updates' do + let(:recipient) { Fabricate(:account, username: 'Bob') } + let(:mail) { described_class.with(recipient: recipient).new_critical_software_updates } + + before do + recipient.user.update(locale: :en) + end + + it 'renders the headers', :aggregate_failures do + expect(mail.subject).to eq('Critical Mastodon updates are available for cb6e6126.ngrok.io!') + expect(mail.to).to eq [recipient.user_email] + expect(mail.from).to eq ['notifications@localhost'] + + expect(mail['Importance'].value).to eq 'high' + expect(mail['Priority'].value).to eq 'urgent' + expect(mail['X-Priority'].value).to eq '1' + end + + it 'renders the body' do + expect(mail.body.encoded).to match 'New critical versions of Mastodon have been released, you may want to update as soon as possible!' + end + end end diff --git a/spec/mailers/notification_mailer_spec.rb b/spec/mailers/notification_mailer_spec.rb index 29bdc349b..78a497c06 100644 --- a/spec/mailers/notification_mailer_spec.rb +++ b/spec/mailers/notification_mailer_spec.rb @@ -1,104 +1,125 @@ -require "rails_helper" +# frozen_string_literal: true -RSpec.describe NotificationMailer, type: :mailer do - let(:receiver) { Fabricate(:user) } +require 'rails_helper' + +RSpec.describe NotificationMailer do + let(:receiver) { Fabricate(:user, account_attributes: { username: 'alice' }) } let(:sender) { Fabricate(:account, username: 'bob') } let(:foreign_status) { Fabricate(:status, account: sender, text: 'The body of the foreign status') } let(:own_status) { Fabricate(:status, account: receiver.account, text: 'The body of the own status') } - shared_examples 'localized subject' do |*args, **kwrest| - it 'renders subject localized for the locale of the receiver' do - locale = %i(de en).sample - receiver.update!(locale: locale) - expect(mail.subject).to eq I18n.t(*args, **kwrest.merge(locale: locale)) + shared_examples 'headers' do |type, thread| + it 'renders the to and from headers' do + expect(mail[:to].value).to eq "#{receiver.account.username} <#{receiver.email}>" + expect(mail.from).to eq ['notifications@localhost'] end - it 'renders subject localized for the default locale if the locale of the receiver is unavailable' do - receiver.update!(locale: nil) - expect(mail.subject).to eq I18n.t(*args, **kwrest.merge(locale: I18n.default_locale)) + it 'renders the list headers' do + expect(mail['List-ID'].value).to eq "<#{type}.alice.cb6e6126.ngrok.io>" + expect(mail['List-Unsubscribe'].value).to match(%r{}) + expect(mail['List-Unsubscribe'].value).to match("&type=#{type}") + expect(mail['List-Unsubscribe-Post'].value).to eq 'List-Unsubscribe=One-Click' + end + + if thread + it 'renders the thread headers' do + expect(mail['In-Reply-To'].value).to match(//) + expect(mail['References'].value).to match(//) + end end end - describe "mention" do + describe 'mention' do let(:mention) { Mention.create!(account: receiver.account, status: foreign_status) } - let(:mail) { NotificationMailer.mention(receiver.account, Notification.create!(account: receiver.account, activity: mention)) } + let(:notification) { Notification.create!(account: receiver.account, activity: mention) } + let(:mail) { prepared_mailer_for(receiver.account).mention } include_examples 'localized subject', 'notification_mailer.mention.subject', name: 'bob' + include_examples 'headers', 'mention', true - it "renders the headers" do - expect(mail.subject).to eq("You were mentioned by bob") - expect(mail.to).to eq([receiver.email]) + it 'renders the subject' do + expect(mail.subject).to eq('You were mentioned by bob') end - it "renders the body" do - expect(mail.body.encoded).to match("You were mentioned by bob") + it 'renders the body' do + expect(mail.body.encoded).to match('You were mentioned by bob') expect(mail.body.encoded).to include 'The body of the foreign status' end end - describe "follow" do + describe 'follow' do let(:follow) { sender.follow!(receiver.account) } - let(:mail) { NotificationMailer.follow(receiver.account, Notification.create!(account: receiver.account, activity: follow)) } + let(:notification) { Notification.create!(account: receiver.account, activity: follow) } + let(:mail) { prepared_mailer_for(receiver.account).follow } include_examples 'localized subject', 'notification_mailer.follow.subject', name: 'bob' + include_examples 'headers', 'follow', false - it "renders the headers" do - expect(mail.subject).to eq("bob is now following you") - expect(mail.to).to eq([receiver.email]) + it 'renders the subject' do + expect(mail.subject).to eq('bob is now following you') end - it "renders the body" do - expect(mail.body.encoded).to match("bob is now following you") + it 'renders the body' do + expect(mail.body.encoded).to match('bob is now following you') end end - describe "favourite" do + describe 'favourite' do let(:favourite) { Favourite.create!(account: sender, status: own_status) } - let(:mail) { NotificationMailer.favourite(own_status.account, Notification.create!(account: receiver.account, activity: favourite)) } + let(:notification) { Notification.create!(account: receiver.account, activity: favourite) } + let(:mail) { prepared_mailer_for(own_status.account).favourite } include_examples 'localized subject', 'notification_mailer.favourite.subject', name: 'bob' + include_examples 'headers', 'favourite', true - it "renders the headers" do - expect(mail.subject).to eq("bob favourited your post") - expect(mail.to).to eq([receiver.email]) + it 'renders the subject' do + expect(mail.subject).to eq('bob favorited your post') end - it "renders the body" do - expect(mail.body.encoded).to match("Your post was favourited by bob") + it 'renders the body' do + expect(mail.body.encoded).to match('Your post was favorited by bob') expect(mail.body.encoded).to include 'The body of the own status' end end - describe "reblog" do + describe 'reblog' do let(:reblog) { Status.create!(account: sender, reblog: own_status) } - let(:mail) { NotificationMailer.reblog(own_status.account, Notification.create!(account: receiver.account, activity: reblog)) } + let(:notification) { Notification.create!(account: receiver.account, activity: reblog) } + let(:mail) { prepared_mailer_for(own_status.account).reblog } include_examples 'localized subject', 'notification_mailer.reblog.subject', name: 'bob' + include_examples 'headers', 'reblog', true - it "renders the headers" do - expect(mail.subject).to eq("bob boosted your post") - expect(mail.to).to eq([receiver.email]) + it 'renders the subject' do + expect(mail.subject).to eq('bob boosted your post') end - it "renders the body" do - expect(mail.body.encoded).to match("Your post was boosted by bob") + it 'renders the body' do + expect(mail.body.encoded).to match('Your post was boosted by bob') expect(mail.body.encoded).to include 'The body of the own status' end end describe 'follow_request' do let(:follow_request) { Fabricate(:follow_request, account: sender, target_account: receiver.account) } - let(:mail) { NotificationMailer.follow_request(receiver.account, Notification.create!(account: receiver.account, activity: follow_request)) } + let(:notification) { Notification.create!(account: receiver.account, activity: follow_request) } + let(:mail) { prepared_mailer_for(receiver.account).follow_request } include_examples 'localized subject', 'notification_mailer.follow_request.subject', name: 'bob' + include_examples 'headers', 'follow_request', false - it 'renders the headers' do + it 'renders the subject' do expect(mail.subject).to eq('Pending follower: bob') - expect(mail.to).to eq([receiver.email]) end it 'renders the body' do - expect(mail.body.encoded).to match("bob has requested to follow you") + expect(mail.body.encoded).to match('bob has requested to follow you') end end + + private + + def prepared_mailer_for(recipient) + described_class.with(recipient: recipient, notification: notification) + end end diff --git a/spec/mailers/previews/admin_mailer_preview.rb b/spec/mailers/previews/admin_mailer_preview.rb index 0ec9e9882..bc8f0193b 100644 --- a/spec/mailers/previews/admin_mailer_preview.rb +++ b/spec/mailers/previews/admin_mailer_preview.rb @@ -1,18 +1,20 @@ +# frozen_string_literal: true + # Preview all emails at http://localhost:3000/rails/mailers/admin_mailer class AdminMailerPreview < ActionMailer::Preview # Preview this email at http://localhost:3000/rails/mailers/admin_mailer/new_pending_account def new_pending_account - AdminMailer.new_pending_account(Account.first, User.pending.first) + AdminMailer.with(recipient: Account.first).new_pending_account(User.pending.first) end # Preview this email at http://localhost:3000/rails/mailers/admin_mailer/new_trends def new_trends - AdminMailer.new_trends(Account.first, PreviewCard.joins(:trend).limit(3), Tag.limit(3), Status.joins(:trend).where(reblog_of_id: nil).limit(3)) + AdminMailer.with(recipient: Account.first).new_trends(PreviewCard.joins(:trend).limit(3), Tag.limit(3), Status.joins(:trend).where(reblog_of_id: nil).limit(3)) end # Preview this email at http://localhost:3000/rails/mailers/admin_mailer/new_appeal def new_appeal - AdminMailer.new_appeal(Account.first, Appeal.first) + AdminMailer.with(recipient: Account.first).new_appeal(Appeal.first) end end diff --git a/spec/mailers/previews/notification_mailer_preview.rb b/spec/mailers/previews/notification_mailer_preview.rb index e31445c36..a63c20c27 100644 --- a/spec/mailers/previews/notification_mailer_preview.rb +++ b/spec/mailers/previews/notification_mailer_preview.rb @@ -1,38 +1,44 @@ +# frozen_string_literal: true + # Preview all emails at http://localhost:3000/rails/mailers/notification_mailer class NotificationMailerPreview < ActionMailer::Preview # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/mention def mention - m = Mention.last - NotificationMailer.mention(m.account, Notification.find_by(activity: m)) + activity = Mention.last + mailer_for(activity.account, activity).mention end # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/follow def follow - f = Follow.last - NotificationMailer.follow(f.target_account, Notification.find_by(activity: f)) + activity = Follow.last + mailer_for(activity.target_account, activity).follow end # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/follow_request def follow_request - f = Follow.last - NotificationMailer.follow_request(f.target_account, Notification.find_by(activity: f)) + activity = Follow.last + mailer_for(activity.target_account, activity).follow_request end # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/favourite def favourite - f = Favourite.last - NotificationMailer.favourite(f.status.account, Notification.find_by(activity: f)) + activity = Favourite.last + mailer_for(activity.status.account, activity).favourite end # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/reblog def reblog - r = Status.where.not(reblog_of_id: nil).first - NotificationMailer.reblog(r.reblog.account, Notification.find_by(activity: r)) + activity = Status.where.not(reblog_of_id: nil).first + mailer_for(activity.reblog.account, activity).reblog end - # Preview this email at http://localhost:3000/rails/mailers/notification_mailer/digest - def digest - NotificationMailer.digest(Account.first, since: 90.days.ago) + private + + def mailer_for(account, activity) + NotificationMailer.with( + recipient: account, + notification: Notification.find_by(activity: activity) + ) end end diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb index 95712e6cf..098c9cd90 100644 --- a/spec/mailers/previews/user_mailer_preview.rb +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Preview all emails at http://localhost:3000/rails/mailers/user_mailer class UserMailerPreview < ActionMailer::Preview diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 2ed33c1e4..aa9167dd6 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -2,96 +2,191 @@ require 'rails_helper' -describe UserMailer, type: :mailer do +describe UserMailer do let(:receiver) { Fabricate(:user) } - shared_examples 'localized subject' do |*args, **kwrest| - it 'renders subject localized for the locale of the receiver' do - locale = I18n.available_locales.sample - receiver.update!(locale: locale) - expect(mail.subject).to eq I18n.t(*args, **kwrest.merge(locale: locale)) - end - - it 'renders subject localized for the default locale if the locale of the receiver is unavailable' do - receiver.update!(locale: nil) - expect(mail.subject).to eq I18n.t(*args, **kwrest.merge(locale: I18n.default_locale)) - end - end - describe 'confirmation_instructions' do - let(:mail) { UserMailer.confirmation_instructions(receiver, 'spec') } + let(:mail) { described_class.confirmation_instructions(receiver, 'spec') } it 'renders confirmation instructions' do receiver.update!(locale: nil) - expect(mail.body.encoded).to include I18n.t('devise.mailer.confirmation_instructions.title') + expect(mail.body.encoded).to include I18n.t('devise.mailer.confirmation_instructions.title', title: Setting.site_title) expect(mail.body.encoded).to include 'spec' expect(mail.body.encoded).to include Rails.configuration.x.local_domain end include_examples 'localized subject', 'devise.mailer.confirmation_instructions.subject', - instance: Rails.configuration.x.local_domain + instance: Rails.configuration.x.local_domain, + title: Setting.site_title end describe 'reconfirmation_instructions' do - let(:mail) { UserMailer.confirmation_instructions(receiver, 'spec') } + let(:mail) { described_class.confirmation_instructions(receiver, 'spec') } it 'renders reconfirmation instructions' do receiver.update!(email: 'new-email@example.com', locale: nil) - expect(mail.body.encoded).to include I18n.t('devise.mailer.reconfirmation_instructions.title') + expect(mail.body.encoded).to include I18n.t('devise.mailer.reconfirmation_instructions.title', title: Setting.site_title) expect(mail.body.encoded).to include 'spec' expect(mail.body.encoded).to include Rails.configuration.x.local_domain expect(mail.subject).to eq I18n.t('devise.mailer.reconfirmation_instructions.subject', instance: Rails.configuration.x.local_domain, - locale: I18n.default_locale) + locale: I18n.default_locale, + title: Setting.site_title) end end describe 'reset_password_instructions' do - let(:mail) { UserMailer.reset_password_instructions(receiver, 'spec') } + let(:mail) { described_class.reset_password_instructions(receiver, 'spec') } it 'renders reset password instructions' do receiver.update!(locale: nil) - expect(mail.body.encoded).to include I18n.t('devise.mailer.reset_password_instructions.title') + expect(mail.body.encoded).to include I18n.t('devise.mailer.reset_password_instructions.title', title: Setting.site_title) expect(mail.body.encoded).to include 'spec' end include_examples 'localized subject', - 'devise.mailer.reset_password_instructions.subject' + 'devise.mailer.reset_password_instructions.subject', + title: Setting.site_title end describe 'password_change' do - let(:mail) { UserMailer.password_change(receiver) } + let(:mail) { described_class.password_change(receiver) } it 'renders password change notification' do receiver.update!(locale: nil) - expect(mail.body.encoded).to include I18n.t('devise.mailer.password_change.title') + expect(mail.body.encoded).to include I18n.t('devise.mailer.password_change.title', title: Setting.site_title) end include_examples 'localized subject', - 'devise.mailer.password_change.subject' + 'devise.mailer.password_change.subject', + title: Setting.site_title end describe 'email_changed' do - let(:mail) { UserMailer.email_changed(receiver) } + let(:mail) { described_class.email_changed(receiver) } it 'renders email change notification' do receiver.update!(locale: nil) - expect(mail.body.encoded).to include I18n.t('devise.mailer.email_changed.title') + expect(mail.body.encoded).to include I18n.t('devise.mailer.email_changed.title', title: Setting.site_title) end include_examples 'localized subject', - 'devise.mailer.email_changed.subject' + 'devise.mailer.email_changed.subject', + title: Setting.site_title end describe 'warning' do let(:strike) { Fabricate(:account_warning, target_account: receiver.account, text: 'dont worry its just the testsuite', action: 'suspend') } - let(:mail) { UserMailer.warning(receiver, strike) } + let(:mail) { described_class.warning(receiver, strike) } it 'renders warning notification' do receiver.update!(locale: nil) - expect(mail.body.encoded).to include I18n.t("user_mailer.warning.title.suspend", acct: receiver.account.acct) + expect(mail.body.encoded).to include I18n.t('user_mailer.warning.title.suspend', acct: receiver.account.acct) expect(mail.body.encoded).to include strike.text end end + + describe 'webauthn_credential_deleted' do + let(:credential) { Fabricate(:webauthn_credential, user_id: receiver.id) } + let(:mail) { described_class.webauthn_credential_deleted(receiver, credential) } + + it 'renders webauthn credential deleted notification' do + receiver.update!(locale: nil) + expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_credential.deleted.title') + end + + include_examples 'localized subject', + 'devise.mailer.webauthn_credential.deleted.subject' + end + + describe 'suspicious_sign_in' do + let(:ip) { '192.168.0.1' } + let(:agent) { 'NCSA_Mosaic/2.0 (Windows 3.1)' } + let(:timestamp) { Time.now.utc } + let(:mail) { described_class.suspicious_sign_in(receiver, ip, agent, timestamp) } + + it 'renders suspicious sign in notification' do + receiver.update!(locale: nil) + expect(mail.body.encoded).to include I18n.t('user_mailer.suspicious_sign_in.explanation') + end + + include_examples 'localized subject', + 'user_mailer.suspicious_sign_in.subject' + end + + describe 'appeal_approved' do + let(:appeal) { Fabricate(:appeal, account: receiver.account, approved_at: Time.now.utc) } + let(:mail) { described_class.appeal_approved(receiver, appeal) } + + it 'renders appeal_approved notification' do + expect(mail.subject).to eq I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at)) + expect(mail.body.encoded).to include I18n.t('user_mailer.appeal_approved.title') + end + end + + describe 'appeal_rejected' do + let(:appeal) { Fabricate(:appeal, account: receiver.account, rejected_at: Time.now.utc) } + let(:mail) { described_class.appeal_rejected(receiver, appeal) } + + it 'renders appeal_rejected notification' do + expect(mail.subject).to eq I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at)) + expect(mail.body.encoded).to include I18n.t('user_mailer.appeal_rejected.title') + end + end + + describe 'two_factor_enabled' do + let(:mail) { described_class.two_factor_enabled(receiver) } + + it 'renders two_factor_enabled mail' do + expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_enabled.subject') + expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_enabled.explanation') + end + end + + describe 'two_factor_disabled' do + let(:mail) { described_class.two_factor_disabled(receiver) } + + it 'renders two_factor_disabled mail' do + expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_disabled.subject') + expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_disabled.explanation') + end + end + + describe 'webauthn_enabled' do + let(:mail) { described_class.webauthn_enabled(receiver) } + + it 'renders webauthn_enabled mail' do + expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_enabled.subject') + expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_enabled.explanation') + end + end + + describe 'webauthn_disabled' do + let(:mail) { described_class.webauthn_disabled(receiver) } + + it 'renders webauthn_disabled mail' do + expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_disabled.subject') + expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_disabled.explanation') + end + end + + describe 'two_factor_recovery_codes_changed' do + let(:mail) { described_class.two_factor_recovery_codes_changed(receiver) } + + it 'renders two_factor_recovery_codes_changed mail' do + expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject') + expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_recovery_codes_changed.explanation') + end + end + + describe 'webauthn_credential_added' do + let(:credential) { Fabricate.build(:webauthn_credential) } + let(:mail) { described_class.webauthn_credential_added(receiver, credential) } + + it 'renders webauthn_credential_added mail' do + expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_credential.added.subject') + expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_credential.added.explanation') + end + end end diff --git a/spec/models/account/field_spec.rb b/spec/models/account/field_spec.rb index b4beec048..22593bb21 100644 --- a/spec/models/account/field_spec.rb +++ b/spec/models/account/field_spec.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Account::Field, type: :model do +RSpec.describe Account::Field do describe '#verified?' do - let(:account) { double('Account', local?: true) } - subject { described_class.new(account, 'name' => 'Foo', 'value' => 'Bar', 'verified_at' => verified_at) } + let(:account) { instance_double(Account, local?: true) } + context 'when verified_at is set' do let(:verified_at) { Time.now.utc.iso8601 } @@ -24,11 +26,11 @@ RSpec.describe Account::Field, type: :model do end describe '#mark_verified!' do - let(:account) { double('Account', local?: true) } - let(:original_hash) { { 'name' => 'Foo', 'value' => 'Bar' } } - subject { described_class.new(account, original_hash) } + let(:account) { instance_double(Account, local?: true) } + let(:original_hash) { { 'name' => 'Foo', 'value' => 'Bar' } } + before do subject.mark_verified! end @@ -43,14 +45,14 @@ RSpec.describe Account::Field, type: :model do end describe '#verifiable?' do - let(:account) { double('Account', local?: local) } - subject { described_class.new(account, 'name' => 'Foo', 'value' => value) } - context 'for local accounts' do + let(:account) { instance_double(Account, local?: local) } + + context 'with local accounts' do let(:local) { true } - context 'for a URL with misleading authentication' do + context 'with a URL with misleading authentication' do let(:value) { 'https://spacex.com @h.43z.one' } it 'returns false' do @@ -58,7 +60,7 @@ RSpec.describe Account::Field, type: :model do end end - context 'for a URL' do + context 'with a URL' do let(:value) { 'https://example.com' } it 'returns true' do @@ -66,15 +68,23 @@ RSpec.describe Account::Field, type: :model do end end - context 'for an IDN URL' do - let(:value) { 'http://twitter.com∕dougallj∕status∕1590357240443437057.ê.cc/twitter.html' } + context 'with an IDN URL' do + let(:value) { 'https://twitter.com∕dougallj∕status∕1590357240443437057.ê.cc/twitter.html' } it 'returns false' do expect(subject.verifiable?).to be false end end - context 'for text that is not a URL' do + context 'with a URL with a non-normalized path' do + let(:value) { 'https://github.com/octocatxxxxxxxx/../mastodon' } + + it 'returns false' do + expect(subject.verifiable?).to be false + end + end + + context 'with text that is not a URL' do let(:value) { 'Hello world' } it 'returns false' do @@ -82,15 +92,15 @@ RSpec.describe Account::Field, type: :model do end end - context 'for text that contains a URL' do + context 'with text that contains a URL' do let(:value) { 'Hello https://example.com world' } it 'returns false' do expect(subject.verifiable?).to be false end end - - context 'for text which is blank' do + + context 'with text which is blank' do let(:value) { '' } it 'returns false' do @@ -99,10 +109,10 @@ RSpec.describe Account::Field, type: :model do end end - context 'for remote accounts' do + context 'with remote accounts' do let(:local) { false } - context 'for a link' do + context 'with a link' do let(:value) { 'patreon.com/mastodon' } it 'returns true' do @@ -110,7 +120,7 @@ RSpec.describe Account::Field, type: :model do end end - context 'for a link with misleading authentication' do + context 'with a link with misleading authentication' do let(:value) { 'google.com' } it 'returns false' do @@ -118,7 +128,7 @@ RSpec.describe Account::Field, type: :model do end end - context 'for HTML that has more than just a link' do + context 'with HTML that has more than just a link' do let(:value) { 'google.com @h.43z.one' } it 'returns false' do @@ -126,7 +136,7 @@ RSpec.describe Account::Field, type: :model do end end - context 'for a link with different visible text' do + context 'with a link with different visible text' do let(:value) { 'https://example.com/foo' } it 'returns false' do @@ -134,15 +144,15 @@ RSpec.describe Account::Field, type: :model do end end - context 'for text that is a URL but is not linked' do + context 'with text that is a URL but is not linked' do let(:value) { 'https://example.com/foo' } it 'returns false' do expect(subject.verifiable?).to be false end end - - context 'for text which is blank' do + + context 'with text which is blank' do let(:value) { '' } it 'returns false' do diff --git a/spec/models/account_alias_spec.rb b/spec/models/account_alias_spec.rb deleted file mode 100644 index 27ec215aa..000000000 --- a/spec/models/account_alias_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -RSpec.describe AccountAlias, type: :model do - -end diff --git a/spec/models/account_conversation_spec.rb b/spec/models/account_conversation_spec.rb index 70a76281e..4e8727ca3 100644 --- a/spec/models/account_conversation_spec.rb +++ b/spec/models/account_conversation_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe AccountConversation, type: :model do +RSpec.describe AccountConversation do let!(:alice) { Fabricate(:account, username: 'alice') } let!(:bob) { Fabricate(:account, username: 'bob') } let!(:mark) { Fabricate(:account, username: 'mark') } @@ -10,7 +12,7 @@ RSpec.describe AccountConversation, type: :model do status = Fabricate(:status, account: alice, visibility: :direct) status.mentions.create(account: bob) - conversation = AccountConversation.add_status(alice, status) + conversation = described_class.add_status(alice, status) expect(conversation.participant_accounts).to include(bob) expect(conversation.last_status).to eq status @@ -19,12 +21,12 @@ RSpec.describe AccountConversation, type: :model do it 'appends to old record when there is a match' do last_status = Fabricate(:status, account: alice, visibility: :direct) - conversation = AccountConversation.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [last_status.id]) + conversation = described_class.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [last_status.id]) status = Fabricate(:status, account: bob, visibility: :direct, thread: last_status) status.mentions.create(account: alice) - new_conversation = AccountConversation.add_status(alice, status) + new_conversation = described_class.add_status(alice, status) expect(new_conversation.id).to eq conversation.id expect(new_conversation.participant_accounts).to include(bob) @@ -34,13 +36,13 @@ RSpec.describe AccountConversation, type: :model do it 'creates new record when new participants are added' do last_status = Fabricate(:status, account: alice, visibility: :direct) - conversation = AccountConversation.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [last_status.id]) + conversation = described_class.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [last_status.id]) status = Fabricate(:status, account: bob, visibility: :direct, thread: last_status) status.mentions.create(account: alice) status.mentions.create(account: mark) - new_conversation = AccountConversation.add_status(alice, status) + new_conversation = described_class.add_status(alice, status) expect(new_conversation.id).to_not eq conversation.id expect(new_conversation.participant_accounts).to include(bob, mark) @@ -53,7 +55,7 @@ RSpec.describe AccountConversation, type: :model do it 'updates last status to a previous value' do last_status = Fabricate(:status, account: alice, visibility: :direct) status = Fabricate(:status, account: alice, visibility: :direct) - conversation = AccountConversation.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [status.id, last_status.id]) + conversation = described_class.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [status.id, last_status.id]) last_status.mentions.create(account: bob) last_status.destroy! conversation.reload @@ -63,10 +65,10 @@ RSpec.describe AccountConversation, type: :model do it 'removes the record if no other statuses are referenced' do last_status = Fabricate(:status, account: alice, visibility: :direct) - conversation = AccountConversation.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [last_status.id]) + conversation = described_class.create!(account: alice, conversation: last_status.conversation, participant_account_ids: [bob.id], status_ids: [last_status.id]) last_status.mentions.create(account: bob) last_status.destroy! - expect(AccountConversation.where(id: conversation.id).count).to eq 0 + expect(described_class.where(id: conversation.id).count).to eq 0 end end end diff --git a/spec/models/account_deletion_request_spec.rb b/spec/models/account_deletion_request_spec.rb deleted file mode 100644 index afaecbe22..000000000 --- a/spec/models/account_deletion_request_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe AccountDeletionRequest, type: :model do -end diff --git a/spec/models/account_domain_block_spec.rb b/spec/models/account_domain_block_spec.rb index 469bc05cb..10bd57936 100644 --- a/spec/models/account_domain_block_spec.rb +++ b/spec/models/account_domain_block_spec.rb @@ -1,22 +1,24 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe AccountDomainBlock, type: :model do +RSpec.describe AccountDomainBlock do it 'removes blocking cache after creation' do account = Fabricate(:account) Rails.cache.write("exclude_domains_for:#{account.id}", 'a.domain.already.blocked') - AccountDomainBlock.create!(account: account, domain: 'a.domain.blocked.later') + described_class.create!(account: account, domain: 'a.domain.blocked.later') - expect(Rails.cache.exist?("exclude_domains_for:#{account.id}")).to eq false + expect(Rails.cache.exist?("exclude_domains_for:#{account.id}")).to be false end it 'removes blocking cache after destruction' do account = Fabricate(:account) - block = AccountDomainBlock.create!(account: account, domain: 'domain') + block = described_class.create!(account: account, domain: 'domain') Rails.cache.write("exclude_domains_for:#{account.id}", 'domain') block.destroy! - expect(Rails.cache.exist?("exclude_domains_for:#{account.id}")).to eq false + expect(Rails.cache.exist?("exclude_domains_for:#{account.id}")).to be false end end diff --git a/spec/models/account_filter_spec.rb b/spec/models/account_filter_spec.rb index c2bd8c220..fa47b5954 100644 --- a/spec/models/account_filter_spec.rb +++ b/spec/models/account_filter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe AccountFilter do @@ -16,4 +18,49 @@ describe AccountFilter do expect { filter.results }.to raise_error(/wrong/) end end + + describe 'with origin and by_domain interacting' do + let!(:local_account) { Fabricate(:account, domain: nil) } + let!(:remote_account_one) { Fabricate(:account, domain: 'example.org') } + let(:remote_account_two) { Fabricate(:account, domain: 'other.domain') } + + it 'works with domain first and origin remote' do + filter = described_class.new(by_domain: 'example.org', origin: 'remote') + expect(filter.results).to contain_exactly(remote_account_one) + end + + it 'works with domain last and origin remote' do + filter = described_class.new(origin: 'remote', by_domain: 'example.org') + expect(filter.results).to contain_exactly(remote_account_one) + end + + it 'works with domain first and origin local' do + filter = described_class.new(by_domain: 'example.org', origin: 'local') + expect(filter.results).to contain_exactly(local_account) + end + + it 'works with domain last and origin local' do + filter = described_class.new(origin: 'local', by_domain: 'example.org') + expect(filter.results).to contain_exactly(remote_account_one) + end + end + + describe 'with username' do + let!(:local_account) { Fabricate(:account, domain: nil, username: 'validUserName') } + + it 'works with @ at the beginning of the username' do + filter = described_class.new(username: '@validUserName') + expect(filter.results).to contain_exactly(local_account) + end + + it 'does not work with more than one @ at the beginning of the username' do + filter = described_class.new(username: '@@validUserName') + expect(filter.results).to_not contain_exactly(local_account) + end + + it 'does not work with @ outside the beginning of the username' do + filter = described_class.new(username: 'validUserName@') + expect(filter.results).to_not contain_exactly(local_account) + end + end end diff --git a/spec/models/account_migration_spec.rb b/spec/models/account_migration_spec.rb index 8461b4b28..1f32c6082 100644 --- a/spec/models/account_migration_spec.rb +++ b/spec/models/account_migration_spec.rb @@ -1,5 +1,50 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe AccountMigration, type: :model do +RSpec.describe AccountMigration do + describe 'validations' do + subject { described_class.new(account: source_account, acct: target_acct) } + let(:source_account) { Fabricate(:account) } + let(:target_acct) { target_account.acct } + + context 'with valid properties' do + let(:target_account) { Fabricate(:account, username: 'target', domain: 'remote.org') } + + before do + target_account.aliases.create!(acct: source_account.acct) + + service_double = instance_double(ResolveAccountService) + allow(ResolveAccountService).to receive(:new).and_return(service_double) + allow(service_double).to receive(:call).with(target_acct, anything).and_return(target_account) + end + + it 'passes validations' do + expect(subject).to be_valid + end + end + + context 'with unresolvable account' do + let(:target_acct) { 'target@remote' } + + before do + service_double = instance_double(ResolveAccountService) + allow(ResolveAccountService).to receive(:new).and_return(service_double) + allow(service_double).to receive(:call).with(target_acct, anything).and_return(nil) + end + + it 'has errors on acct field' do + expect(subject).to model_have_error_on_field(:acct) + end + end + + context 'with a space in the domain part' do + let(:target_acct) { 'target@remote. org' } + + it 'has errors on acct field' do + expect(subject).to model_have_error_on_field(:acct) + end + end + end end diff --git a/spec/models/account_moderation_note_spec.rb b/spec/models/account_moderation_note_spec.rb deleted file mode 100644 index 69bd5500a..000000000 --- a/spec/models/account_moderation_note_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe AccountModerationNote, type: :model do -end diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index a40dfae96..fc7a43110 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -1,10 +1,13 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Account, type: :model do - context do - let(:bob) { Fabricate(:account, username: 'bob') } +RSpec.describe Account do + context 'with an account record' do subject { Fabricate(:account) } + let(:bob) { Fabricate(:account, username: 'bob') } + describe '#suspend!' do it 'marks the account as suspended' do subject.suspend! @@ -17,7 +20,9 @@ RSpec.describe Account, type: :model do end context 'when the account is of a local user' do - let!(:subject) { Fabricate(:user, email: 'foo+bar@domain.org').account } + subject { local_user_account } + + let!(:local_user_account) { Fabricate(:user, email: 'foo+bar@domain.org').account } it 'creates a canonical domain block' do subject.suspend! @@ -30,7 +35,7 @@ RSpec.describe Account, type: :model do end it 'does not raise an error' do - expect { subject.suspend! }.not_to raise_error + expect { subject.suspend! }.to_not raise_error end end end @@ -86,14 +91,14 @@ RSpec.describe Account, type: :model do end describe 'Local domain user methods' do + subject { Fabricate(:account, domain: nil, username: 'alice') } + around do |example| before = Rails.configuration.x.local_domain example.run Rails.configuration.x.local_domain = before end - subject { Fabricate(:account, domain: nil, username: 'alice') } - describe '#to_webfinger_s' do it 'returns a webfinger string for the account' do Rails.configuration.x.local_domain = 'example.com' @@ -159,8 +164,8 @@ RSpec.describe Account, type: :model do it 'sets default avatar, header, avatar_remote_url, and header_remote_url' do expect(account.avatar_remote_url).to eq 'https://remote.test/invalid_avatar' expect(account.header_remote_url).to eq expectation.header_remote_url - expect(account.avatar_file_name).to eq nil - expect(account.header_file_name).to eq nil + expect(account.avatar_file_name).to be_nil + expect(account.header_file_name).to eq expectation.header_file_name end end end @@ -168,7 +173,7 @@ RSpec.describe Account, type: :model do describe '#possibly_stale?' do let(:account) { Fabricate(:account, last_webfingered_at: last_webfingered_at) } - context 'last_webfingered_at is nil' do + context 'when last_webfingered_at is nil' do let(:last_webfingered_at) { nil } it 'returns true' do @@ -176,7 +181,7 @@ RSpec.describe Account, type: :model do end end - context 'last_webfingered_at is more than 24 hours before' do + context 'when last_webfingered_at is more than 24 hours before' do let(:last_webfingered_at) { 25.hours.ago } it 'returns true' do @@ -184,7 +189,7 @@ RSpec.describe Account, type: :model do end end - context 'last_webfingered_at is less than 24 hours before' do + context 'when last_webfingered_at is less than 24 hours before' do let(:last_webfingered_at) { 23.hours.ago } it 'returns false' do @@ -197,7 +202,7 @@ RSpec.describe Account, type: :model do let(:account) { Fabricate(:account, domain: domain) } let(:acct) { account.acct } - context 'domain is nil' do + context 'when domain is nil' do let(:domain) { nil } it 'returns nil' do @@ -205,12 +210,12 @@ RSpec.describe Account, type: :model do end it 'calls not ResolveAccountService#call' do - expect_any_instance_of(ResolveAccountService).not_to receive(:call).with(acct) + expect_any_instance_of(ResolveAccountService).to_not receive(:call).with(acct) account.refresh! end end - context 'domain is present' do + context 'when domain is present' do let(:domain) { 'example.com' } it 'calls ResolveAccountService#call' do @@ -242,13 +247,13 @@ RSpec.describe Account, type: :model do end describe '#favourited?' do + subject { Fabricate(:account) } + let(:original_status) do author = Fabricate(:account, username: 'original') Fabricate(:status, account: author) end - subject { Fabricate(:account) } - context 'when the status is a reblog of another status' do let(:original_reblog) do author = Fabricate(:account, username: 'original_reblogger') @@ -258,11 +263,11 @@ RSpec.describe Account, type: :model do it 'is true when this account has favourited it' do Fabricate(:favourite, status: original_reblog, account: subject) - expect(subject.favourited?(original_status)).to eq true + expect(subject.favourited?(original_status)).to be true end it 'is false when this account has not favourited it' do - expect(subject.favourited?(original_status)).to eq false + expect(subject.favourited?(original_status)).to be false end end @@ -270,23 +275,23 @@ RSpec.describe Account, type: :model do it 'is true when this account has favourited it' do Fabricate(:favourite, status: original_status, account: subject) - expect(subject.favourited?(original_status)).to eq true + expect(subject.favourited?(original_status)).to be true end it 'is false when this account has not favourited it' do - expect(subject.favourited?(original_status)).to eq false + expect(subject.favourited?(original_status)).to be false end end end describe '#reblogged?' do + subject { Fabricate(:account) } + let(:original_status) do author = Fabricate(:account, username: 'original') Fabricate(:status, account: author) end - subject { Fabricate(:account) } - context 'when the status is a reblog of another status' do let(:original_reblog) do author = Fabricate(:account, username: 'original_reblogger') @@ -296,11 +301,11 @@ RSpec.describe Account, type: :model do it 'is true when this account has reblogged it' do Fabricate(:status, reblog: original_reblog, account: subject) - expect(subject.reblogged?(original_reblog)).to eq true + expect(subject.reblogged?(original_reblog)).to be true end it 'is false when this account has not reblogged it' do - expect(subject.reblogged?(original_reblog)).to eq false + expect(subject.reblogged?(original_reblog)).to be false end end @@ -308,11 +313,11 @@ RSpec.describe Account, type: :model do it 'is true when this account has reblogged it' do Fabricate(:status, reblog: original_status, account: subject) - expect(subject.reblogged?(original_status)).to eq true + expect(subject.reblogged?(original_status)).to be true end it 'is false when this account has not reblogged it' do - expect(subject.reblogged?(original_status)).to eq false + expect(subject.reblogged?(original_status)).to be false end end end @@ -336,7 +341,7 @@ RSpec.describe Account, type: :model do it 'returns the domains blocked by the account' do account = Fabricate(:account) account.block_domain!('domain') - expect(account.excluded_from_timeline_domains).to match_array ['domain'] + expect(account.excluded_from_timeline_domains).to contain_exactly('domain') end end @@ -344,9 +349,9 @@ RSpec.describe Account, type: :model do before do _missing = Fabricate( :account, - display_name: "Missing", - username: "missing", - domain: "missing.com" + display_name: 'Missing', + username: 'missing', + domain: 'missing.com' ) end @@ -359,7 +364,7 @@ RSpec.describe Account, type: :model do suspended: true ) - results = Account.search_for('username') + results = described_class.search_for('username') expect(results).to eq [] end @@ -372,7 +377,7 @@ RSpec.describe Account, type: :model do match.user.update(approved: false) - results = Account.search_for('username') + results = described_class.search_for('username') expect(results).to eq [] end @@ -385,7 +390,7 @@ RSpec.describe Account, type: :model do match.user.update(confirmed_at: nil) - results = Account.search_for('username') + results = described_class.search_for('username') expect(results).to eq [] end @@ -397,65 +402,65 @@ RSpec.describe Account, type: :model do domain: 'example.com' ) - results = Account.search_for('A?l\i:c e') + results = described_class.search_for('A?l\i:c e') expect(results).to eq [match] end it 'finds accounts with matching display_name' do match = Fabricate( :account, - display_name: "Display Name", - username: "username", - domain: "example.com" + display_name: 'Display Name', + username: 'username', + domain: 'example.com' ) - results = Account.search_for("display") + results = described_class.search_for('display') expect(results).to eq [match] end it 'finds accounts with matching username' do match = Fabricate( :account, - display_name: "Display Name", - username: "username", - domain: "example.com" + display_name: 'Display Name', + username: 'username', + domain: 'example.com' ) - results = Account.search_for("username") + results = described_class.search_for('username') expect(results).to eq [match] end it 'finds accounts with matching domain' do match = Fabricate( :account, - display_name: "Display Name", - username: "username", - domain: "example.com" + display_name: 'Display Name', + username: 'username', + domain: 'example.com' ) - results = Account.search_for("example") + results = described_class.search_for('example') expect(results).to eq [match] end it 'limits by 10 by default' do - 11.times.each { Fabricate(:account, display_name: "Display Name") } - results = Account.search_for("display") + 11.times.each { Fabricate(:account, display_name: 'Display Name') } + results = described_class.search_for('display') expect(results.size).to eq 10 end it 'accepts arbitrary limits' do - 2.times.each { Fabricate(:account, display_name: "Display Name") } - results = Account.search_for("display", limit: 1) + 2.times.each { Fabricate(:account, display_name: 'Display Name') } + results = described_class.search_for('display', limit: 1) expect(results.size).to eq 1 end it 'ranks multiple matches higher' do matches = [ - { username: "username", display_name: "username" }, - { display_name: "Display Name", username: "username", domain: "example.com" }, + { username: 'username', display_name: 'username' }, + { display_name: 'Display Name', username: 'username', domain: 'example.com' }, ].map(&method(:Fabricate).curry(2).call(:account)) - results = Account.search_for("username") + results = described_class.search_for('username') expect(results).to eq matches end end @@ -473,7 +478,7 @@ RSpec.describe Account, type: :model do ) account.follow!(match) - results = Account.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) + results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) expect(results).to eq [match] end @@ -485,7 +490,7 @@ RSpec.describe Account, type: :model do domain: 'example.com' ) - results = Account.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) + results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true) expect(results).to eq [] end @@ -498,7 +503,7 @@ RSpec.describe Account, type: :model do suspended: true ) - results = Account.advanced_search_for('username', account, limit: 10, following: true) + results = described_class.advanced_search_for('username', account, limit: 10, following: true) expect(results).to eq [] end @@ -511,7 +516,7 @@ RSpec.describe Account, type: :model do match.user.update(approved: false) - results = Account.advanced_search_for('username', account, limit: 10, following: true) + results = described_class.advanced_search_for('username', account, limit: 10, following: true) expect(results).to eq [] end @@ -524,7 +529,7 @@ RSpec.describe Account, type: :model do match.user.update(confirmed_at: nil) - results = Account.advanced_search_for('username', account, limit: 10, following: true) + results = described_class.advanced_search_for('username', account, limit: 10, following: true) expect(results).to eq [] end end @@ -538,7 +543,7 @@ RSpec.describe Account, type: :model do suspended: true ) - results = Account.advanced_search_for('username', account) + results = described_class.advanced_search_for('username', account) expect(results).to eq [] end @@ -551,7 +556,7 @@ RSpec.describe Account, type: :model do match.user.update(approved: false) - results = Account.advanced_search_for('username', account) + results = described_class.advanced_search_for('username', account) expect(results).to eq [] end @@ -564,7 +569,7 @@ RSpec.describe Account, type: :model do match.user.update(confirmed_at: nil) - results = Account.advanced_search_for('username', account) + results = described_class.advanced_search_for('username', account) expect(results).to eq [] end @@ -576,28 +581,28 @@ RSpec.describe Account, type: :model do domain: 'example.com' ) - results = Account.advanced_search_for('A?l\i:c e', account) + results = described_class.advanced_search_for('A?l\i:c e', account) expect(results).to eq [match] end it 'limits by 10 by default' do - 11.times { Fabricate(:account, display_name: "Display Name") } - results = Account.advanced_search_for("display", account) + 11.times { Fabricate(:account, display_name: 'Display Name') } + results = described_class.advanced_search_for('display', account) expect(results.size).to eq 10 end it 'accepts arbitrary limits' do - 2.times { Fabricate(:account, display_name: "Display Name") } - results = Account.advanced_search_for("display", account, limit: 1) + 2.times { Fabricate(:account, display_name: 'Display Name') } + results = described_class.advanced_search_for('display', account, limit: 1) expect(results.size).to eq 1 end it 'ranks followed accounts higher' do - match = Fabricate(:account, username: "Matching") - followed_match = Fabricate(:account, username: "Matcher") + match = Fabricate(:account, username: 'Matching') + followed_match = Fabricate(:account, username: 'Matcher') Fabricate(:follow, account: account, target_account: followed_match) - results = Account.advanced_search_for("match", account) + results = described_class.advanced_search_for('match', account) expect(results).to eq [followed_match, match] expect(results.first.rank).to be > results.last.rank end @@ -636,25 +641,31 @@ RSpec.describe Account, type: :model do describe '.following_map' do it 'returns an hash' do - expect(Account.following_map([], 1)).to be_a Hash + expect(described_class.following_map([], 1)).to be_a Hash end end describe '.followed_by_map' do it 'returns an hash' do - expect(Account.followed_by_map([], 1)).to be_a Hash + expect(described_class.followed_by_map([], 1)).to be_a Hash end end describe '.blocking_map' do it 'returns an hash' do - expect(Account.blocking_map([], 1)).to be_a Hash + expect(described_class.blocking_map([], 1)).to be_a Hash end end describe '.requested_map' do it 'returns an hash' do - expect(Account.requested_map([], 1)).to be_a Hash + expect(described_class.requested_map([], 1)).to be_a Hash + end + end + + describe '.requested_by_map' do + it 'returns an hash' do + expect(described_class.requested_by_map([], 1)).to be_a Hash end end @@ -695,12 +706,6 @@ RSpec.describe Account, type: :model do end describe 'validations' do - it 'has a valid fabricator' do - account = Fabricate.build(:account) - account.valid? - expect(account).to be_valid - end - it 'is invalid without a username' do account = Fabricate.build(:account, username: nil) account.valid? @@ -804,19 +809,19 @@ RSpec.describe Account, type: :model do it 'is valid even if the username is longer than 30 characters' do account = Fabricate.build(:account, domain: 'domain', username: Faker::Lorem.characters(number: 31)) account.valid? - expect(account).not_to model_have_error_on_field(:username) + expect(account).to_not model_have_error_on_field(:username) end it 'is valid even if the display name is longer than 30 characters' do account = Fabricate.build(:account, domain: 'domain', display_name: Faker::Lorem.characters(number: 31)) account.valid? - expect(account).not_to model_have_error_on_field(:display_name) + expect(account).to_not model_have_error_on_field(:display_name) end it 'is valid even if the note is longer than 500 characters' do account = Fabricate.build(:account, domain: 'domain', note: Faker::Lorem.characters(number: 501)) account.valid? - expect(account).not_to model_have_error_on_field(:note) + expect(account).to_not model_have_error_on_field(:note) end end end @@ -831,7 +836,7 @@ RSpec.describe Account, type: :model do { username: 'b', domain: 'b' }, ].map(&method(:Fabricate).curry(2).call(:account)) - expect(Account.where('id > 0').alphabetic).to eq matches + expect(described_class.where('id > 0').alphabetic).to eq matches end end @@ -840,7 +845,7 @@ RSpec.describe Account, type: :model do match = Fabricate(:account, display_name: 'pattern and suffix') Fabricate(:account, display_name: 'prefix and pattern') - expect(Account.matches_display_name('pattern')).to eq [match] + expect(described_class.matches_display_name('pattern')).to eq [match] end end @@ -849,24 +854,24 @@ RSpec.describe Account, type: :model do match = Fabricate(:account, username: 'pattern_and_suffix') Fabricate(:account, username: 'prefix_and_pattern') - expect(Account.matches_username('pattern')).to eq [match] + expect(described_class.matches_username('pattern')).to eq [match] end end describe 'by_domain_and_subdomains' do it 'returns exact domain matches' do account = Fabricate(:account, domain: 'example.com') - expect(Account.by_domain_and_subdomains('example.com')).to eq [account] + expect(described_class.by_domain_and_subdomains('example.com')).to eq [account] end it 'returns subdomains' do account = Fabricate(:account, domain: 'foo.example.com') - expect(Account.by_domain_and_subdomains('example.com')).to eq [account] + expect(described_class.by_domain_and_subdomains('example.com')).to eq [account] end it 'does not return partially matching domains' do account = Fabricate(:account, domain: 'grexample.com') - expect(Account.by_domain_and_subdomains('example.com')).to_not eq [account] + expect(described_class.by_domain_and_subdomains('example.com')).to_not eq [account] end end @@ -874,7 +879,7 @@ RSpec.describe Account, type: :model do it 'returns an array of accounts who have a domain' do account_1 = Fabricate(:account, domain: nil) account_2 = Fabricate(:account, domain: 'example.com') - expect(Account.remote).to match_array([account_2]) + expect(described_class.remote).to contain_exactly(account_2) end end @@ -882,25 +887,25 @@ RSpec.describe Account, type: :model do it 'returns an array of accounts who do not have a domain' do account_1 = Fabricate(:account, domain: nil) account_2 = Fabricate(:account, domain: 'example.com') - expect(Account.where('id > 0').local).to match_array([account_1]) + expect(described_class.where('id > 0').local).to contain_exactly(account_1) end end describe 'partitioned' do it 'returns a relation of accounts partitioned by domain' do - matches = ['a', 'b', 'a', 'b'] + matches = %w(a b a b) matches.size.times.to_a.shuffle.each do |index| matches[index] = Fabricate(:account, domain: matches[index]) end - expect(Account.where('id > 0').partitioned).to match_array(matches) + expect(described_class.where('id > 0').partitioned).to match_array(matches) end end describe 'recent' do it 'returns a relation of accounts sorted by recent creation' do - matches = 2.times.map { Fabricate(:account) } - expect(Account.where('id > 0').recent).to match_array(matches) + matches = Array.new(2) { Fabricate(:account) } + expect(described_class.where('id > 0').recent).to match_array(matches) end end @@ -908,7 +913,7 @@ RSpec.describe Account, type: :model do it 'returns an array of accounts who are silenced' do account_1 = Fabricate(:account, silenced: true) account_2 = Fabricate(:account, silenced: false) - expect(Account.silenced).to match_array([account_1]) + expect(described_class.silenced).to contain_exactly(account_1) end end @@ -916,7 +921,7 @@ RSpec.describe Account, type: :model do it 'returns an array of accounts who are suspended' do account_1 = Fabricate(:account, suspended: true) account_2 = Fabricate(:account, suspended: false) - expect(Account.suspended).to match_array([account_1]) + expect(described_class.suspended).to contain_exactly(account_1) end end @@ -938,32 +943,32 @@ RSpec.describe Account, type: :model do end it 'returns every usable non-suspended account' do - expect(Account.searchable).to match_array([silenced_local, silenced_remote, local_account, remote_account]) + expect(described_class.searchable).to contain_exactly(silenced_local, silenced_remote, local_account, remote_account) end it 'does not mess with previously-applied scopes' do - expect(Account.where.not(id: remote_account.id).searchable).to match_array([silenced_local, silenced_remote, local_account]) + expect(described_class.where.not(id: remote_account.id).searchable).to contain_exactly(silenced_local, silenced_remote, local_account) end end end context 'when is local' do - # Test disabled because test environment omits autogenerating keys for performance - xit 'generates keys' do - account = Account.create!(domain: nil, username: Faker::Internet.user_name(separators: ['_'])) - expect(account.keypair.private?).to eq true + it 'generates keys' do + account = described_class.create!(domain: nil, username: Faker::Internet.user_name(separators: ['_'])) + expect(account.keypair).to be_private + expect(account.keypair).to be_public end end context 'when is remote' do it 'does not generate keys' do key = OpenSSL::PKey::RSA.new(1024).public_key - account = Account.create!(domain: 'remote', username: Faker::Internet.user_name(separators: ['_']), public_key: key.to_pem) + account = described_class.create!(domain: 'remote', uri: 'https://remote/actor', username: Faker::Internet.user_name(separators: ['_']), public_key: key.to_pem) expect(account.keypair.params).to eq key.params end it 'normalizes domain' do - account = Account.create!(domain: 'にゃん', username: Faker::Internet.user_name(separators: ['_'])) + account = described_class.create!(domain: 'にゃん', uri: 'https://xn--r9j5b5b/actor', username: Faker::Internet.user_name(separators: ['_'])) expect(account.domain).to eq 'xn--r9j5b5b' end end @@ -983,7 +988,7 @@ RSpec.describe Account, type: :model do threads = Array.new(increment_by) do Thread.new do true while wait_for_start - Account.find(subject.id).increment_count!(:followers_count) + described_class.find(subject.id).increment_count!(:followers_count) end end diff --git a/spec/models/account_statuses_cleanup_policy_spec.rb b/spec/models/account_statuses_cleanup_policy_spec.rb index b01321a20..7405bdfa2 100644 --- a/spec/models/account_statuses_cleanup_policy_spec.rb +++ b/spec/models/account_statuses_cleanup_policy_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe AccountStatusesCleanupPolicy, type: :model do +RSpec.describe AccountStatusesCleanupPolicy do let(:account) { Fabricate(:account, username: 'alice', domain: nil) } describe 'validation' do @@ -16,16 +18,15 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do context 'when widening a policy' do let!(:account_statuses_cleanup_policy) do Fabricate(:account_statuses_cleanup_policy, - account: account, - keep_direct: true, - keep_pinned: true, - keep_polls: true, - keep_media: true, - keep_self_fav: true, - keep_self_bookmark: true, - min_favs: 1, - min_reblogs: 1 - ) + account: account, + keep_direct: true, + keep_pinned: true, + keep_polls: true, + keep_media: true, + keep_self_fav: true, + keep_self_bookmark: true, + min_favs: 1, + min_reblogs: 1) end before do @@ -35,77 +36,76 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do it 'invalidates last_inspected when widened because of keep_direct' do account_statuses_cleanup_policy.keep_direct = false account_statuses_cleanup_policy.save - expect(account_statuses_cleanup_policy.last_inspected).to be nil + expect(account_statuses_cleanup_policy.last_inspected).to be_nil end it 'invalidates last_inspected when widened because of keep_pinned' do account_statuses_cleanup_policy.keep_pinned = false account_statuses_cleanup_policy.save - expect(account_statuses_cleanup_policy.last_inspected).to be nil + expect(account_statuses_cleanup_policy.last_inspected).to be_nil end it 'invalidates last_inspected when widened because of keep_polls' do account_statuses_cleanup_policy.keep_polls = false account_statuses_cleanup_policy.save - expect(account_statuses_cleanup_policy.last_inspected).to be nil + expect(account_statuses_cleanup_policy.last_inspected).to be_nil end it 'invalidates last_inspected when widened because of keep_media' do account_statuses_cleanup_policy.keep_media = false account_statuses_cleanup_policy.save - expect(account_statuses_cleanup_policy.last_inspected).to be nil + expect(account_statuses_cleanup_policy.last_inspected).to be_nil end it 'invalidates last_inspected when widened because of keep_self_fav' do account_statuses_cleanup_policy.keep_self_fav = false account_statuses_cleanup_policy.save - expect(account_statuses_cleanup_policy.last_inspected).to be nil + expect(account_statuses_cleanup_policy.last_inspected).to be_nil end it 'invalidates last_inspected when widened because of keep_self_bookmark' do account_statuses_cleanup_policy.keep_self_bookmark = false account_statuses_cleanup_policy.save - expect(account_statuses_cleanup_policy.last_inspected).to be nil + expect(account_statuses_cleanup_policy.last_inspected).to be_nil end it 'invalidates last_inspected when widened because of higher min_favs' do account_statuses_cleanup_policy.min_favs = 5 account_statuses_cleanup_policy.save - expect(account_statuses_cleanup_policy.last_inspected).to be nil + expect(account_statuses_cleanup_policy.last_inspected).to be_nil end it 'invalidates last_inspected when widened because of disabled min_favs' do account_statuses_cleanup_policy.min_favs = nil account_statuses_cleanup_policy.save - expect(account_statuses_cleanup_policy.last_inspected).to be nil + expect(account_statuses_cleanup_policy.last_inspected).to be_nil end it 'invalidates last_inspected when widened because of higher min_reblogs' do account_statuses_cleanup_policy.min_reblogs = 5 account_statuses_cleanup_policy.save - expect(account_statuses_cleanup_policy.last_inspected).to be nil + expect(account_statuses_cleanup_policy.last_inspected).to be_nil end it 'invalidates last_inspected when widened because of disable min_reblogs' do account_statuses_cleanup_policy.min_reblogs = nil account_statuses_cleanup_policy.save - expect(account_statuses_cleanup_policy.last_inspected).to be nil + expect(account_statuses_cleanup_policy.last_inspected).to be_nil end end context 'when narrowing a policy' do let!(:account_statuses_cleanup_policy) do Fabricate(:account_statuses_cleanup_policy, - account: account, - keep_direct: false, - keep_pinned: false, - keep_polls: false, - keep_media: false, - keep_self_fav: false, - keep_self_bookmark: false, - min_favs: nil, - min_reblogs: nil - ) + account: account, + keep_direct: false, + keep_pinned: false, + keep_polls: false, + keep_media: false, + keep_self_fav: false, + keep_self_bookmark: false, + min_favs: nil, + min_reblogs: nil) end it 'does not unnecessarily invalidate last_inspected' do @@ -134,9 +134,10 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end describe '#invalidate_last_inspected' do + subject { account_statuses_cleanup_policy.invalidate_last_inspected(status, action) } + let(:account_statuses_cleanup_policy) { Fabricate(:account_statuses_cleanup_policy, account: account) } let(:status) { Fabricate(:status, id: 10, account: account) } - subject { account_statuses_cleanup_policy.invalidate_last_inspected(status, action) } before do account_statuses_cleanup_policy.record_last_inspected(42) @@ -232,11 +233,11 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end describe '#compute_cutoff_id' do - let!(:unrelated_status) { Fabricate(:status, created_at: 3.years.ago) } - let(:account_statuses_cleanup_policy) { Fabricate(:account_statuses_cleanup_policy, account: account) } - subject { account_statuses_cleanup_policy.compute_cutoff_id } + let!(:unrelated_status) { Fabricate(:status, created_at: 3.years.ago) } + let(:account_statuses_cleanup_policy) { Fabricate(:account_statuses_cleanup_policy, account: account) } + context 'when the account has posted multiple toots' do let!(:very_old_status) { Fabricate(:status, created_at: 3.years.ago, account: account) } let!(:old_status) { Fabricate(:status, created_at: 3.weeks.ago, account: account) } @@ -255,18 +256,20 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end describe '#statuses_to_delete' do + subject { account_statuses_cleanup_policy.statuses_to_delete } + let!(:unrelated_status) { Fabricate(:status, created_at: 3.years.ago) } let!(:very_old_status) { Fabricate(:status, created_at: 3.years.ago, account: account) } let!(:pinned_status) { Fabricate(:status, created_at: 1.year.ago, account: account) } let!(:direct_message) { Fabricate(:status, created_at: 1.year.ago, account: account, visibility: :direct) } let!(:self_faved) { Fabricate(:status, created_at: 1.year.ago, account: account) } let!(:self_bookmarked) { Fabricate(:status, created_at: 1.year.ago, account: account) } - let!(:status_with_poll) { Fabricate(:status, created_at: 1.year.ago, account: account, poll_attributes: { account: account, voters_count: 0, options: ['a', 'b'], expires_in: 2.days }) } + let!(:status_with_poll) { Fabricate(:status, created_at: 1.year.ago, account: account, poll_attributes: { account: account, voters_count: 0, options: %w(a b), expires_in: 2.days }) } let!(:status_with_media) { Fabricate(:status, created_at: 1.year.ago, account: account) } - let!(:faved4) { Fabricate(:status, created_at: 1.year.ago, account: account) } - let!(:faved5) { Fabricate(:status, created_at: 1.year.ago, account: account) } - let!(:reblogged4) { Fabricate(:status, created_at: 1.year.ago, account: account) } - let!(:reblogged5) { Fabricate(:status, created_at: 1.year.ago, account: account) } + let!(:faved_primary) { Fabricate(:status, created_at: 1.year.ago, account: account) } + let!(:faved_secondary) { Fabricate(:status, created_at: 1.year.ago, account: account) } + let!(:reblogged_primary) { Fabricate(:status, created_at: 1.year.ago, account: account) } + let!(:reblogged_secondary) { Fabricate(:status, created_at: 1.year.ago, account: account) } let!(:recent_status) { Fabricate(:status, created_at: 2.days.ago, account: account) } let!(:media_attachment) { Fabricate(:media_attachment, account: account, status: status_with_media) } @@ -276,21 +279,19 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do let(:account_statuses_cleanup_policy) { Fabricate(:account_statuses_cleanup_policy, account: account) } - subject { account_statuses_cleanup_policy.statuses_to_delete } - before do - 4.times { faved4.increment_count!(:favourites_count) } - 5.times { faved5.increment_count!(:favourites_count) } - 4.times { reblogged4.increment_count!(:reblogs_count) } - 5.times { reblogged5.increment_count!(:reblogs_count) } + 4.times { faved_primary.increment_count!(:favourites_count) } + 5.times { faved_secondary.increment_count!(:favourites_count) } + 4.times { reblogged_primary.increment_count!(:reblogs_count) } + 5.times { reblogged_secondary.increment_count!(:reblogs_count) } end context 'when passed a max_id' do + subject { account_statuses_cleanup_policy.statuses_to_delete(50, old_status.id).pluck(:id) } + let!(:old_status) { Fabricate(:status, created_at: 1.year.ago, account: account) } let!(:slightly_less_old_status) { Fabricate(:status, created_at: 6.months.ago, account: account) } - subject { account_statuses_cleanup_policy.statuses_to_delete(50, old_status.id).pluck(:id) } - it 'returns statuses including max_id' do expect(subject).to include(old_status.id) end @@ -305,11 +306,11 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end context 'when passed a min_id' do + subject { account_statuses_cleanup_policy.statuses_to_delete(50, recent_status.id, old_status.id).pluck(:id) } + let!(:old_status) { Fabricate(:status, created_at: 1.year.ago, account: account) } let!(:slightly_less_old_status) { Fabricate(:status, created_at: 6.months.ago, account: account) } - subject { account_statuses_cleanup_policy.statuses_to_delete(50, recent_status.id, old_status.id).pluck(:id) } - it 'returns statuses including min_id' do expect(subject).to include(old_status.id) end @@ -358,7 +359,7 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end it 'returns every other old status for deletion' do - expect(subject.pluck(:id)).to include(very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id) + expect(subject.pluck(:id)).to include(very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id) end end @@ -377,7 +378,7 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end it 'returns every other old status for deletion' do - expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id) + expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, status_with_poll.id, status_with_media.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id) end end @@ -396,7 +397,7 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end it 'returns every other old status for deletion' do - expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id) + expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id) end end @@ -415,7 +416,7 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end it 'returns every other old status for deletion' do - expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id) + expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id) end end @@ -434,7 +435,7 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end it 'returns every other old status for deletion' do - expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id) + expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_media.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id) end end @@ -453,7 +454,7 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end it 'returns every other old status for deletion' do - expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id) + expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id) end end @@ -476,7 +477,7 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end it 'returns every other old status for deletion' do - expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id) + expect(subject.pluck(:id)).to include(direct_message.id, very_old_status.id, pinned_status.id, self_faved.id, self_bookmarked.id, status_with_poll.id, status_with_media.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id) end end @@ -495,7 +496,7 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end it 'returns only normal statuses for deletion' do - expect(subject.pluck(:id)).to match_array([very_old_status.id, faved4.id, faved5.id, reblogged4.id, reblogged5.id]) + expect(subject.pluck(:id)).to contain_exactly(very_old_status.id, faved_primary.id, faved_secondary.id, reblogged_primary.id, reblogged_secondary.id) end end @@ -509,7 +510,7 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end it 'does not return the toot reblogged 5 times' do - expect(subject.pluck(:id)).to_not include(reblogged5.id) + expect(subject.pluck(:id)).to_not include(reblogged_secondary.id) end it 'does not return the unrelated toot' do @@ -517,7 +518,7 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end it 'returns old statuses not reblogged as much' do - expect(subject.pluck(:id)).to include(very_old_status.id, faved4.id, faved5.id, reblogged4.id) + expect(subject.pluck(:id)).to include(very_old_status.id, faved_primary.id, faved_secondary.id, reblogged_primary.id) end end @@ -531,7 +532,7 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end it 'does not return the toot faved 5 times' do - expect(subject.pluck(:id)).to_not include(faved5.id) + expect(subject.pluck(:id)).to_not include(faved_secondary.id) end it 'does not return the unrelated toot' do @@ -539,7 +540,7 @@ RSpec.describe AccountStatusesCleanupPolicy, type: :model do end it 'returns old statuses not faved as much' do - expect(subject.pluck(:id)).to include(very_old_status.id, faved4.id, reblogged4.id, reblogged5.id) + expect(subject.pluck(:id)).to include(very_old_status.id, faved_primary.id, reblogged_primary.id, reblogged_secondary.id) end end end diff --git a/spec/models/account_warning_preset_spec.rb b/spec/models/account_warning_preset_spec.rb new file mode 100644 index 000000000..f171df7c9 --- /dev/null +++ b/spec/models/account_warning_preset_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe AccountWarningPreset do + describe 'alphabetical' do + let(:first) { Fabricate(:account_warning_preset, title: 'aaa', text: 'aaa') } + let(:second) { Fabricate(:account_warning_preset, title: 'bbb', text: 'aaa') } + let(:third) { Fabricate(:account_warning_preset, title: 'bbb', text: 'bbb') } + + it 'returns records in order of title and text' do + results = described_class.alphabetic + + expect(results).to eq([first, second, third]) + end + end +end diff --git a/spec/models/admin/account_action_spec.rb b/spec/models/admin/account_action_spec.rb index b6a052b76..b47561dd4 100644 --- a/spec/models/admin/account_action_spec.rb +++ b/spec/models/admin/account_action_spec.rb @@ -1,23 +1,26 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Admin::AccountAction, type: :model do +RSpec.describe Admin::AccountAction do let(:account_action) { described_class.new } describe '#save!' do subject { account_action.save! } + let(:account) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:target_account) { Fabricate(:account) } let(:type) { 'disable' } before do account_action.assign_attributes( - type: type, + type: type, current_account: account, - target_account: target_account + target_account: target_account ) end - context 'type is "disable"' do + context 'when type is "disable"' do let(:type) { 'disable' } it 'disable user' do @@ -26,7 +29,7 @@ RSpec.describe Admin::AccountAction, type: :model do end end - context 'type is "silence"' do + context 'when type is "silence"' do let(:type) { 'silence' } it 'silences account' do @@ -35,7 +38,7 @@ RSpec.describe Admin::AccountAction, type: :model do end end - context 'type is "suspend"' do + context 'when type is "suspend"' do let(:type) { 'suspend' } it 'suspends account' do @@ -52,10 +55,26 @@ RSpec.describe Admin::AccountAction, type: :model do end end + context 'when type is invalid' do + let(:type) { 'whatever' } + + it 'raises an invalid record error' do + expect { subject }.to raise_error(ActiveRecord::RecordInvalid) + end + end + + context 'when type is not given' do + let(:type) { '' } + + it 'raises an invalid record error' do + expect { subject }.to raise_error(ActiveRecord::RecordInvalid) + end + end + it 'creates Admin::ActionLog' do expect do subject - end.to change { Admin::ActionLog.count }.by 1 + end.to change(Admin::ActionLog, :count).by 1 end it 'calls process_email!' do @@ -72,7 +91,7 @@ RSpec.describe Admin::AccountAction, type: :model do describe '#report' do subject { account_action.report } - context 'report_id.present?' do + context 'with report_id.present?' do before do account_action.report_id = Fabricate(:report).id end @@ -82,7 +101,7 @@ RSpec.describe Admin::AccountAction, type: :model do end end - context '!report_id.present?' do + context 'with !report_id.present?' do it 'returns nil' do expect(subject).to be_nil end @@ -92,7 +111,7 @@ RSpec.describe Admin::AccountAction, type: :model do describe '#with_report?' do subject { account_action.with_report? } - context '!report.nil?' do + context 'with !report.nil?' do before do account_action.report_id = Fabricate(:report).id end @@ -102,7 +121,7 @@ RSpec.describe Admin::AccountAction, type: :model do end end - context '!(!report.nil?)' do + context 'with !(!report.nil?)' do it 'returns false' do expect(subject).to be false end @@ -112,7 +131,7 @@ RSpec.describe Admin::AccountAction, type: :model do describe '.types_for_account' do subject { described_class.types_for_account(account) } - context 'account.local?' do + context 'when Account.local?' do let(:account) { Fabricate(:account, domain: nil) } it 'returns ["none", "disable", "sensitive", "silence", "suspend"]' do @@ -120,7 +139,7 @@ RSpec.describe Admin::AccountAction, type: :model do end end - context '!account.local?' do + context 'with !account.local?' do let(:account) { Fabricate(:account, domain: 'hoge.com') } it 'returns ["sensitive", "silence", "suspend"]' do diff --git a/spec/models/admin/action_log_spec.rb b/spec/models/admin/action_log_spec.rb index 3495cc514..1e3649b83 100644 --- a/spec/models/admin/action_log_spec.rb +++ b/spec/models/admin/action_log_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe Admin::ActionLog, type: :model do +RSpec.describe Admin::ActionLog do describe '#action' do it 'returns action' do action_log = described_class.new(action: 'hoge') diff --git a/spec/models/admin/appeal_filter_spec.rb b/spec/models/admin/appeal_filter_spec.rb new file mode 100644 index 000000000..e840bc3bc --- /dev/null +++ b/spec/models/admin/appeal_filter_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::AppealFilter do + describe '#results' do + let(:approved_appeal) { Fabricate(:appeal, approved_at: 10.days.ago) } + let(:not_approved_appeal) { Fabricate(:appeal, approved_at: nil) } + + it 'returns filtered appeals' do + filter = described_class.new(status: 'approved') + + expect(filter.results).to eq([approved_appeal]) + end + end +end diff --git a/spec/models/announcement_mute_spec.rb b/spec/models/announcement_mute_spec.rb deleted file mode 100644 index 9d0e4c903..000000000 --- a/spec/models/announcement_mute_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe AnnouncementMute, type: :model do -end diff --git a/spec/models/announcement_reaction_spec.rb b/spec/models/announcement_reaction_spec.rb deleted file mode 100644 index f6e151584..000000000 --- a/spec/models/announcement_reaction_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe AnnouncementReaction, type: :model do -end diff --git a/spec/models/announcement_spec.rb b/spec/models/announcement_spec.rb deleted file mode 100644 index 7f7b647a9..000000000 --- a/spec/models/announcement_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe Announcement, type: :model do -end diff --git a/spec/models/appeal_spec.rb b/spec/models/appeal_spec.rb index 14062dc4f..12373a949 100644 --- a/spec/models/appeal_spec.rb +++ b/spec/models/appeal_spec.rb @@ -1,5 +1,38 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Appeal, type: :model do - pending "add some examples to (or delete) #{__FILE__}" +describe Appeal do + describe 'scopes' do + describe 'approved' do + let(:approved_appeal) { Fabricate(:appeal, approved_at: 10.days.ago) } + let(:not_approved_appeal) { Fabricate(:appeal, approved_at: nil) } + + it 'finds the correct records' do + results = described_class.approved + expect(results).to eq([approved_appeal]) + end + end + + describe 'rejected' do + let(:rejected_appeal) { Fabricate(:appeal, rejected_at: 10.days.ago) } + let(:not_rejected_appeal) { Fabricate(:appeal, rejected_at: nil) } + + it 'finds the correct records' do + results = described_class.rejected + expect(results).to eq([rejected_appeal]) + end + end + + describe 'pending' do + let(:approved_appeal) { Fabricate(:appeal, approved_at: 10.days.ago) } + let(:rejected_appeal) { Fabricate(:appeal, rejected_at: 10.days.ago) } + let(:pending_appeal) { Fabricate(:appeal, rejected_at: nil, approved_at: nil) } + + it 'finds the correct records' do + results = described_class.pending + expect(results).to eq([pending_appeal]) + end + end + end end diff --git a/spec/models/backup_spec.rb b/spec/models/backup_spec.rb deleted file mode 100644 index 45230986d..000000000 --- a/spec/models/backup_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe Backup, type: :model do -end diff --git a/spec/models/block_spec.rb b/spec/models/block_spec.rb index acbdc77f5..8249503c5 100644 --- a/spec/models/block_spec.rb +++ b/spec/models/block_spec.rb @@ -1,12 +1,9 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Block, type: :model do +RSpec.describe Block do describe 'validations' do - it 'has a valid fabricator' do - block = Fabricate.build(:block) - expect(block).to be_valid - end - it 'is invalid without an account' do block = Fabricate.build(:block, account: nil) block.valid? @@ -26,22 +23,22 @@ RSpec.describe Block, type: :model do Rails.cache.write("exclude_account_ids_for:#{account.id}", []) Rails.cache.write("exclude_account_ids_for:#{target_account.id}", []) - Block.create!(account: account, target_account: target_account) + described_class.create!(account: account, target_account: target_account) - expect(Rails.cache.exist?("exclude_account_ids_for:#{account.id}")).to eq false - expect(Rails.cache.exist?("exclude_account_ids_for:#{target_account.id}")).to eq false + expect(Rails.cache.exist?("exclude_account_ids_for:#{account.id}")).to be false + expect(Rails.cache.exist?("exclude_account_ids_for:#{target_account.id}")).to be false end it 'removes blocking cache after destruction' do account = Fabricate(:account) target_account = Fabricate(:account) - block = Block.create!(account: account, target_account: target_account) + block = described_class.create!(account: account, target_account: target_account) Rails.cache.write("exclude_account_ids_for:#{account.id}", [target_account.id]) Rails.cache.write("exclude_account_ids_for:#{target_account.id}", [account.id]) block.destroy! - expect(Rails.cache.exist?("exclude_account_ids_for:#{account.id}")).to eq false - expect(Rails.cache.exist?("exclude_account_ids_for:#{target_account.id}")).to eq false + expect(Rails.cache.exist?("exclude_account_ids_for:#{account.id}")).to be false + expect(Rails.cache.exist?("exclude_account_ids_for:#{target_account.id}")).to be false end end diff --git a/spec/models/canonical_email_block_spec.rb b/spec/models/canonical_email_block_spec.rb index 8e0050d65..0acff8237 100644 --- a/spec/models/canonical_email_block_spec.rb +++ b/spec/models/canonical_email_block_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe CanonicalEmailBlock, type: :model do +RSpec.describe CanonicalEmailBlock do describe '#email=' do let(:target_hash) { '973dfe463ec85785f5f95af5ba3906eedb2d931c24e69824a89ea65dba4e813b' } diff --git a/spec/models/concerns/account_counters_spec.rb b/spec/models/concerns/account_counters_spec.rb index 4350496e7..fb02d79f1 100644 --- a/spec/models/concerns/account_counters_spec.rb +++ b/spec/models/concerns/account_counters_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe AccountCounters do diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb index 1d1898ab0..64d1d46d3 100644 --- a/spec/models/concerns/account_interactions_spec.rb +++ b/spec/models/concerns/account_interactions_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe AccountInteractions do @@ -11,16 +13,16 @@ describe AccountInteractions do describe '.following_map' do subject { Account.following_map(target_account_ids, account_id) } - context 'account with Follow' do + context 'when Account with Follow' do it 'returns { target_account_id => true }' do Fabricate(:follow, account: account, target_account: target_account) - is_expected.to eq(target_account_id => { reblogs: true, notify: false, languages: nil }) + expect(subject).to eq(target_account_id => { reblogs: true, notify: false, languages: nil }) end end - context 'account without Follow' do + context 'when Account without Follow' do it 'returns {}' do - is_expected.to eq({}) + expect(subject).to eq({}) end end end @@ -28,16 +30,16 @@ describe AccountInteractions do describe '.followed_by_map' do subject { Account.followed_by_map(target_account_ids, account_id) } - context 'account with Follow' do + context 'when Account with Follow' do it 'returns { target_account_id => true }' do Fabricate(:follow, account: target_account, target_account: account) - is_expected.to eq(target_account_id => true) + expect(subject).to eq(target_account_id => true) end end - context 'account without Follow' do + context 'when Account without Follow' do it 'returns {}' do - is_expected.to eq({}) + expect(subject).to eq({}) end end end @@ -45,16 +47,16 @@ describe AccountInteractions do describe '.blocking_map' do subject { Account.blocking_map(target_account_ids, account_id) } - context 'account with Block' do + context 'when Account with Block' do it 'returns { target_account_id => true }' do Fabricate(:block, account: account, target_account: target_account) - is_expected.to eq(target_account_id => true) + expect(subject).to eq(target_account_id => true) end end - context 'account without Block' do + context 'when Account without Block' do it 'returns {}' do - is_expected.to eq({}) + expect(subject).to eq({}) end end end @@ -62,31 +64,31 @@ describe AccountInteractions do describe '.muting_map' do subject { Account.muting_map(target_account_ids, account_id) } - context 'account with Mute' do + context 'when Account with Mute' do before do Fabricate(:mute, target_account: target_account, account: account, hide_notifications: hide) end - context 'if Mute#hide_notifications?' do + context 'when Mute#hide_notifications?' do let(:hide) { true } it 'returns { target_account_id => { notifications: true } }' do - is_expected.to eq(target_account_id => { notifications: true }) + expect(subject).to eq(target_account_id => { notifications: true }) end end - context 'unless Mute#hide_notifications?' do + context 'when not Mute#hide_notifications?' do let(:hide) { false } it 'returns { target_account_id => { notifications: false } }' do - is_expected.to eq(target_account_id => { notifications: false }) + expect(subject).to eq(target_account_id => { notifications: false }) end end end - context 'account without Mute' do + context 'when Account without Mute' do it 'returns {}' do - is_expected.to eq({}) + expect(subject).to eq({}) end end end @@ -94,7 +96,7 @@ describe AccountInteractions do describe '#follow!' do it 'creates and returns Follow' do expect do - expect(account.follow!(target_account)).to be_kind_of Follow + expect(account.follow!(target_account)).to be_a Follow end.to change { account.following.count }.by 1 end end @@ -102,7 +104,7 @@ describe AccountInteractions do describe '#block' do it 'creates and returns Block' do expect do - expect(account.block!(target_account)).to be_kind_of Block + expect(account.block!(target_account)).to be_a Block end.to change { account.block_relationships.count }.by 1 end end @@ -110,113 +112,113 @@ describe AccountInteractions do describe '#mute!' do subject { account.mute!(target_account, notifications: arg_notifications) } - context 'Mute does not exist yet' do - context 'arg :notifications is nil' do + context 'when Mute does not exist yet' do + context 'when arg :notifications is nil' do let(:arg_notifications) { nil } it 'creates Mute, and returns Mute' do expect do - expect(subject).to be_kind_of Mute + expect(subject).to be_a Mute end.to change { account.mute_relationships.count }.by 1 end end - context 'arg :notifications is false' do + context 'when arg :notifications is false' do let(:arg_notifications) { false } it 'creates Mute, and returns Mute' do expect do - expect(subject).to be_kind_of Mute + expect(subject).to be_a Mute end.to change { account.mute_relationships.count }.by 1 end end - context 'arg :notifications is true' do + context 'when arg :notifications is true' do let(:arg_notifications) { true } it 'creates Mute, and returns Mute' do expect do - expect(subject).to be_kind_of Mute + expect(subject).to be_a Mute end.to change { account.mute_relationships.count }.by 1 end end end - context 'Mute already exists' do + context 'when Mute already exists' do before do account.mute_relationships << mute end let(:mute) do Fabricate(:mute, - account: account, - target_account: target_account, + account: account, + target_account: target_account, hide_notifications: hide_notifications) end - context 'mute.hide_notifications is true' do + context 'when mute.hide_notifications is true' do let(:hide_notifications) { true } - context 'arg :notifications is nil' do + context 'when arg :notifications is nil' do let(:arg_notifications) { nil } it 'returns Mute without updating mute.hide_notifications' do expect do - expect(subject).to be_kind_of Mute - end.not_to change { mute.reload.hide_notifications? }.from(true) + expect(subject).to be_a Mute + end.to_not change { mute.reload.hide_notifications? }.from(true) end end - context 'arg :notifications is false' do + context 'when arg :notifications is false' do let(:arg_notifications) { false } it 'returns Mute, and updates mute.hide_notifications false' do expect do - expect(subject).to be_kind_of Mute + expect(subject).to be_a Mute end.to change { mute.reload.hide_notifications? }.from(true).to(false) end end - context 'arg :notifications is true' do + context 'when arg :notifications is true' do let(:arg_notifications) { true } it 'returns Mute without updating mute.hide_notifications' do expect do - expect(subject).to be_kind_of Mute - end.not_to change { mute.reload.hide_notifications? }.from(true) + expect(subject).to be_a Mute + end.to_not change { mute.reload.hide_notifications? }.from(true) end end end - context 'mute.hide_notifications is false' do + context 'when mute.hide_notifications is false' do let(:hide_notifications) { false } - context 'arg :notifications is nil' do + context 'when arg :notifications is nil' do let(:arg_notifications) { nil } it 'returns Mute, and updates mute.hide_notifications true' do expect do - expect(subject).to be_kind_of Mute + expect(subject).to be_a Mute end.to change { mute.reload.hide_notifications? }.from(false).to(true) end end - context 'arg :notifications is false' do + context 'when arg :notifications is false' do let(:arg_notifications) { false } it 'returns Mute without updating mute.hide_notifications' do expect do - expect(subject).to be_kind_of Mute - end.not_to change { mute.reload.hide_notifications? }.from(false) + expect(subject).to be_a Mute + end.to_not change { mute.reload.hide_notifications? }.from(false) end end - context 'arg :notifications is true' do + context 'when arg :notifications is true' do let(:arg_notifications) { true } it 'returns Mute, and updates mute.hide_notifications true' do expect do - expect(subject).to be_kind_of Mute + expect(subject).to be_a Mute end.to change { mute.reload.hide_notifications? }.from(false).to(true) end end @@ -225,25 +227,43 @@ describe AccountInteractions do end describe '#mute_conversation!' do - let(:conversation) { Fabricate(:conversation) } - subject { account.mute_conversation!(conversation) } + let(:conversation) { Fabricate(:conversation) } + it 'creates and returns ConversationMute' do expect do - is_expected.to be_kind_of ConversationMute + expect(subject).to be_a ConversationMute end.to change { account.conversation_mutes.count }.by 1 end end describe '#block_domain!' do - let(:domain) { 'example.com' } - subject { account.block_domain!(domain) } + let(:domain) { 'example.com' } + it 'creates and returns AccountDomainBlock' do expect do - is_expected.to be_kind_of AccountDomainBlock + expect(subject).to be_a AccountDomainBlock + end.to change { account.domain_blocks.count }.by 1 + end + end + + describe '#block_idna_domain!' do + subject do + [ + account.block_domain!(idna_domain), + account.block_domain!(punycode_domain), + ] + end + + let(:idna_domain) { '대한민국.한국' } + let(:punycode_domain) { 'xn--3e0bs9hfvinn1a.xn--3e0b707e' } + + it 'creates single AccountDomainBlock' do + expect do + expect(subject).to all(be_a AccountDomainBlock) end.to change { account.domain_blocks.count }.by 1 end end @@ -251,17 +271,17 @@ describe AccountInteractions do describe '#unfollow!' do subject { account.unfollow!(target_account) } - context 'following target_account' do + context 'when following target_account' do it 'returns destroyed Follow' do account.active_relationships.create(target_account: target_account) - is_expected.to be_kind_of Follow + expect(subject).to be_a Follow expect(subject).to be_destroyed end end - context 'not following target_account' do + context 'when not following target_account' do it 'returns nil' do - is_expected.to be_nil + expect(subject).to be_nil end end end @@ -269,17 +289,17 @@ describe AccountInteractions do describe '#unblock!' do subject { account.unblock!(target_account) } - context 'blocking target_account' do + context 'when blocking target_account' do it 'returns destroyed Block' do account.block_relationships.create(target_account: target_account) - is_expected.to be_kind_of Block + expect(subject).to be_a Block expect(subject).to be_destroyed end end - context 'not blocking target_account' do + context 'when not blocking target_account' do it 'returns nil' do - is_expected.to be_nil + expect(subject).to be_nil end end end @@ -287,58 +307,80 @@ describe AccountInteractions do describe '#unmute!' do subject { account.unmute!(target_account) } - context 'muting target_account' do + context 'when muting target_account' do it 'returns destroyed Mute' do account.mute_relationships.create(target_account: target_account) - is_expected.to be_kind_of Mute + expect(subject).to be_a Mute expect(subject).to be_destroyed end end - context 'not muting target_account' do + context 'when not muting target_account' do it 'returns nil' do - is_expected.to be_nil + expect(subject).to be_nil end end end describe '#unmute_conversation!' do - let(:conversation) { Fabricate(:conversation) } - subject { account.unmute_conversation!(conversation) } - context 'muting the conversation' do + let(:conversation) { Fabricate(:conversation) } + + context 'when muting the conversation' do it 'returns destroyed ConversationMute' do account.conversation_mutes.create(conversation: conversation) - is_expected.to be_kind_of ConversationMute + expect(subject).to be_a ConversationMute expect(subject).to be_destroyed end end - context 'not muting the conversation' do + context 'when not muting the conversation' do it 'returns nil' do - is_expected.to be nil + expect(subject).to be_nil end end end describe '#unblock_domain!' do - let(:domain) { 'example.com' } - subject { account.unblock_domain!(domain) } - context 'blocking the domain' do + let(:domain) { 'example.com' } + + context 'when blocking the domain' do it 'returns destroyed AccountDomainBlock' do account_domain_block = Fabricate(:account_domain_block, domain: domain) account.domain_blocks << account_domain_block - is_expected.to be_kind_of AccountDomainBlock + expect(subject).to be_a AccountDomainBlock expect(subject).to be_destroyed end end - context 'unblocking the domain' do + context 'when unblocking the domain' do it 'returns nil' do - is_expected.to be_nil + expect(subject).to be_nil + end + end + end + + describe '#unblock_idna_domain!' do + subject { account.unblock_domain!(punycode_domain) } + + let(:idna_domain) { '대한민국.한국' } + let(:punycode_domain) { 'xn--3e0bs9hfvinn1a.xn--3e0b707e' } + + context 'when blocking the domain' do + it 'returns destroyed AccountDomainBlock' do + account_domain_block = Fabricate(:account_domain_block, domain: idna_domain) + account.domain_blocks << account_domain_block + expect(subject).to be_a AccountDomainBlock + expect(subject).to be_destroyed + end + end + + context 'when unblocking idna domain' do + it 'returns nil' do + expect(subject).to be_nil end end end @@ -346,16 +388,16 @@ describe AccountInteractions do describe '#following?' do subject { account.following?(target_account) } - context 'following target_account' do + context 'when following target_account' do it 'returns true' do account.active_relationships.create(target_account: target_account) - is_expected.to be true + expect(subject).to be true end end - context 'not following target_account' do + context 'when not following target_account' do it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end @@ -363,16 +405,16 @@ describe AccountInteractions do describe '#followed_by?' do subject { account.followed_by?(target_account) } - context 'followed by target_account' do + context 'when followed by target_account' do it 'returns true' do account.passive_relationships.create(account: target_account) - is_expected.to be true + expect(subject).to be true end end - context 'not followed by target_account' do + context 'when not followed by target_account' do it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end @@ -380,36 +422,36 @@ describe AccountInteractions do describe '#blocking?' do subject { account.blocking?(target_account) } - context 'blocking target_account' do + context 'when blocking target_account' do it 'returns true' do account.block_relationships.create(target_account: target_account) - is_expected.to be true + expect(subject).to be true end end - context 'not blocking target_account' do + context 'when not blocking target_account' do it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end describe '#domain_blocking?' do - let(:domain) { 'example.com' } - subject { account.domain_blocking?(domain) } - context 'blocking the domain' do - it' returns true' do + let(:domain) { 'example.com' } + + context 'when blocking the domain' do + it 'returns true' do account_domain_block = Fabricate(:account_domain_block, domain: domain) account.domain_blocks << account_domain_block - is_expected.to be true + expect(subject).to be true end end - context 'not blocking the domain' do + context 'when not blocking the domain' do it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end @@ -417,61 +459,61 @@ describe AccountInteractions do describe '#muting?' do subject { account.muting?(target_account) } - context 'muting target_account' do + context 'when muting target_account' do it 'returns true' do mute = Fabricate(:mute, account: account, target_account: target_account) account.mute_relationships << mute - is_expected.to be true + expect(subject).to be true end end - context 'not muting target_account' do + context 'when not muting target_account' do it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end describe '#muting_conversation?' do - let(:conversation) { Fabricate(:conversation) } - subject { account.muting_conversation?(conversation) } - context 'muting the conversation' do + let(:conversation) { Fabricate(:conversation) } + + context 'when muting the conversation' do it 'returns true' do account.conversation_mutes.create(conversation: conversation) - is_expected.to be true + expect(subject).to be true end end - context 'not muting the conversation' do + context 'when not muting the conversation' do it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end describe '#muting_notifications?' do + subject { account.muting_notifications?(target_account) } + before do mute = Fabricate(:mute, target_account: target_account, account: account, hide_notifications: hide) account.mute_relationships << mute end - subject { account.muting_notifications?(target_account) } - - context 'muting notifications of target_account' do + context 'when muting notifications of target_account' do let(:hide) { true } it 'returns true' do - is_expected.to be true + expect(subject).to be true end end - context 'not muting notifications of target_account' do + context 'when not muting notifications of target_account' do let(:hide) { false } it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end @@ -479,96 +521,96 @@ describe AccountInteractions do describe '#requested?' do subject { account.requested?(target_account) } - context 'requested by target_account' do + context 'with requested by target_account' do it 'returns true' do Fabricate(:follow_request, account: account, target_account: target_account) - is_expected.to be true + expect(subject).to be true end end - context 'not requested by target_account' do + context 'when not requested by target_account' do it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end describe '#favourited?' do - let(:status) { Fabricate(:status, account: account, favourites: favourites) } - subject { account.favourited?(status) } - context 'favorited' do + let(:status) { Fabricate(:status, account: account, favourites: favourites) } + + context 'when favorited' do let(:favourites) { [Fabricate(:favourite, account: account)] } it 'returns true' do - is_expected.to be true + expect(subject).to be true end end - context 'not favorited' do + context 'when not favorited' do let(:favourites) { [] } it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end describe '#reblogged?' do - let(:status) { Fabricate(:status, account: account, reblogs: reblogs) } - subject { account.reblogged?(status) } - context 'reblogged' do + let(:status) { Fabricate(:status, account: account, reblogs: reblogs) } + + context 'with reblogged' do let(:reblogs) { [Fabricate(:status, account: account)] } it 'returns true' do - is_expected.to be true + expect(subject).to be true end end - context 'not reblogged' do + context 'when not reblogged' do let(:reblogs) { [] } it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end describe '#pinned?' do - let(:status) { Fabricate(:status, account: account) } - subject { account.pinned?(status) } - context 'pinned' do + let(:status) { Fabricate(:status, account: account) } + + context 'when pinned' do it 'returns true' do Fabricate(:status_pin, account: account, status: status) - is_expected.to be true + expect(subject).to be true end end - context 'not pinned' do + context 'when not pinned' do it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end describe '#remote_followers_hash' do let(:me) { Fabricate(:account, username: 'Me') } - let(:remote_1) { Fabricate(:account, username: 'alice', domain: 'example.org', uri: 'https://example.org/users/alice') } - let(:remote_2) { Fabricate(:account, username: 'bob', domain: 'example.org', uri: 'https://example.org/users/bob') } - let(:remote_3) { Fabricate(:account, username: 'instance-actor', domain: 'example.org', uri: 'https://example.org') } - let(:remote_4) { Fabricate(:account, username: 'eve', domain: 'foo.org', uri: 'https://foo.org/users/eve') } + let(:remote_alice) { Fabricate(:account, username: 'alice', domain: 'example.org', uri: 'https://example.org/users/alice') } + let(:remote_bob) { Fabricate(:account, username: 'bob', domain: 'example.org', uri: 'https://example.org/users/bob') } + let(:remote_instance_actor) { Fabricate(:account, username: 'instance-actor', domain: 'example.org', uri: 'https://example.org') } + let(:remote_eve) { Fabricate(:account, username: 'eve', domain: 'foo.org', uri: 'https://foo.org/users/eve') } before do - remote_1.follow!(me) - remote_2.follow!(me) - remote_3.follow!(me) - remote_4.follow!(me) - me.follow!(remote_1) + remote_alice.follow!(me) + remote_bob.follow!(me) + remote_instance_actor.follow!(me) + remote_eve.follow!(me) + me.follow!(remote_alice) end it 'returns correct hash for remote domains' do @@ -580,33 +622,33 @@ describe AccountInteractions do it 'invalidates cache as needed when removing or adding followers' do expect(me.remote_followers_hash('https://example.org/')).to eq '20aecbe774b3d61c25094370baf370012b9271c5b172ecedb05caff8d79ef0c7' - remote_3.unfollow!(me) + remote_instance_actor.unfollow!(me) expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec' - remote_1.unfollow!(me) + remote_alice.unfollow!(me) expect(me.remote_followers_hash('https://example.org/')).to eq '241b00794ce9b46aa864f3220afadef128318da2659782985bac5ed5bd436bff' - remote_1.follow!(me) + remote_alice.follow!(me) expect(me.remote_followers_hash('https://example.org/')).to eq '707962e297b7bd94468a21bc8e506a1bcea607a9142cd64e27c9b106b2a5f6ec' end end describe '#local_followers_hash' do let(:me) { Fabricate(:account, username: 'Me') } - let(:remote_1) { Fabricate(:account, username: 'alice', domain: 'example.org', uri: 'https://example.org/users/alice') } + let(:remote_alice) { Fabricate(:account, username: 'alice', domain: 'example.org', uri: 'https://example.org/users/alice') } before do - me.follow!(remote_1) + me.follow!(remote_alice) end it 'returns correct hash for local users' do - expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) + expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) end it 'invalidates cache as needed when removing or adding followers' do - expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) - me.unfollow!(remote_1) - expect(remote_1.local_followers_hash).to eq '0000000000000000000000000000000000000000000000000000000000000000' - me.follow!(remote_1) - expect(remote_1.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) + expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) + me.unfollow!(remote_alice) + expect(remote_alice.local_followers_hash).to eq '0000000000000000000000000000000000000000000000000000000000000000' + me.follow!(remote_alice) + expect(remote_alice.local_followers_hash).to eq Digest::SHA256.hexdigest(ActivityPub::TagManager.instance.uri_for(me)) end end @@ -681,4 +723,32 @@ describe AccountInteractions do end end end + + describe '#lists_for_local_distribution' do + let(:account) { Fabricate(:user, current_sign_in_at: Time.now.utc).account } + let!(:inactive_follower_user) { Fabricate(:user, current_sign_in_at: 5.years.ago) } + let!(:follower_user) { Fabricate(:user, current_sign_in_at: Time.now.utc) } + let!(:follow_request_user) { Fabricate(:user, current_sign_in_at: Time.now.utc) } + + let!(:inactive_follower_list) { Fabricate(:list, account: inactive_follower_user.account) } + let!(:follower_list) { Fabricate(:list, account: follower_user.account) } + let!(:follow_request_list) { Fabricate(:list, account: follow_request_user.account) } + + let!(:self_list) { Fabricate(:list, account: account) } + + before do + inactive_follower_user.account.follow!(account) + follower_user.account.follow!(account) + follow_request_user.account.follow_requests.create!(target_account: account) + + inactive_follower_list.accounts << account + follower_list.accounts << account + follow_request_list.accounts << account + self_list.accounts << account + end + + it 'includes only the list from the active follower and from oneself' do + expect(account.lists_for_local_distribution.to_a).to contain_exactly(follower_list, self_list) + end + end end diff --git a/spec/models/concerns/account_statuses_search_spec.rb b/spec/models/concerns/account_statuses_search_spec.rb new file mode 100644 index 000000000..46362936f --- /dev/null +++ b/spec/models/concerns/account_statuses_search_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe AccountStatusesSearch do + let(:account) { Fabricate(:account, indexable: indexable) } + + before do + allow(Chewy).to receive(:enabled?).and_return(true) + end + + describe '#enqueue_update_public_statuses_index' do + before do + allow(account).to receive(:enqueue_add_to_public_statuses_index) + allow(account).to receive(:enqueue_remove_from_public_statuses_index) + end + + context 'when account is indexable' do + let(:indexable) { true } + + it 'enqueues add_to_public_statuses_index and not to remove_from_public_statuses_index' do + account.enqueue_update_public_statuses_index + expect(account).to have_received(:enqueue_add_to_public_statuses_index) + expect(account).to_not have_received(:enqueue_remove_from_public_statuses_index) + end + end + + context 'when account is not indexable' do + let(:indexable) { false } + + it 'enqueues remove_from_public_statuses_index and not to add_to_public_statuses_index' do + account.enqueue_update_public_statuses_index + expect(account).to have_received(:enqueue_remove_from_public_statuses_index) + expect(account).to_not have_received(:enqueue_add_to_public_statuses_index) + end + end + end + + describe '#enqueue_add_to_public_statuses_index' do + let(:indexable) { true } + let(:worker) { AddToPublicStatusesIndexWorker } + + before do + allow(worker).to receive(:perform_async) + end + + it 'enqueues AddToPublicStatusesIndexWorker' do + account.enqueue_add_to_public_statuses_index + expect(worker).to have_received(:perform_async).with(account.id) + end + end + + describe '#enqueue_remove_from_public_statuses_index' do + let(:indexable) { false } + let(:worker) { RemoveFromPublicStatusesIndexWorker } + + before do + allow(worker).to receive(:perform_async) + end + + it 'enqueues RemoveFromPublicStatusesIndexWorker' do + account.enqueue_remove_from_public_statuses_index + expect(worker).to have_received(:perform_async).with(account.id) + end + end +end diff --git a/spec/models/concerns/remotable_spec.rb b/spec/models/concerns/remotable_spec.rb index 9cc849ded..b2aa56a70 100644 --- a/spec/models/concerns/remotable_spec.rb +++ b/spec/models/concerns/remotable_spec.rb @@ -3,48 +3,47 @@ require 'rails_helper' RSpec.describe Remotable do - class Foo - def initialize - @attrs = {} - end + let(:foo_class) do + Class.new do + def initialize + @attrs = {} + end - def [](arg) - @attrs[arg] - end + def [](arg) + @attrs[arg] + end - def []=(arg1, arg2) - @attrs[arg1] = arg2 - end + def []=(arg1, arg2) + @attrs[arg1] = arg2 + end - def hoge=(arg); end + def hoge=(arg); end - def hoge_file_name; end + def hoge_file_name; end - def hoge_file_name=(arg); end + def hoge_file_name=(arg); end - def has_attribute?(arg); end + def has_attribute?(arg); end - def self.attachment_definitions - { hoge: nil } - end - end - - before do - class Foo - include Remotable - - remotable_attachment :hoge, 1.kilobyte + def self.attachment_definitions + { hoge: nil } + end end end let(:attribute_name) { "#{hoge}_remote_url".to_sym } let(:code) { 200 } let(:file) { 'filename="foo.txt"' } - let(:foo) { Foo.new } + let(:foo) { foo_class.new } let(:headers) { { 'content-disposition' => file } } let(:hoge) { :hoge } let(:url) { 'https://google.com' } + before do + foo_class.include described_class + foo_class.remotable_attachment :hoge, 1.kilobyte + end + it 'defines a method #hoge_remote_url=' do expect(foo).to respond_to(:hoge_remote_url=) end @@ -147,8 +146,8 @@ RSpec.describe Remotable do let(:code) { 500 } it 'does not assign file' do - expect(foo).not_to receive(:public_send).with("#{hoge}=", any_args) - expect(foo).not_to receive(:public_send).with("#{hoge}_file_name=", any_args) + expect(foo).to_not receive(:public_send).with("#{hoge}=", any_args) + expect(foo).to_not receive(:public_send).with("#{hoge}_file_name=", any_args) foo.hoge_remote_url = url end @@ -157,7 +156,7 @@ RSpec.describe Remotable do context 'when the response is successful' do let(:code) { 200 } - context 'and contains Content-Disposition header' do + context 'when contains Content-Disposition header' do let(:file) { 'filename="foo.txt"' } let(:headers) { { 'content-disposition' => file } } @@ -194,7 +193,9 @@ RSpec.describe Remotable do let(:error_class) { error_class } it 'calls Rails.logger.debug' do - expect(Rails.logger).to receive(:debug).with(/^Error fetching remote #{hoge}: /) + expect(Rails.logger).to receive(:debug) do |&block| + expect(block.call).to match(/^Error fetching remote #{hoge}: /) + end foo.hoge_remote_url = url end end diff --git a/spec/models/concerns/status_threading_concern_spec.rb b/spec/models/concerns/status_threading_concern_spec.rb index 50286ef77..2eac1ca6e 100644 --- a/spec/models/concerns/status_threading_concern_spec.rb +++ b/spec/models/concerns/status_threading_concern_spec.rb @@ -8,40 +8,40 @@ describe StatusThreadingConcern do let!(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com') } let!(:jeff) { Fabricate(:account, username: 'jeff') } let!(:status) { Fabricate(:status, account: alice) } - let!(:reply1) { Fabricate(:status, thread: status, account: jeff) } - let!(:reply2) { Fabricate(:status, thread: reply1, account: bob) } - let!(:reply3) { Fabricate(:status, thread: reply2, account: alice) } + let!(:reply_to_status) { Fabricate(:status, thread: status, account: jeff) } + let!(:reply_to_first_reply) { Fabricate(:status, thread: reply_to_status, account: bob) } + let!(:reply_to_second_reply) { Fabricate(:status, thread: reply_to_first_reply, account: alice) } let!(:viewer) { Fabricate(:account, username: 'viewer') } it 'returns conversation history' do - expect(reply3.ancestors(4)).to include(status, reply1, reply2) + expect(reply_to_second_reply.ancestors(4)).to include(status, reply_to_status, reply_to_first_reply) end it 'does not return conversation history user is not allowed to see' do - reply1.update(visibility: :private) + reply_to_status.update(visibility: :private) status.update(visibility: :direct) - expect(reply3.ancestors(4, viewer)).to_not include(reply1, status) + expect(reply_to_second_reply.ancestors(4, viewer)).to_not include(reply_to_status, status) end it 'does not return conversation history from blocked users' do viewer.block!(jeff) - expect(reply3.ancestors(4, viewer)).to_not include(reply1) + expect(reply_to_second_reply.ancestors(4, viewer)).to_not include(reply_to_status) end it 'does not return conversation history from muted users' do viewer.mute!(jeff) - expect(reply3.ancestors(4, viewer)).to_not include(reply1) + expect(reply_to_second_reply.ancestors(4, viewer)).to_not include(reply_to_status) end it 'does not return conversation history from silenced and not followed users' do jeff.silence! - expect(reply3.ancestors(4, viewer)).to_not include(reply1) + expect(reply_to_second_reply.ancestors(4, viewer)).to_not include(reply_to_status) end it 'does not return conversation history from blocked domains' do viewer.block_domain!('example.com') - expect(reply3.ancestors(4, viewer)).to_not include(reply2) + expect(reply_to_second_reply.ancestors(4, viewer)).to_not include(reply_to_first_reply) end it 'ignores deleted records' do @@ -83,40 +83,40 @@ describe StatusThreadingConcern do let!(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com') } let!(:jeff) { Fabricate(:account, username: 'jeff') } let!(:status) { Fabricate(:status, account: alice) } - let!(:reply1) { Fabricate(:status, thread: status, account: alice) } - let!(:reply2) { Fabricate(:status, thread: status, account: bob) } - let!(:reply3) { Fabricate(:status, thread: reply1, account: jeff) } + let!(:reply_to_status_from_alice) { Fabricate(:status, thread: status, account: alice) } + let!(:reply_to_status_from_bob) { Fabricate(:status, thread: status, account: bob) } + let!(:reply_to_alice_reply_from_jeff) { Fabricate(:status, thread: reply_to_status_from_alice, account: jeff) } let!(:viewer) { Fabricate(:account, username: 'viewer') } it 'returns replies' do - expect(status.descendants(4)).to include(reply1, reply2, reply3) + expect(status.descendants(4)).to include(reply_to_status_from_alice, reply_to_status_from_bob, reply_to_alice_reply_from_jeff) end it 'does not return replies user is not allowed to see' do - reply1.update(visibility: :private) - reply3.update(visibility: :direct) + reply_to_status_from_alice.update(visibility: :private) + reply_to_alice_reply_from_jeff.update(visibility: :direct) - expect(status.descendants(4, viewer)).to_not include(reply1, reply3) + expect(status.descendants(4, viewer)).to_not include(reply_to_status_from_alice, reply_to_alice_reply_from_jeff) end it 'does not return replies from blocked users' do viewer.block!(jeff) - expect(status.descendants(4, viewer)).to_not include(reply3) + expect(status.descendants(4, viewer)).to_not include(reply_to_alice_reply_from_jeff) end it 'does not return replies from muted users' do viewer.mute!(jeff) - expect(status.descendants(4, viewer)).to_not include(reply3) + expect(status.descendants(4, viewer)).to_not include(reply_to_alice_reply_from_jeff) end it 'does not return replies from silenced and not followed users' do jeff.silence! - expect(status.descendants(4, viewer)).to_not include(reply3) + expect(status.descendants(4, viewer)).to_not include(reply_to_alice_reply_from_jeff) end it 'does not return replies from blocked domains' do viewer.block_domain!('example.com') - expect(status.descendants(4, viewer)).to_not include(reply2) + expect(status.descendants(4, viewer)).to_not include(reply_to_status_from_bob) end it 'promotes self-replies to the top while leaving the rest in order' do diff --git a/spec/models/conversation_mute_spec.rb b/spec/models/conversation_mute_spec.rb deleted file mode 100644 index 3fc2915d4..000000000 --- a/spec/models/conversation_mute_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe ConversationMute, type: :model do -end diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index 8b5e4fdaf..c1d6659aa 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Conversation, type: :model do +RSpec.describe Conversation do describe '#local?' do it 'returns true when URI is nil' do expect(Fabricate(:conversation).local?).to be true diff --git a/spec/models/custom_emoji_category_spec.rb b/spec/models/custom_emoji_category_spec.rb index 160033f4d..30de07bd8 100644 --- a/spec/models/custom_emoji_category_spec.rb +++ b/spec/models/custom_emoji_category_spec.rb @@ -1,5 +1,14 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe CustomEmojiCategory, type: :model do - pending "add some examples to (or delete) #{__FILE__}" +describe CustomEmojiCategory do + describe 'validations' do + it 'validates name presence' do + record = described_class.new(name: nil) + + expect(record).to_not be_valid + expect(record).to model_have_error_on_field(:name) + end + end end diff --git a/spec/models/custom_emoji_filter_spec.rb b/spec/models/custom_emoji_filter_spec.rb index 2b1b5dc54..c36fecd60 100644 --- a/spec/models/custom_emoji_filter_spec.rb +++ b/spec/models/custom_emoji_filter_spec.rb @@ -4,50 +4,50 @@ require 'rails_helper' RSpec.describe CustomEmojiFilter do describe '#results' do - let!(:custom_emoji_0) { Fabricate(:custom_emoji, domain: 'a') } - let!(:custom_emoji_1) { Fabricate(:custom_emoji, domain: 'b') } - let!(:custom_emoji_2) { Fabricate(:custom_emoji, domain: nil, shortcode: 'hoge') } - subject { described_class.new(params).results } - context 'params have values' do - context 'local' do + let!(:custom_emoji_domain_a) { Fabricate(:custom_emoji, domain: 'a') } + let!(:custom_emoji_domain_b) { Fabricate(:custom_emoji, domain: 'b') } + let!(:custom_emoji_domain_nil) { Fabricate(:custom_emoji, domain: nil, shortcode: 'hoge') } + + context 'when params have values' do + context 'when local' do let(:params) { { local: true } } it 'returns ActiveRecord::Relation' do - expect(subject).to be_kind_of(ActiveRecord::Relation) - expect(subject).to match_array([custom_emoji_2]) + expect(subject).to be_a(ActiveRecord::Relation) + expect(subject).to contain_exactly(custom_emoji_domain_nil) end end - context 'remote' do + context 'when remote' do let(:params) { { remote: true } } it 'returns ActiveRecord::Relation' do - expect(subject).to be_kind_of(ActiveRecord::Relation) - expect(subject).to match_array([custom_emoji_0, custom_emoji_1]) + expect(subject).to be_a(ActiveRecord::Relation) + expect(subject).to contain_exactly(custom_emoji_domain_a, custom_emoji_domain_b) end end - context 'by_domain' do + context 'with by_domain' do let(:params) { { by_domain: 'a' } } it 'returns ActiveRecord::Relation' do - expect(subject).to be_kind_of(ActiveRecord::Relation) - expect(subject).to match_array([custom_emoji_0]) + expect(subject).to be_a(ActiveRecord::Relation) + expect(subject).to contain_exactly(custom_emoji_domain_a) end end - context 'shortcode' do + context 'when shortcode' do let(:params) { { shortcode: 'hoge' } } it 'returns ActiveRecord::Relation' do - expect(subject).to be_kind_of(ActiveRecord::Relation) - expect(subject).to match_array([custom_emoji_2]) + expect(subject).to be_a(ActiveRecord::Relation) + expect(subject).to contain_exactly(custom_emoji_domain_nil) end end - context 'else' do + context 'when some other case' do let(:params) { { else: 'else' } } it 'raises Mastodon::InvalidParameterError' do @@ -58,12 +58,12 @@ RSpec.describe CustomEmojiFilter do end end - context 'params without value' do + context 'when params without value' do let(:params) { { hoge: nil } } it 'returns ActiveRecord::Relation' do - expect(subject).to be_kind_of(ActiveRecord::Relation) - expect(subject).to match_array([custom_emoji_0, custom_emoji_1, custom_emoji_2]) + expect(subject).to be_a(ActiveRecord::Relation) + expect(subject).to contain_exactly(custom_emoji_domain_a, custom_emoji_domain_b, custom_emoji_domain_nil) end end end diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb index 9de218b4f..8a6487c32 100644 --- a/spec/models/custom_emoji_spec.rb +++ b/spec/models/custom_emoji_spec.rb @@ -1,48 +1,50 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe CustomEmoji, type: :model do +RSpec.describe CustomEmoji do describe '#search' do - let(:custom_emoji) { Fabricate(:custom_emoji, shortcode: shortcode) } - subject { described_class.search(search_term) } - context 'shortcode is exact' do + let(:custom_emoji) { Fabricate(:custom_emoji, shortcode: shortcode) } + + context 'when shortcode is exact' do let(:shortcode) { 'blobpats' } let(:search_term) { 'blobpats' } it 'finds emoji' do - is_expected.to include(custom_emoji) + expect(subject).to include(custom_emoji) end end - context 'shortcode is partial' do + context 'when shortcode is partial' do let(:shortcode) { 'blobpats' } let(:search_term) { 'blob' } it 'finds emoji' do - is_expected.to include(custom_emoji) + expect(subject).to include(custom_emoji) end end end describe '#local?' do - let(:custom_emoji) { Fabricate(:custom_emoji, domain: domain) } - subject { custom_emoji.local? } - context 'domain is nil' do + let(:custom_emoji) { Fabricate(:custom_emoji, domain: domain) } + + context 'when domain is nil' do let(:domain) { nil } it 'returns true' do - is_expected.to be true + expect(subject).to be true end end - context 'domain is present' do + context 'when domain is present' do let(:domain) { 'example.com' } it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end @@ -55,15 +57,15 @@ RSpec.describe CustomEmoji, type: :model do end describe '.from_text' do - let!(:emojo) { Fabricate(:custom_emoji) } - subject { described_class.from_text(text, nil) } + let!(:emojo) { Fabricate(:custom_emoji) } + context 'with plain text' do let(:text) { 'Hello :coolcat:' } it 'returns records used via shortcodes in text' do - is_expected.to include(emojo) + expect(subject).to include(emojo) end end @@ -71,7 +73,7 @@ RSpec.describe CustomEmoji, type: :model do let(:text) { '

Hello :coolcat:

' } it 'returns records used via shortcodes in text' do - is_expected.to include(emojo) + expect(subject).to include(emojo) end end end @@ -79,7 +81,7 @@ RSpec.describe CustomEmoji, type: :model do describe 'pre_validation' do let(:custom_emoji) { Fabricate(:custom_emoji, domain: 'wWw.MaStOdOn.CoM') } - it 'should downcase' do + it 'downcases' do custom_emoji.valid? expect(custom_emoji.domain).to eq('www.mastodon.com') end diff --git a/spec/models/custom_filter_keyword_spec.rb b/spec/models/custom_filter_keyword_spec.rb deleted file mode 100644 index e15b9dad5..000000000 --- a/spec/models/custom_filter_keyword_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe CustomFilterKeyword, type: :model do -end diff --git a/spec/models/custom_filter_spec.rb b/spec/models/custom_filter_spec.rb deleted file mode 100644 index 3943dd5f1..000000000 --- a/spec/models/custom_filter_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe CustomFilter, type: :model do -end diff --git a/spec/models/device_spec.rb b/spec/models/device_spec.rb deleted file mode 100644 index f56fbf978..000000000 --- a/spec/models/device_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -RSpec.describe Device, type: :model do - -end diff --git a/spec/models/domain_allow_spec.rb b/spec/models/domain_allow_spec.rb index e65435127..49e16376e 100644 --- a/spec/models/domain_allow_spec.rb +++ b/spec/models/domain_allow_spec.rb @@ -1,5 +1,18 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe DomainAllow, type: :model do - pending "add some examples to (or delete) #{__FILE__}" +describe DomainAllow do + describe 'scopes' do + describe 'matches_domain' do + let(:domain) { Fabricate(:domain_allow, domain: 'example.com') } + let(:other_domain) { Fabricate(:domain_allow, domain: 'example.biz') } + + it 'returns the correct records' do + results = described_class.matches_domain('example.com') + + expect(results).to eq([domain]) + end + end + end end diff --git a/spec/models/domain_block_spec.rb b/spec/models/domain_block_spec.rb index 28647dc89..67f53fa78 100644 --- a/spec/models/domain_block_spec.rb +++ b/spec/models/domain_block_spec.rb @@ -1,12 +1,9 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe DomainBlock, type: :model do +RSpec.describe DomainBlock do describe 'validations' do - it 'has a valid fabricator' do - domain_block = Fabricate.build(:domain_block) - expect(domain_block).to be_valid - end - it 'is invalid without a domain' do domain_block = Fabricate.build(:domain_block, domain: nil) domain_block.valid? @@ -24,74 +21,92 @@ RSpec.describe DomainBlock, type: :model do describe '.blocked?' do it 'returns true if the domain is suspended' do Fabricate(:domain_block, domain: 'example.com', severity: :suspend) - expect(DomainBlock.blocked?('example.com')).to eq true + expect(described_class.blocked?('example.com')).to be true end it 'returns false even if the domain is silenced' do Fabricate(:domain_block, domain: 'example.com', severity: :silence) - expect(DomainBlock.blocked?('example.com')).to eq false + expect(described_class.blocked?('example.com')).to be false end it 'returns false if the domain is not suspended nor silenced' do - expect(DomainBlock.blocked?('example.com')).to eq false + expect(described_class.blocked?('example.com')).to be false end end describe '.rule_for' do it 'returns rule matching a blocked domain' do block = Fabricate(:domain_block, domain: 'example.com') - expect(DomainBlock.rule_for('example.com')).to eq block + expect(described_class.rule_for('example.com')).to eq block end it 'returns a rule matching a subdomain of a blocked domain' do block = Fabricate(:domain_block, domain: 'example.com') - expect(DomainBlock.rule_for('sub.example.com')).to eq block + expect(described_class.rule_for('sub.example.com')).to eq block end it 'returns a rule matching a blocked subdomain' do block = Fabricate(:domain_block, domain: 'sub.example.com') - expect(DomainBlock.rule_for('sub.example.com')).to eq block + expect(described_class.rule_for('sub.example.com')).to eq block end it 'returns a rule matching a blocked TLD' do block = Fabricate(:domain_block, domain: 'google') - expect(DomainBlock.rule_for('google')).to eq block + expect(described_class.rule_for('google')).to eq block end it 'returns a rule matching a subdomain of a blocked TLD' do block = Fabricate(:domain_block, domain: 'google') - expect(DomainBlock.rule_for('maps.google')).to eq block + expect(described_class.rule_for('maps.google')).to eq block end end describe '#stricter_than?' do it 'returns true if the new block has suspend severity while the old has lower severity' do - suspend = DomainBlock.new(domain: 'domain', severity: :suspend) - silence = DomainBlock.new(domain: 'domain', severity: :silence) - noop = DomainBlock.new(domain: 'domain', severity: :noop) + suspend = described_class.new(domain: 'domain', severity: :suspend) + silence = described_class.new(domain: 'domain', severity: :silence) + noop = described_class.new(domain: 'domain', severity: :noop) expect(suspend.stricter_than?(silence)).to be true expect(suspend.stricter_than?(noop)).to be true end it 'returns false if the new block has lower severity than the old one' do - suspend = DomainBlock.new(domain: 'domain', severity: :suspend) - silence = DomainBlock.new(domain: 'domain', severity: :silence) - noop = DomainBlock.new(domain: 'domain', severity: :noop) + suspend = described_class.new(domain: 'domain', severity: :suspend) + silence = described_class.new(domain: 'domain', severity: :silence) + noop = described_class.new(domain: 'domain', severity: :noop) expect(silence.stricter_than?(suspend)).to be false expect(noop.stricter_than?(suspend)).to be false expect(noop.stricter_than?(silence)).to be false end it 'returns false if the new block does is less strict regarding reports' do - older = DomainBlock.new(domain: 'domain', severity: :silence, reject_reports: true) - newer = DomainBlock.new(domain: 'domain', severity: :silence, reject_reports: false) + older = described_class.new(domain: 'domain', severity: :silence, reject_reports: true) + newer = described_class.new(domain: 'domain', severity: :silence, reject_reports: false) expect(newer.stricter_than?(older)).to be false end it 'returns false if the new block does is less strict regarding media' do - older = DomainBlock.new(domain: 'domain', severity: :silence, reject_media: true) - newer = DomainBlock.new(domain: 'domain', severity: :silence, reject_media: false) + older = described_class.new(domain: 'domain', severity: :silence, reject_media: true) + newer = described_class.new(domain: 'domain', severity: :silence, reject_media: false) expect(newer.stricter_than?(older)).to be false end end + + describe '#public_domain' do + context 'with a domain block that is obfuscated' do + let(:domain_block) { Fabricate(:domain_block, domain: 'hostname.example.com', obfuscate: true) } + + it 'garbles the domain' do + expect(domain_block.public_domain).to eq 'hostna**.******e.com' + end + end + + context 'with a domain block that is not obfuscated' do + let(:domain_block) { Fabricate(:domain_block, domain: 'example.com', obfuscate: false) } + + it 'returns the domain value' do + expect(domain_block.public_domain).to eq 'example.com' + end + end + end end diff --git a/spec/models/email_domain_block_spec.rb b/spec/models/email_domain_block_spec.rb index e23116888..5874c5e53 100644 --- a/spec/models/email_domain_block_spec.rb +++ b/spec/models/email_domain_block_spec.rb @@ -1,34 +1,29 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe EmailDomainBlock, type: :model do - describe 'validations' do - it 'has a valid fabricator' do - email_domain_block = Fabricate.build(:email_domain_block) - expect(email_domain_block).to be_valid - end - end - +RSpec.describe EmailDomainBlock do describe 'block?' do let(:input) { nil } - context 'given an e-mail address' do + context 'when given an e-mail address' do let(:input) { "foo@#{domain}" } - context do + context 'with a top level domain' do let(:domain) { 'example.com' } it 'returns true if the domain is blocked' do Fabricate(:email_domain_block, domain: 'example.com') - expect(EmailDomainBlock.block?(input)).to be true + expect(described_class.block?(input)).to be true end it 'returns false if the domain is not blocked' do Fabricate(:email_domain_block, domain: 'other-example.com') - expect(EmailDomainBlock.block?(input)).to be false + expect(described_class.block?(input)).to be false end end - context do + context 'with a subdomain' do let(:domain) { 'mail.example.com' } it 'returns true if it is a subdomain of a blocked domain' do @@ -38,12 +33,12 @@ RSpec.describe EmailDomainBlock, type: :model do end end - context 'given an array of domains' do + context 'when given an array of domains' do let(:input) { %w(foo.com mail.foo.com) } it 'returns true if the domain is blocked' do Fabricate(:email_domain_block, domain: 'mail.foo.com') - expect(EmailDomainBlock.block?(input)).to be true + expect(described_class.block?(input)).to be true end end end diff --git a/spec/models/encrypted_message_spec.rb b/spec/models/encrypted_message_spec.rb deleted file mode 100644 index 1238d57b6..000000000 --- a/spec/models/encrypted_message_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -RSpec.describe EncryptedMessage, type: :model do - -end diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb index 135d7a36b..75468898d 100644 --- a/spec/models/export_spec.rb +++ b/spec/models/export_spec.rb @@ -1,16 +1,18 @@ +# frozen_string_literal: true + require 'rails_helper' describe Export do let(:account) { Fabricate(:account) } let(:target_accounts) do - [ {}, { username: 'one', domain: 'local.host' } ].map(&method(:Fabricate).curry(2).call(:account)) + [{}, { username: 'one', domain: 'local.host' }].map(&method(:Fabricate).curry(2).call(:account)) end describe 'to_csv' do it 'returns a csv of the blocked accounts' do - target_accounts.each(&account.method(:block!)) + target_accounts.each { |target_account| account.block!(target_account) } - export = Export.new(account).to_blocked_accounts_csv + export = described_class.new(account).to_blocked_accounts_csv results = export.strip.split expect(results.size).to eq 2 @@ -18,9 +20,9 @@ describe Export do end it 'returns a csv of the muted accounts' do - target_accounts.each(&account.method(:mute!)) + target_accounts.each { |target_account| account.mute!(target_account) } - export = Export.new(account).to_muted_accounts_csv + export = described_class.new(account).to_muted_accounts_csv results = export.strip.split("\n") expect(results.size).to eq 3 @@ -29,9 +31,9 @@ describe Export do end it 'returns a csv of the following accounts' do - target_accounts.each(&account.method(:follow!)) + target_accounts.each { |target_account| account.follow!(target_account) } - export = Export.new(account).to_following_accounts_csv + export = described_class.new(account).to_following_accounts_csv results = export.strip.split("\n") expect(results.size).to eq 3 @@ -43,24 +45,24 @@ describe Export do describe 'total_storage' do it 'returns the total size of the media attachments' do media_attachment = Fabricate(:media_attachment, account: account) - expect(Export.new(account).total_storage).to eq media_attachment.file_file_size || 0 + expect(described_class.new(account).total_storage).to eq media_attachment.file_file_size || 0 end end describe 'total_follows' do it 'returns the total number of the followed accounts' do - target_accounts.each(&account.method(:follow!)) - expect(Export.new(account.reload).total_follows).to eq 2 + target_accounts.each { |target_account| account.follow!(target_account) } + expect(described_class.new(account.reload).total_follows).to eq 2 end it 'returns the total number of the blocked accounts' do - target_accounts.each(&account.method(:block!)) - expect(Export.new(account.reload).total_blocks).to eq 2 + target_accounts.each { |target_account| account.block!(target_account) } + expect(described_class.new(account.reload).total_blocks).to eq 2 end it 'returns the total number of the muted accounts' do - target_accounts.each(&account.method(:mute!)) - expect(Export.new(account.reload).total_mutes).to eq 2 + target_accounts.each { |target_account| account.mute!(target_account) } + expect(described_class.new(account.reload).total_mutes).to eq 2 end end end diff --git a/spec/models/extended_description_spec.rb b/spec/models/extended_description_spec.rb new file mode 100644 index 000000000..ecc27c0f6 --- /dev/null +++ b/spec/models/extended_description_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ExtendedDescription do + describe '.current' do + context 'with the default values' do + it 'makes a new instance' do + record = described_class.current + + expect(record.text).to be_nil + expect(record.updated_at).to be_nil + end + end + + context 'with a custom setting value' do + before do + setting = instance_double(Setting, value: 'Extended text', updated_at: 10.days.ago) + allow(Setting).to receive(:find_by).with(var: 'site_extended_description').and_return(setting) + end + + it 'has the privacy text' do + record = described_class.current + + expect(record.text).to eq('Extended text') + end + end + end +end diff --git a/spec/models/favourite_spec.rb b/spec/models/favourite_spec.rb index ba1410a45..ef7fbdefc 100644 --- a/spec/models/favourite_spec.rb +++ b/spec/models/favourite_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Favourite, type: :model do +RSpec.describe Favourite do let(:account) { Fabricate(:account) } context 'when status is a reblog' do @@ -8,12 +10,12 @@ RSpec.describe Favourite, type: :model do let(:status) { Fabricate(:status, reblog: reblog) } it 'invalidates if the reblogged status is already a favourite' do - Favourite.create!(account: account, status: reblog) - expect(Favourite.new(account: account, status: status).valid?).to eq false + described_class.create!(account: account, status: reblog) + expect(described_class.new(account: account, status: status).valid?).to be false end it 'replaces status with the reblogged one if it is a reblog' do - favourite = Favourite.create!(account: account, status: status) + favourite = described_class.create!(account: account, status: status) expect(favourite.status).to eq reblog end end @@ -22,7 +24,7 @@ RSpec.describe Favourite, type: :model do let(:status) { Fabricate(:status, reblog: nil) } it 'saves with the specified status' do - favourite = Favourite.create!(account: account, status: status) + favourite = described_class.create!(account: account, status: status) expect(favourite.status).to eq status end end diff --git a/spec/models/featured_tag_spec.rb b/spec/models/featured_tag_spec.rb deleted file mode 100644 index 07533e0b9..000000000 --- a/spec/models/featured_tag_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe FeaturedTag, type: :model do -end diff --git a/spec/models/follow_recommendation_suppression_spec.rb b/spec/models/follow_recommendation_suppression_spec.rb deleted file mode 100644 index 39107a2b0..000000000 --- a/spec/models/follow_recommendation_suppression_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe FollowRecommendationSuppression, type: :model do -end diff --git a/spec/models/follow_request_spec.rb b/spec/models/follow_request_spec.rb index 901eabc9d..e41374785 100644 --- a/spec/models/follow_request_spec.rb +++ b/spec/models/follow_request_spec.rb @@ -1,14 +1,30 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe FollowRequest, type: :model do +RSpec.describe FollowRequest do describe '#authorize!' do - let(:follow_request) { Fabricate(:follow_request, account: account, target_account: target_account) } - let(:account) { Fabricate(:account) } - let(:target_account) { Fabricate(:account) } + let!(:follow_request) { Fabricate(:follow_request, account: account, target_account: target_account) } + let(:account) { Fabricate(:account) } + let(:target_account) { Fabricate(:account) } + + context 'when the to-be-followed person has been added to a list' do + let!(:list) { Fabricate(:list, account: account) } + + before do + list.accounts << target_account + end + + it 'updates the ListAccount' do + expect { follow_request.authorize! }.to change { [list.list_accounts.first.follow_request_id, list.list_accounts.first.follow_id] }.from([follow_request.id, nil]).to([nil, anything]) + end + end it 'calls Account#follow!, MergeWorker.perform_async, and #destroy!' do - expect(account).to receive(:follow!).with(target_account, reblogs: true, notify: false, uri: follow_request.uri, languages: nil, bypass_limit: true) - expect(MergeWorker).to receive(:perform_async).with(target_account.id, account.id) + expect(account).to receive(:follow!).with(target_account, reblogs: true, notify: false, uri: follow_request.uri, languages: nil, bypass_limit: true) do + account.active_relationships.create!(target_account: target_account) + end + expect(MergeWorker).to receive(:perform_async).with(target_account.id, account.id) expect(follow_request).to receive(:destroy!) follow_request.authorize! end @@ -27,4 +43,22 @@ RSpec.describe FollowRequest, type: :model do expect(follow_request.account.muting_reblogs?(target)).to be true end end + + describe '#reject!' do + let!(:follow_request) { Fabricate(:follow_request, account: account, target_account: target_account) } + let(:account) { Fabricate(:account) } + let(:target_account) { Fabricate(:account) } + + context 'when the to-be-followed person has been added to a list' do + let!(:list) { Fabricate(:list, account: account) } + + before do + list.accounts << target_account + end + + it 'deletes the ListAccount record' do + expect { follow_request.reject! }.to change { list.accounts.count }.from(1).to(0) + end + end + end end diff --git a/spec/models/follow_spec.rb b/spec/models/follow_spec.rb index e723a1ef2..c7743183c 100644 --- a/spec/models/follow_spec.rb +++ b/spec/models/follow_spec.rb @@ -1,16 +1,13 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Follow, type: :model do +RSpec.describe Follow do let(:alice) { Fabricate(:account, username: 'alice') } let(:bob) { Fabricate(:account, username: 'bob') } describe 'validations' do - subject { Follow.new(account: alice, target_account: bob, rate_limit: true) } - - it 'has a valid fabricator' do - follow = Fabricate.build(:follow) - expect(follow).to be_valid - end + subject { described_class.new(account: alice, target_account: bob, rate_limit: true) } it 'is invalid without an account' do follow = Fabricate.build(:follow, account: nil) @@ -41,10 +38,10 @@ RSpec.describe Follow, type: :model do describe 'recent' do it 'sorts so that more recent follows comes earlier' do - follow0 = Follow.create!(account: alice, target_account: bob) - follow1 = Follow.create!(account: bob, target_account: alice) + follow0 = described_class.create!(account: alice, target_account: bob) + follow1 = described_class.create!(account: bob, target_account: alice) - a = Follow.recent.to_a + a = described_class.recent.to_a expect(a.size).to eq 2 expect(a[0]).to eq follow1 diff --git a/spec/models/form/account_batch_spec.rb b/spec/models/form/account_batch_spec.rb new file mode 100644 index 000000000..fd8e90901 --- /dev/null +++ b/spec/models/form/account_batch_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Form::AccountBatch do + let(:account_batch) { described_class.new } + + describe '#save' do + subject { account_batch.save } + + let(:account) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:account_ids) { [] } + let(:query) { Account.none } + + before do + account_batch.assign_attributes( + action: action, + current_account: account, + account_ids: account_ids, + query: query, + select_all_matching: select_all_matching + ) + end + + context 'when action is "suspend"' do + let(:action) { 'suspend' } + + let(:target_account) { Fabricate(:account) } + let(:target_account2) { Fabricate(:account) } + + before do + Fabricate(:report, target_account: target_account) + Fabricate(:report, target_account: target_account2) + end + + context 'when accounts are passed as account_ids' do + let(:select_all_matching) { '0' } + let(:account_ids) { [target_account.id, target_account2.id] } + + it 'suspends the expected users' do + expect { subject }.to change { [target_account.reload.suspended?, target_account2.reload.suspended?] }.from([false, false]).to([true, true]) + end + + it 'closes open reports targeting the suspended users' do + expect { subject }.to change { Report.unresolved.where(target_account: [target_account, target_account2]).count }.from(2).to(0) + end + end + + context 'when accounts are passed as a query' do + let(:select_all_matching) { '1' } + let(:query) { Account.where(id: [target_account.id, target_account2.id]) } + + it 'suspends the expected users' do + expect { subject }.to change { [target_account.reload.suspended?, target_account2.reload.suspended?] }.from([false, false]).to([true, true]) + end + + it 'closes open reports targeting the suspended users' do + expect { subject }.to change { Report.unresolved.where(target_account: [target_account, target_account2]).count }.from(2).to(0) + end + end + end + end +end diff --git a/spec/models/form/admin_settings_spec.rb b/spec/models/form/admin_settings_spec.rb new file mode 100644 index 000000000..0dc2d881a --- /dev/null +++ b/spec/models/form/admin_settings_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Form::AdminSettings do + describe 'validations' do + describe 'site_contact_username' do + context 'with no accounts' do + it 'is not valid' do + setting = described_class.new(site_contact_username: 'Test') + setting.valid? + + expect(setting).to model_have_error_on_field(:site_contact_username) + end + end + + context 'with an account' do + before { Fabricate(:account, username: 'Glorp') } + + it 'is not valid when account doesnt match' do + setting = described_class.new(site_contact_username: 'Test') + setting.valid? + + expect(setting).to model_have_error_on_field(:site_contact_username) + end + + it 'is valid when account matches' do + setting = described_class.new(site_contact_username: 'Glorp') + setting.valid? + + expect(setting).to_not model_have_error_on_field(:site_contact_username) + end + end + end + end +end diff --git a/spec/models/form/import_spec.rb b/spec/models/form/import_spec.rb new file mode 100644 index 000000000..2b70e396b --- /dev/null +++ b/spec/models/form/import_spec.rb @@ -0,0 +1,318 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Form::Import do + subject { described_class.new(current_account: account, type: import_type, mode: import_mode, data: data) } + + let(:account) { Fabricate(:account) } + let(:data) { fixture_file_upload(import_file) } + let(:import_mode) { 'merge' } + + describe 'validations' do + shared_examples 'incompatible import type' do |type, file| + let(:import_file) { file } + let(:import_type) { type } + + it 'has errors' do + subject.validate + expect(subject.errors[:data]).to include(I18n.t('imports.errors.incompatible_type')) + end + end + + shared_examples 'too many CSV rows' do |type, file, allowed_rows| + let(:import_file) { file } + let(:import_type) { type } + + before do + stub_const 'Form::Import::ROWS_PROCESSING_LIMIT', allowed_rows + end + + it 'has errors' do + subject.validate + expect(subject.errors[:data]).to include(I18n.t('imports.errors.over_rows_processing_limit', count: Form::Import::ROWS_PROCESSING_LIMIT)) + end + end + + shared_examples 'valid import' do |type, file| + let(:import_file) { file } + let(:import_type) { type } + + it 'passes validation' do + expect(subject).to be_valid + end + end + + context 'when the file too large' do + let(:import_type) { 'following' } + let(:import_file) { 'imports.txt' } + + before do + stub_const 'Form::Import::FILE_SIZE_LIMIT', 5 + end + + it 'has errors' do + subject.validate + expect(subject.errors[:data]).to include(I18n.t('imports.errors.too_large')) + end + end + + context 'when the CSV file is malformed CSV' do + let(:import_type) { 'following' } + let(:import_file) { 'boop.ogg' } + + it 'has errors' do + # NOTE: not testing more specific error because we don't know the string to match + expect(subject).to model_have_error_on_field(:data) + end + end + + context 'when importing more follows than allowed' do + let(:import_type) { 'following' } + let(:import_file) { 'imports.txt' } + + before do + allow(FollowLimitValidator).to receive(:limit_for_account).with(account).and_return(1) + end + + it 'has errors' do + subject.validate + expect(subject.errors[:data]).to include(I18n.t('users.follow_limit_reached', limit: 1)) + end + end + + it_behaves_like 'too many CSV rows', 'following', 'imports.txt', 1 + it_behaves_like 'too many CSV rows', 'blocking', 'imports.txt', 1 + it_behaves_like 'too many CSV rows', 'muting', 'imports.txt', 1 + it_behaves_like 'too many CSV rows', 'domain_blocking', 'domain_blocks.csv', 2 + it_behaves_like 'too many CSV rows', 'bookmarks', 'bookmark-imports.txt', 3 + it_behaves_like 'too many CSV rows', 'lists', 'lists.csv', 2 + + # Importing list of addresses with no headers into various types + it_behaves_like 'valid import', 'following', 'imports.txt' + it_behaves_like 'valid import', 'blocking', 'imports.txt' + it_behaves_like 'valid import', 'muting', 'imports.txt' + + # Importing domain blocks with headers into expected type + it_behaves_like 'valid import', 'domain_blocking', 'domain_blocks.csv' + + # Importing bookmarks list with no headers into expected type + it_behaves_like 'valid import', 'bookmarks', 'bookmark-imports.txt' + + # Importing lists with no headers into expected type + it_behaves_like 'valid import', 'lists', 'lists.csv' + + # Importing followed accounts with headers into various compatible types + it_behaves_like 'valid import', 'following', 'following_accounts.csv' + it_behaves_like 'valid import', 'blocking', 'following_accounts.csv' + it_behaves_like 'valid import', 'muting', 'following_accounts.csv' + + # Importing domain blocks with headers into incompatible types + it_behaves_like 'incompatible import type', 'following', 'domain_blocks.csv' + it_behaves_like 'incompatible import type', 'blocking', 'domain_blocks.csv' + it_behaves_like 'incompatible import type', 'muting', 'domain_blocks.csv' + it_behaves_like 'incompatible import type', 'bookmarks', 'domain_blocks.csv' + + # Importing followed accounts with headers into incompatible types + it_behaves_like 'incompatible import type', 'domain_blocking', 'following_accounts.csv' + it_behaves_like 'incompatible import type', 'bookmarks', 'following_accounts.csv' + end + + describe '#guessed_type' do + shared_examples 'with enough information' do |type, file, original_filename, expected_guess| + let(:import_file) { file } + let(:import_type) { type } + + before do + allow(data).to receive(:original_filename).and_return(original_filename) + end + + it 'guesses the expected type' do + expect(subject.guessed_type).to eq expected_guess + end + end + + context 'when the headers are enough to disambiguate' do + it_behaves_like 'with enough information', 'following', 'following_accounts.csv', 'import.csv', :following + it_behaves_like 'with enough information', 'blocking', 'following_accounts.csv', 'import.csv', :following + it_behaves_like 'with enough information', 'muting', 'following_accounts.csv', 'import.csv', :following + + it_behaves_like 'with enough information', 'following', 'muted_accounts.csv', 'imports.csv', :muting + it_behaves_like 'with enough information', 'blocking', 'muted_accounts.csv', 'imports.csv', :muting + it_behaves_like 'with enough information', 'muting', 'muted_accounts.csv', 'imports.csv', :muting + end + + context 'when the file name is enough to disambiguate' do + it_behaves_like 'with enough information', 'following', 'imports.txt', 'following_accounts.csv', :following + it_behaves_like 'with enough information', 'blocking', 'imports.txt', 'following_accounts.csv', :following + it_behaves_like 'with enough information', 'muting', 'imports.txt', 'following_accounts.csv', :following + + it_behaves_like 'with enough information', 'following', 'imports.txt', 'follows.csv', :following + it_behaves_like 'with enough information', 'blocking', 'imports.txt', 'follows.csv', :following + it_behaves_like 'with enough information', 'muting', 'imports.txt', 'follows.csv', :following + + it_behaves_like 'with enough information', 'following', 'imports.txt', 'blocked_accounts.csv', :blocking + it_behaves_like 'with enough information', 'blocking', 'imports.txt', 'blocked_accounts.csv', :blocking + it_behaves_like 'with enough information', 'muting', 'imports.txt', 'blocked_accounts.csv', :blocking + + it_behaves_like 'with enough information', 'following', 'imports.txt', 'blocks.csv', :blocking + it_behaves_like 'with enough information', 'blocking', 'imports.txt', 'blocks.csv', :blocking + it_behaves_like 'with enough information', 'muting', 'imports.txt', 'blocks.csv', :blocking + + it_behaves_like 'with enough information', 'following', 'imports.txt', 'muted_accounts.csv', :muting + it_behaves_like 'with enough information', 'blocking', 'imports.txt', 'muted_accounts.csv', :muting + it_behaves_like 'with enough information', 'muting', 'imports.txt', 'muted_accounts.csv', :muting + + it_behaves_like 'with enough information', 'following', 'imports.txt', 'mutes.csv', :muting + it_behaves_like 'with enough information', 'blocking', 'imports.txt', 'mutes.csv', :muting + it_behaves_like 'with enough information', 'muting', 'imports.txt', 'mutes.csv', :muting + end + end + + describe '#likely_mismatched?' do + shared_examples 'with matching types' do |type, file, original_filename = nil| + let(:import_file) { file } + let(:import_type) { type } + + before do + allow(data).to receive(:original_filename).and_return(original_filename) if original_filename.present? + end + + it 'returns false' do + expect(subject.likely_mismatched?).to be false + end + end + + shared_examples 'with mismatching types' do |type, file, original_filename = nil| + let(:import_file) { file } + let(:import_type) { type } + + before do + allow(data).to receive(:original_filename).and_return(original_filename) if original_filename.present? + end + + it 'returns true' do + expect(subject.likely_mismatched?).to be true + end + end + + it_behaves_like 'with matching types', 'following', 'following_accounts.csv' + it_behaves_like 'with matching types', 'following', 'following_accounts.csv', 'imports.txt' + it_behaves_like 'with matching types', 'following', 'imports.txt' + it_behaves_like 'with matching types', 'blocking', 'imports.txt', 'blocks.csv' + it_behaves_like 'with matching types', 'blocking', 'imports.txt' + it_behaves_like 'with matching types', 'muting', 'muted_accounts.csv' + it_behaves_like 'with matching types', 'muting', 'muted_accounts.csv', 'imports.txt' + it_behaves_like 'with matching types', 'muting', 'imports.txt' + it_behaves_like 'with matching types', 'domain_blocking', 'domain_blocks.csv' + it_behaves_like 'with matching types', 'domain_blocking', 'domain_blocks.csv', 'imports.txt' + it_behaves_like 'with matching types', 'bookmarks', 'bookmark-imports.txt' + it_behaves_like 'with matching types', 'bookmarks', 'bookmark-imports.txt', 'imports.txt' + + it_behaves_like 'with mismatching types', 'following', 'imports.txt', 'blocks.csv' + it_behaves_like 'with mismatching types', 'following', 'imports.txt', 'blocked_accounts.csv' + it_behaves_like 'with mismatching types', 'following', 'imports.txt', 'mutes.csv' + it_behaves_like 'with mismatching types', 'following', 'imports.txt', 'muted_accounts.csv' + it_behaves_like 'with mismatching types', 'following', 'muted_accounts.csv' + it_behaves_like 'with mismatching types', 'following', 'muted_accounts.csv', 'imports.txt' + it_behaves_like 'with mismatching types', 'blocking', 'following_accounts.csv' + it_behaves_like 'with mismatching types', 'blocking', 'following_accounts.csv', 'imports.txt' + it_behaves_like 'with mismatching types', 'blocking', 'muted_accounts.csv' + it_behaves_like 'with mismatching types', 'blocking', 'muted_accounts.csv', 'imports.txt' + it_behaves_like 'with mismatching types', 'blocking', 'imports.txt', 'follows.csv' + it_behaves_like 'with mismatching types', 'blocking', 'imports.txt', 'following_accounts.csv' + it_behaves_like 'with mismatching types', 'blocking', 'imports.txt', 'mutes.csv' + it_behaves_like 'with mismatching types', 'blocking', 'imports.txt', 'muted_accounts.csv' + it_behaves_like 'with mismatching types', 'muting', 'following_accounts.csv' + it_behaves_like 'with mismatching types', 'muting', 'following_accounts.csv', 'imports.txt' + it_behaves_like 'with mismatching types', 'muting', 'imports.txt', 'follows.csv' + it_behaves_like 'with mismatching types', 'muting', 'imports.txt', 'following_accounts.csv' + it_behaves_like 'with mismatching types', 'muting', 'imports.txt', 'blocks.csv' + it_behaves_like 'with mismatching types', 'muting', 'imports.txt', 'blocked_accounts.csv' + end + + describe 'save' do + shared_examples 'on successful import' do |type, mode, file, expected_rows| + let(:import_type) { type } + let(:import_file) { file } + let(:import_mode) { mode } + + before do + subject.save + end + + it 'creates the expected rows' do + expect(account.bulk_imports.first.rows.pluck(:data)).to match_array(expected_rows) + end + + context 'with a BulkImport' do + let(:bulk_import) { account.bulk_imports.first } + + it 'creates a non-nil bulk import' do + expect(bulk_import).to_not be_nil + end + + it 'matches the subjects type' do + expect(bulk_import.type.to_sym).to eq subject.type.to_sym + end + + it 'matches the subjects original filename' do + expect(bulk_import.original_filename).to eq subject.data.original_filename + end + + it 'matches the subjects likely_mismatched? value' do + expect(bulk_import.likely_mismatched?).to eq subject.likely_mismatched? + end + + it 'matches the subject overwrite value' do + expect(bulk_import.overwrite?).to eq !!subject.overwrite # rubocop:disable Style/DoubleNegation + end + + it 'has zero processed items' do + expect(bulk_import.processed_items).to eq 0 + end + + it 'has zero imported items' do + expect(bulk_import.imported_items).to eq 0 + end + + it 'has a correct total_items value' do + expect(bulk_import.total_items).to eq bulk_import.rows.count + end + + it 'defaults to unconfirmed true' do + expect(bulk_import.unconfirmed?).to be true + end + end + end + + it_behaves_like 'on successful import', 'following', 'merge', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) + it_behaves_like 'on successful import', 'following', 'overwrite', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) + it_behaves_like 'on successful import', 'blocking', 'merge', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) + it_behaves_like 'on successful import', 'blocking', 'overwrite', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) + it_behaves_like 'on successful import', 'muting', 'merge', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } }) + it_behaves_like 'on successful import', 'domain_blocking', 'merge', 'domain_blocks.csv', (%w(bad.domain worse.domain reject.media).map { |domain| { 'domain' => domain } }) + it_behaves_like 'on successful import', 'bookmarks', 'merge', 'bookmark-imports.txt', (%w(https://example.com/statuses/1312 https://local.com/users/foo/statuses/42 https://unknown-remote.com/users/bar/statuses/1 https://example.com/statuses/direct).map { |uri| { 'uri' => uri } }) + + it_behaves_like 'on successful import', 'following', 'merge', 'following_accounts.csv', [ + { 'acct' => 'user@example.com', 'show_reblogs' => true, 'notify' => false, 'languages' => nil }, + { 'acct' => 'user@test.com', 'show_reblogs' => true, 'notify' => true, 'languages' => ['en', 'fr'] }, + ] + + it_behaves_like 'on successful import', 'muting', 'merge', 'muted_accounts.csv', [ + { 'acct' => 'user@example.com', 'hide_notifications' => true }, + { 'acct' => 'user@test.com', 'hide_notifications' => false }, + ] + + it_behaves_like 'on successful import', 'lists', 'merge', 'lists.csv', [ + { 'acct' => 'gargron@example.com', 'list_name' => 'Mastodon project' }, + { 'acct' => 'mastodon@example.com', 'list_name' => 'Mastodon project' }, + { 'acct' => 'foo@example.com', 'list_name' => 'test' }, + ] + + # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users + # + # https://github.com/mastodon/mastodon/issues/20571 + it_behaves_like 'on successful import', 'following', 'merge', 'utf8-followers.txt', [{ 'acct' => 'nare@թութ.հայ' }] + end +end diff --git a/spec/models/form/status_filter_batch_action_spec.rb b/spec/models/form/status_filter_batch_action_spec.rb new file mode 100644 index 000000000..f06a11cc8 --- /dev/null +++ b/spec/models/form/status_filter_batch_action_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Form::StatusFilterBatchAction do + describe '#save!' do + it 'does nothing if status_filter_ids is empty' do + batch_action = described_class.new(status_filter_ids: []) + + expect(batch_action.save!).to be_nil + end + end +end diff --git a/spec/models/home_feed_spec.rb b/spec/models/home_feed_spec.rb index 80f6edbff..06bb63b1a 100644 --- a/spec/models/home_feed_spec.rb +++ b/spec/models/home_feed_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe HomeFeed, type: :model do - let(:account) { Fabricate(:account) } - +RSpec.describe HomeFeed do subject { described_class.new(account) } + let(:account) { Fabricate(:account) } + describe '#get' do before do Fabricate(:status, account: account, id: 1) @@ -25,7 +27,6 @@ RSpec.describe HomeFeed, type: :model do results = subject.get(3) expect(results.map(&:id)).to eq [3, 2] - expect(results.first.attributes.keys).to eq %w(id updated_at) end end diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb index 081c254d8..22c8dbf22 100644 --- a/spec/models/identity_spec.rb +++ b/spec/models/identity_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Identity, type: :model do +RSpec.describe Identity do describe '.find_for_omniauth' do let(:auth) { Fabricate(:identity, user: Fabricate(:user)) } @@ -10,7 +12,7 @@ RSpec.describe Identity, type: :model do end it 'returns an instance of Identity' do - expect(described_class.find_for_omniauth(auth)).to be_instance_of Identity + expect(described_class.find_for_omniauth(auth)).to be_instance_of described_class end end end diff --git a/spec/models/import_spec.rb b/spec/models/import_spec.rb index a5eec1722..3605f0b9b 100644 --- a/spec/models/import_spec.rb +++ b/spec/models/import_spec.rb @@ -1,33 +1,25 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Import, type: :model do - let (:account) { Fabricate(:account) } - let (:type) { 'following' } - let (:data) { attachment_fixture('imports.txt') } +RSpec.describe Import do + let(:account) { Fabricate(:account) } + let(:type) { 'following' } + let(:data) { attachment_fixture('imports.txt') } describe 'validations' do it 'has a valid parameters' do - import = Import.create(account: account, type: type, data: data) + import = described_class.create(account: account, type: type, data: data) expect(import).to be_valid end it 'is invalid without an type' do - import = Import.create(account: account, data: data) + import = described_class.create(account: account, data: data) expect(import).to model_have_error_on_field(:type) end it 'is invalid without a data' do - import = Import.create(account: account, type: type) - expect(import).to model_have_error_on_field(:data) - end - - it 'is invalid with too many rows in data' do - import = Import.create(account: account, type: type, data: StringIO.new("foo@bar.com\n" * (ImportService::ROWS_PROCESSING_LIMIT + 10))) - expect(import).to model_have_error_on_field(:data) - end - - it 'is invalid when there are more rows when following limit' do - import = Import.create(account: account, type: type, data: StringIO.new("foo@bar.com\n" * (FollowLimitValidator.limit_for_account(account) + 10))) + import = described_class.create(account: account, type: type) expect(import).to model_have_error_on_field(:data) end end diff --git a/spec/models/invite_spec.rb b/spec/models/invite_spec.rb index b0596c561..4ad589f2c 100644 --- a/spec/models/invite_spec.rb +++ b/spec/models/invite_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Invite, type: :model do +RSpec.describe Invite do describe '#valid_for_use?' do it 'returns true when there are no limitations' do invite = Fabricate(:invite, max_uses: nil, expires_at: nil) diff --git a/spec/models/ip_block_spec.rb b/spec/models/ip_block_spec.rb index 6603c6417..ed5882667 100644 --- a/spec/models/ip_block_spec.rb +++ b/spec/models/ip_block_spec.rb @@ -1,5 +1,15 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe IpBlock, type: :model do - pending "add some examples to (or delete) #{__FILE__}" +describe IpBlock do + describe 'to_log_human_identifier' do + let(:ip_block) { described_class.new(ip: '192.168.0.1') } + + it 'combines the IP and prefix into a string' do + result = ip_block.to_log_human_identifier + + expect(result).to eq('192.168.0.1/32') + end + end end diff --git a/spec/models/list_account_spec.rb b/spec/models/list_account_spec.rb deleted file mode 100644 index a0cf02efe..000000000 --- a/spec/models/list_account_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe ListAccount, type: :model do -end diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb deleted file mode 100644 index b780bb1de..000000000 --- a/spec/models/list_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe List, type: :model do -end diff --git a/spec/models/login_activity_spec.rb b/spec/models/login_activity_spec.rb deleted file mode 100644 index ba2d207c9..000000000 --- a/spec/models/login_activity_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -RSpec.describe LoginActivity, type: :model do - -end diff --git a/spec/models/marker_spec.rb b/spec/models/marker_spec.rb index d716aa75c..51dd58438 100644 --- a/spec/models/marker_spec.rb +++ b/spec/models/marker_spec.rb @@ -1,5 +1,16 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Marker, type: :model do - pending "add some examples to (or delete) #{__FILE__}" +describe Marker do + describe 'validations' do + describe 'timeline' do + it 'must be included in valid list' do + record = described_class.new(timeline: 'not real timeline') + + expect(record).to_not be_valid + expect(record).to model_have_error_on_field(:timeline) + end + end + end end diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb index c3283ccb0..6a82c8135 100644 --- a/spec/models/media_attachment_spec.rb +++ b/spec/models/media_attachment_spec.rb @@ -1,72 +1,74 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe MediaAttachment, type: :model do +RSpec.describe MediaAttachment, paperclip_processing: true do describe 'local?' do - let(:media_attachment) { Fabricate(:media_attachment, remote_url: remote_url) } - subject { media_attachment.local? } - context 'remote_url is blank' do + let(:media_attachment) { described_class.new(remote_url: remote_url) } + + context 'when remote_url is blank' do let(:remote_url) { '' } it 'returns true' do - is_expected.to be true + expect(subject).to be true end end - context 'remote_url is present' do + context 'when remote_url is present' do let(:remote_url) { 'remote_url' } it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end describe 'needs_redownload?' do - let(:media_attachment) { Fabricate(:media_attachment, remote_url: remote_url, file: file) } - subject { media_attachment.needs_redownload? } - context 'file is blank' do + let(:media_attachment) { described_class.new(remote_url: remote_url, file: file) } + + context 'when file is blank' do let(:file) { nil } - context 'remote_url is present' do + context 'when remote_url is present' do let(:remote_url) { 'remote_url' } it 'returns true' do - is_expected.to be true + expect(subject).to be true end end end - context 'file is present' do + context 'when file is present' do let(:file) { attachment_fixture('avatar.gif') } - context 'remote_url is blank' do + context 'when remote_url is blank' do let(:remote_url) { '' } it 'returns false' do - is_expected.to be false + expect(subject).to be false end end - context 'remote_url is present' do + context 'when remote_url is present' do let(:remote_url) { 'remote_url' } it 'returns true' do - is_expected.to be false + expect(subject).to be false end end end end describe '#to_param' do - let(:media_attachment) { Fabricate(:media_attachment, shortcode: shortcode) } - let(:shortcode) { nil } + let(:media_attachment) { Fabricate.build(:media_attachment, shortcode: shortcode, id: id) } context 'when media attachment has a shortcode' do let(:shortcode) { 'foo' } + let(:id) { 123 } it 'returns shortcode' do expect(media_attachment.to_param).to eq shortcode @@ -75,31 +77,104 @@ RSpec.describe MediaAttachment, type: :model do context 'when media attachment does not have a shortcode' do let(:shortcode) { nil } + let(:id) { 123 } it 'returns string representation of id' do - expect(media_attachment.to_param).to eq media_attachment.id.to_s + expect(media_attachment.to_param).to eq id.to_s end end end - describe 'animated gif conversion' do - let(:media) { MediaAttachment.create(account: Fabricate(:account), file: attachment_fixture('avatar.gif')) } - - it 'sets type to gifv' do - expect(media.type).to eq 'gifv' + shared_examples 'static 600x400 image' do |content_type, extension| + after do + media.destroy end - it 'converts original file to mp4' do - expect(media.file_content_type).to eq 'video/mp4' + it 'saves media attachment with correct file metadata' do + expect(media.persisted?).to be true + expect(media.file).to_not be_nil + + # completes processing + expect(media.processing_complete?).to be true + + # sets type + expect(media.type).to eq 'image' + + # sets content type + expect(media.file_content_type).to eq content_type + + # sets file extension + expect(media.file_file_name).to end_with extension + + # Rack::Mime (used by PublicFileServerMiddleware) recognizes file extension + expect(Rack::Mime.mime_type(extension, nil)).to eq content_type end - it 'sets meta' do - expect(media.file.meta["original"]["width"]).to eq 128 - expect(media.file.meta["original"]["height"]).to eq 128 + it 'saves media attachment with correct size metadata' do + # strips original file name + expect(media.file_file_name).to_not start_with '600x400' + + # sets meta for original + expect(media.file.meta['original']['width']).to eq 600 + expect(media.file.meta['original']['height']).to eq 400 + expect(media.file.meta['original']['aspect']).to eq 1.5 + + # sets meta for thumbnail + expect(media.file.meta['small']['width']).to eq 588 + expect(media.file.meta['small']['height']).to eq 392 + expect(media.file.meta['small']['aspect']).to eq 1.5 end end - describe 'non-animated gif non-conversion' do + describe 'jpeg' do + let(:media) { Fabricate(:media_attachment, file: attachment_fixture('600x400.jpeg')) } + + it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg' + end + + describe 'png' do + let(:media) { Fabricate(:media_attachment, file: attachment_fixture('600x400.png')) } + + it_behaves_like 'static 600x400 image', 'image/png', '.png' + end + + describe 'webp' do + let(:media) { Fabricate(:media_attachment, file: attachment_fixture('600x400.webp')) } + + it_behaves_like 'static 600x400 image', 'image/webp', '.webp' + end + + describe 'avif' do + let(:media) { Fabricate(:media_attachment, file: attachment_fixture('600x400.avif')) } + + it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg' + end + + describe 'heic' do + let(:media) { Fabricate(:media_attachment, file: attachment_fixture('600x400.heic')) } + + it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg' + end + + describe 'base64-encoded image' do + let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('600x400.jpeg').read)}" } + let(:media) { Fabricate(:media_attachment, file: base64_attachment) } + + it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg' + end + + describe 'animated gif' do + let(:media) { Fabricate(:media_attachment, file: attachment_fixture('avatar.gif')) } + + it 'sets correct file metadata' do + expect(media.type).to eq 'gifv' + expect(media.file_content_type).to eq 'video/mp4' + expect(media.file.meta['original']['width']).to eq 128 + expect(media.file.meta['original']['height']).to eq 128 + end + end + + describe 'static gif' do fixtures = [ { filename: 'attachment.gif', width: 600, height: 400, aspect: 1.5 }, { filename: 'mini-static.gif', width: 32, height: 32, aspect: 1.0 }, @@ -107,51 +182,33 @@ RSpec.describe MediaAttachment, type: :model do fixtures.each do |fixture| context fixture[:filename] do - let(:media) { MediaAttachment.create(account: Fabricate(:account), file: attachment_fixture(fixture[:filename])) } + let(:media) { Fabricate(:media_attachment, file: attachment_fixture(fixture[:filename])) } - it 'sets type to image' do + it 'sets correct file metadata' do expect(media.type).to eq 'image' - end - - it 'leaves original file as-is' do expect(media.file_content_type).to eq 'image/gif' - end - - it 'sets meta' do - expect(media.file.meta["original"]["width"]).to eq fixture[:width] - expect(media.file.meta["original"]["height"]).to eq fixture[:height] - expect(media.file.meta["original"]["aspect"]).to eq fixture[:aspect] + expect(media.file.meta['original']['width']).to eq fixture[:width] + expect(media.file.meta['original']['height']).to eq fixture[:height] + expect(media.file.meta['original']['aspect']).to eq fixture[:aspect] end end end end describe 'ogg with cover art' do - let(:media) { MediaAttachment.create(account: Fabricate(:account), file: attachment_fixture('boop.ogg')) } + let(:media) { Fabricate(:media_attachment, file: attachment_fixture('boop.ogg')) } - it 'detects it as an audio file' do + it 'sets correct file metadata' do expect(media.type).to eq 'audio' - end - - it 'sets meta for the duration' do expect(media.file.meta['original']['duration']).to be_within(0.05).of(0.235102) - end - - it 'extracts thumbnail' do - expect(media.thumbnail.present?).to eq true - end - - it 'extracts colors from thumbnail' do + expect(media.thumbnail.present?).to be true expect(media.file.meta['colors']['background']).to eq '#3088d4' - end - - it 'gives the file a random name' do expect(media.file_file_name).to_not eq 'boop.ogg' end end describe 'mp3 with large cover art' do - let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('boop.mp3')) } + let(:media) { Fabricate(:media_attachment, file: attachment_fixture('boop.mp3')) } it 'detects it as an audio file' do expect(media.type).to eq 'audio' @@ -170,66 +227,37 @@ RSpec.describe MediaAttachment, type: :model do end end - describe 'jpeg' do - let(:media) { MediaAttachment.create(account: Fabricate(:account), file: attachment_fixture('attachment.jpg')) } - - it 'sets meta for different style' do - expect(media.file.meta["original"]["width"]).to eq 600 - expect(media.file.meta["original"]["height"]).to eq 400 - expect(media.file.meta["original"]["aspect"]).to eq 1.5 - expect(media.file.meta["small"]["width"]).to eq 588 - expect(media.file.meta["small"]["height"]).to eq 392 - expect(media.file.meta["small"]["aspect"]).to eq 1.5 - end - - it 'gives the file a random name' do - expect(media.file_file_name).to_not eq 'attachment.jpg' - end - end - - describe 'base64-encoded jpeg' do - let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" } - let(:media) { MediaAttachment.create(account: Fabricate(:account), file: base64_attachment) } - - it 'saves media attachment' do - expect(media.persisted?).to be true - expect(media.file).to_not be_nil - end - - it 'gives the file a file name' do - expect(media.file_file_name).to_not be_blank - end - end - it 'is invalid without file' do - media = MediaAttachment.new(account: Fabricate(:account)) + media = described_class.new + expect(media.valid?).to be false + expect(media).to model_have_error_on_field(:file) end describe 'size limit validation' do it 'rejects video files that are too large' do stub_const 'MediaAttachment::IMAGE_LIMIT', 100.megabytes stub_const 'MediaAttachment::VIDEO_LIMIT', 1.kilobyte - expect { MediaAttachment.create!(account: Fabricate(:account), file: attachment_fixture('attachment.webm')) }.to raise_error(ActiveRecord::RecordInvalid) + expect { Fabricate(:media_attachment, file: attachment_fixture('attachment.webm')) }.to raise_error(ActiveRecord::RecordInvalid) end it 'accepts video files that are small enough' do stub_const 'MediaAttachment::IMAGE_LIMIT', 1.kilobyte stub_const 'MediaAttachment::VIDEO_LIMIT', 100.megabytes - media = MediaAttachment.create!(account: Fabricate(:account), file: attachment_fixture('attachment.webm')) + media = Fabricate(:media_attachment, file: attachment_fixture('attachment.webm')) expect(media.valid?).to be true end it 'rejects image files that are too large' do stub_const 'MediaAttachment::IMAGE_LIMIT', 1.kilobyte stub_const 'MediaAttachment::VIDEO_LIMIT', 100.megabytes - expect { MediaAttachment.create!(account: Fabricate(:account), file: attachment_fixture('attachment.jpg')) }.to raise_error(ActiveRecord::RecordInvalid) + expect { Fabricate(:media_attachment, file: attachment_fixture('attachment.jpg')) }.to raise_error(ActiveRecord::RecordInvalid) end it 'accepts image files that are small enough' do stub_const 'MediaAttachment::IMAGE_LIMIT', 100.megabytes stub_const 'MediaAttachment::VIDEO_LIMIT', 1.kilobyte - media = MediaAttachment.create!(account: Fabricate(:account), file: attachment_fixture('attachment.jpg')) + media = Fabricate(:media_attachment, file: attachment_fixture('attachment.jpg')) expect(media.valid?).to be true end end diff --git a/spec/models/mention_spec.rb b/spec/models/mention_spec.rb index dbcf6a32c..b241049a5 100644 --- a/spec/models/mention_spec.rb +++ b/spec/models/mention_spec.rb @@ -1,12 +1,9 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Mention, type: :model do +RSpec.describe Mention do describe 'validations' do - it 'has a valid fabricator' do - mention = Fabricate.build(:mention) - expect(mention).to be_valid - end - it 'is invalid without an account' do mention = Fabricate.build(:mention, account: nil) mention.valid? diff --git a/spec/models/mute_spec.rb b/spec/models/mute_spec.rb deleted file mode 100644 index 38a87bdf4..000000000 --- a/spec/models/mute_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe Mute, type: :model do -end diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index 1e9e45d8d..d6e228202 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Notification, type: :model do +RSpec.describe Notification do describe '#target_status' do let(:notification) { Fabricate(:notification, activity: activity) } let(:status) { Fabricate(:status) } @@ -8,7 +10,7 @@ RSpec.describe Notification, type: :model do let(:favourite) { Fabricate(:favourite, status: status) } let(:mention) { Fabricate(:mention, status: status) } - context 'activity is reblog' do + context 'when Activity is reblog' do let(:activity) { reblog } it 'returns status' do @@ -16,7 +18,7 @@ RSpec.describe Notification, type: :model do end end - context 'activity is favourite' do + context 'when Activity is favourite' do let(:type) { :favourite } let(:activity) { favourite } @@ -25,7 +27,7 @@ RSpec.describe Notification, type: :model do end end - context 'activity is mention' do + context 'when Activity is mention' do let(:activity) { mention } it 'returns status' do @@ -36,22 +38,22 @@ RSpec.describe Notification, type: :model do describe '#type' do it 'returns :reblog for a Status' do - notification = Notification.new(activity: Status.new) + notification = described_class.new(activity: Status.new) expect(notification.type).to eq :reblog end it 'returns :mention for a Mention' do - notification = Notification.new(activity: Mention.new) + notification = described_class.new(activity: Mention.new) expect(notification.type).to eq :mention end it 'returns :favourite for a Favourite' do - notification = Notification.new(activity: Favourite.new) + notification = described_class.new(activity: Favourite.new) expect(notification.type).to eq :favourite end it 'returns :follow for a Follow' do - notification = Notification.new(activity: Follow.new) + notification = described_class.new(activity: Follow.new) expect(notification.type).to eq :follow end end @@ -64,15 +66,15 @@ RSpec.describe Notification, type: :model do end end - context 'notifications are empty' do + context 'when notifications are empty' do let(:notifications) { [] } it 'returns []' do - is_expected.to eq [] + expect(subject).to eq [] end end - context 'notifications are present' do + context 'when notifications are present' do before do notifications.each(&:reload) end @@ -97,73 +99,87 @@ RSpec.describe Notification, type: :model do ] end - it 'preloads target status' do - # mention - expect(subject[0].type).to eq :mention - expect(subject[0].association(:mention)).to be_loaded - expect(subject[0].mention.association(:status)).to be_loaded + context 'with a preloaded target status' do + it 'preloads mention' do + expect(subject[0].type).to eq :mention + expect(subject[0].association(:mention)).to be_loaded + expect(subject[0].mention.association(:status)).to be_loaded + end - # status - expect(subject[1].type).to eq :status - expect(subject[1].association(:status)).to be_loaded + it 'preloads status' do + expect(subject[1].type).to eq :status + expect(subject[1].association(:status)).to be_loaded + end - # reblog - expect(subject[2].type).to eq :reblog - expect(subject[2].association(:status)).to be_loaded - expect(subject[2].status.association(:reblog)).to be_loaded + it 'preloads reblog' do + expect(subject[2].type).to eq :reblog + expect(subject[2].association(:status)).to be_loaded + expect(subject[2].status.association(:reblog)).to be_loaded + end - # follow: nothing - expect(subject[3].type).to eq :follow - expect(subject[3].target_status).to be_nil + it 'preloads follow as nil' do + expect(subject[3].type).to eq :follow + expect(subject[3].target_status).to be_nil + end - # follow_request: nothing - expect(subject[4].type).to eq :follow_request - expect(subject[4].target_status).to be_nil + it 'preloads follow_request as nill' do + expect(subject[4].type).to eq :follow_request + expect(subject[4].target_status).to be_nil + end - # favourite - expect(subject[5].type).to eq :favourite - expect(subject[5].association(:favourite)).to be_loaded - expect(subject[5].favourite.association(:status)).to be_loaded + it 'preloads favourite' do + expect(subject[5].type).to eq :favourite + expect(subject[5].association(:favourite)).to be_loaded + expect(subject[5].favourite.association(:status)).to be_loaded + end - # poll - expect(subject[6].type).to eq :poll - expect(subject[6].association(:poll)).to be_loaded - expect(subject[6].poll.association(:status)).to be_loaded + it 'preloads poll' do + expect(subject[6].type).to eq :poll + expect(subject[6].association(:poll)).to be_loaded + expect(subject[6].poll.association(:status)).to be_loaded + end end - it 'replaces to cached status' do - # mention - expect(subject[0].type).to eq :mention - expect(subject[0].target_status.association(:account)).to be_loaded - expect(subject[0].target_status).to eq mention.status + context 'with a cached status' do + it 'replaces mention' do + expect(subject[0].type).to eq :mention + expect(subject[0].target_status.association(:account)).to be_loaded + expect(subject[0].target_status).to eq mention.status + end - # status - expect(subject[1].type).to eq :status - expect(subject[1].target_status.association(:account)).to be_loaded - expect(subject[1].target_status).to eq status + it 'replaces status' do + expect(subject[1].type).to eq :status + expect(subject[1].target_status.association(:account)).to be_loaded + expect(subject[1].target_status).to eq status + end - # reblog - expect(subject[2].type).to eq :reblog - expect(subject[2].target_status.association(:account)).to be_loaded - expect(subject[2].target_status).to eq reblog.reblog + it 'replaces reblog' do + expect(subject[2].type).to eq :reblog + expect(subject[2].target_status.association(:account)).to be_loaded + expect(subject[2].target_status).to eq reblog.reblog + end - # follow: nothing - expect(subject[3].type).to eq :follow - expect(subject[3].target_status).to be_nil + it 'replaces follow' do + expect(subject[3].type).to eq :follow + expect(subject[3].target_status).to be_nil + end - # follow_request: nothing - expect(subject[4].type).to eq :follow_request - expect(subject[4].target_status).to be_nil + it 'replaces follow_request' do + expect(subject[4].type).to eq :follow_request + expect(subject[4].target_status).to be_nil + end - # favourite - expect(subject[5].type).to eq :favourite - expect(subject[5].target_status.association(:account)).to be_loaded - expect(subject[5].target_status).to eq favourite.status + it 'replaces favourite' do + expect(subject[5].type).to eq :favourite + expect(subject[5].target_status.association(:account)).to be_loaded + expect(subject[5].target_status).to eq favourite.status + end - # poll - expect(subject[6].type).to eq :poll - expect(subject[6].target_status.association(:account)).to be_loaded - expect(subject[6].target_status).to eq poll.status + it 'replaces poll' do + expect(subject[6].type).to eq :poll + expect(subject[6].target_status.association(:account)).to be_loaded + expect(subject[6].target_status).to eq poll.status + end end end end diff --git a/spec/models/one_time_key_spec.rb b/spec/models/one_time_key_spec.rb index 34598334c..6ff7ffc5c 100644 --- a/spec/models/one_time_key_spec.rb +++ b/spec/models/one_time_key_spec.rb @@ -1,5 +1,23 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe OneTimeKey, type: :model do +describe OneTimeKey do + describe 'validations' do + context 'with an invalid signature' do + let(:one_time_key) { Fabricate.build(:one_time_key, signature: 'wrong!') } + it 'is invalid' do + expect(one_time_key).to_not be_valid + end + end + + context 'with an invalid key' do + let(:one_time_key) { Fabricate.build(:one_time_key, key: 'wrong!') } + + it 'is invalid' do + expect(one_time_key).to_not be_valid + end + end + end end diff --git a/spec/models/poll_spec.rb b/spec/models/poll_spec.rb index 666f8ca68..8ae04ca41 100644 --- a/spec/models/poll_spec.rb +++ b/spec/models/poll_spec.rb @@ -1,5 +1,32 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Poll, type: :model do - pending "add some examples to (or delete) #{__FILE__}" +describe Poll do + describe 'scopes' do + let(:status) { Fabricate(:status) } + let(:attached_poll) { Fabricate(:poll, status: status) } + let(:not_attached_poll) do + Fabricate(:poll).tap do |poll| + poll.status = nil + poll.save(validate: false) + end + end + + describe 'attached' do + it 'finds the correct records' do + results = described_class.attached + + expect(results).to eq([attached_poll]) + end + end + + describe 'unattached' do + it 'finds the correct records' do + results = described_class.unattached + + expect(results).to eq([not_attached_poll]) + end + end + end end diff --git a/spec/models/poll_vote_spec.rb b/spec/models/poll_vote_spec.rb index 563f34699..b017ea527 100644 --- a/spec/models/poll_vote_spec.rb +++ b/spec/models/poll_vote_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe PollVote, type: :model do +RSpec.describe PollVote do describe '#object_type' do let(:poll_vote) { Fabricate.build(:poll_vote) } @@ -10,4 +10,53 @@ RSpec.describe PollVote, type: :model do expect(poll_vote.object_type).to eq :vote end end + + describe 'validations' do + context 'with a vote on an expired poll' do + it 'marks the vote invalid' do + poll = Fabricate.build(:poll, expires_at: 30.days.ago) + + vote = Fabricate.build(:poll_vote, poll: poll) + expect(vote).to_not be_valid + end + end + + context 'with invalid choices' do + it 'marks vote invalid with negative choice' do + poll = Fabricate.build(:poll) + + vote = Fabricate.build(:poll_vote, poll: poll, choice: -100) + expect(vote).to_not be_valid + end + + it 'marks vote invalid with choice in excess of options' do + poll = Fabricate.build(:poll, options: %w(a b c)) + + vote = Fabricate.build(:poll_vote, poll: poll, choice: 10) + expect(vote).to_not be_valid + end + end + + context 'with a poll where multiple is true' do + it 'does not allow a second vote on same choice from same account' do + poll = Fabricate(:poll, multiple: true, options: %w(a b c)) + first_vote = Fabricate(:poll_vote, poll: poll, choice: 1) + expect(first_vote).to be_valid + + second_vote = Fabricate.build(:poll_vote, account: first_vote.account, poll: poll, choice: 1) + expect(second_vote).to_not be_valid + end + end + + context 'with a poll where multiple is false' do + it 'does not allow a second vote from same account' do + poll = Fabricate(:poll, multiple: false, options: %w(a b c)) + first_vote = Fabricate(:poll_vote, poll: poll) + expect(first_vote).to be_valid + + second_vote = Fabricate.build(:poll_vote, account: first_vote.account, poll: poll) + expect(second_vote).to_not be_valid + end + end + end end diff --git a/spec/models/preview_card_provider_spec.rb b/spec/models/preview_card_provider_spec.rb new file mode 100644 index 000000000..7425b9394 --- /dev/null +++ b/spec/models/preview_card_provider_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe PreviewCardProvider do + describe 'scopes' do + let(:trendable_and_reviewed) { Fabricate(:preview_card_provider, trendable: true, reviewed_at: 5.days.ago) } + let(:not_trendable_and_not_reviewed) { Fabricate(:preview_card_provider, trendable: false, reviewed_at: nil) } + + describe 'trendable' do + it 'returns the relevant records' do + results = described_class.trendable + + expect(results).to eq([trendable_and_reviewed]) + end + end + + describe 'not_trendable' do + it 'returns the relevant records' do + results = described_class.not_trendable + + expect(results).to eq([not_trendable_and_not_reviewed]) + end + end + + describe 'reviewed' do + it 'returns the relevant records' do + results = described_class.reviewed + + expect(results).to eq([trendable_and_reviewed]) + end + end + + describe 'pending_review' do + it 'returns the relevant records' do + results = described_class.pending_review + + expect(results).to eq([not_trendable_and_not_reviewed]) + end + end + end +end diff --git a/spec/models/preview_card_spec.rb b/spec/models/preview_card_spec.rb deleted file mode 100644 index 45233d1d4..000000000 --- a/spec/models/preview_card_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe PreviewCard, type: :model do -end diff --git a/spec/models/preview_card_trend_spec.rb b/spec/models/preview_card_trend_spec.rb deleted file mode 100644 index c7ab6ed14..000000000 --- a/spec/models/preview_card_trend_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe PreviewCardTrend, type: :model do -end diff --git a/spec/models/privacy_policy_spec.rb b/spec/models/privacy_policy_spec.rb new file mode 100644 index 000000000..0d7471375 --- /dev/null +++ b/spec/models/privacy_policy_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe PrivacyPolicy do + describe '.current' do + context 'with the default values' do + it 'has the privacy text' do + policy = described_class.current + + expect(policy.text).to eq(PrivacyPolicy::DEFAULT_PRIVACY_POLICY) + end + end + + context 'with a custom setting value' do + before do + terms_setting = instance_double(Setting, value: 'Terms text', updated_at: 10.days.ago) + allow(Setting).to receive(:find_by).with(var: 'site_terms').and_return(terms_setting) + end + + it 'has the privacy text' do + policy = described_class.current + + expect(policy.text).to eq('Terms text') + end + end + end +end diff --git a/spec/models/public_feed_spec.rb b/spec/models/public_feed_spec.rb index 0ffc343f1..53e01cafd 100644 --- a/spec/models/public_feed_spec.rb +++ b/spec/models/public_feed_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe PublicFeed, type: :model do +RSpec.describe PublicFeed do let(:account) { Fabricate(:account) } describe '#get' do @@ -11,7 +13,7 @@ RSpec.describe PublicFeed, type: :model do private_status = Fabricate(:status, visibility: :private) expect(subject).to include(public_status.id) - expect(subject).not_to include(private_status.id) + expect(subject).to_not include(private_status.id) end it 'does not include replies' do @@ -19,7 +21,7 @@ RSpec.describe PublicFeed, type: :model do reply = Fabricate(:status, in_reply_to_id: status.id) expect(subject).to include(status.id) - expect(subject).not_to include(reply.id) + expect(subject).to_not include(reply.id) end it 'does not include boosts' do @@ -27,7 +29,7 @@ RSpec.describe PublicFeed, type: :model do boost = Fabricate(:status, reblog_of_id: status.id) expect(subject).to include(status.id) - expect(subject).not_to include(boost.id) + expect(subject).to_not include(boost.id) end it 'filters out silenced accounts' do @@ -36,10 +38,12 @@ RSpec.describe PublicFeed, type: :model do silenced_status = Fabricate(:status, account: silenced_account) expect(subject).to include(status.id) - expect(subject).not_to include(silenced_status.id) + expect(subject).to_not include(silenced_status.id) end context 'without local_only option' do + subject { described_class.new(viewer).get(20).map(&:id) } + let(:viewer) { nil } let!(:local_account) { Fabricate(:account, domain: nil) } @@ -47,8 +51,6 @@ RSpec.describe PublicFeed, type: :model do let!(:local_status) { Fabricate(:status, account: local_account) } let!(:remote_status) { Fabricate(:status, account: remote_account) } - subject { described_class.new(viewer).get(20).map(&:id) } - context 'without a viewer' do let(:viewer) { nil } @@ -75,19 +77,19 @@ RSpec.describe PublicFeed, type: :model do end context 'with a local_only option set' do + subject { described_class.new(viewer, local: true).get(20).map(&:id) } + let!(:local_account) { Fabricate(:account, domain: nil) } let!(:remote_account) { Fabricate(:account, domain: 'test.com') } let!(:local_status) { Fabricate(:status, account: local_account) } let!(:remote_status) { Fabricate(:status, account: remote_account) } - subject { described_class.new(viewer, local: true).get(20).map(&:id) } - context 'without a viewer' do let(:viewer) { nil } it 'does not include remote instances statuses' do expect(subject).to include(local_status.id) - expect(subject).not_to include(remote_status.id) + expect(subject).to_not include(remote_status.id) end end @@ -96,30 +98,30 @@ RSpec.describe PublicFeed, type: :model do it 'does not include remote instances statuses' do expect(subject).to include(local_status.id) - expect(subject).not_to include(remote_status.id) + expect(subject).to_not include(remote_status.id) end it 'is not affected by personal domain blocks' do viewer.block_domain!('test.com') expect(subject).to include(local_status.id) - expect(subject).not_to include(remote_status.id) + expect(subject).to_not include(remote_status.id) end end end context 'with a remote_only option set' do + subject { described_class.new(viewer, remote: true).get(20).map(&:id) } + let!(:local_account) { Fabricate(:account, domain: nil) } let!(:remote_account) { Fabricate(:account, domain: 'test.com') } let!(:local_status) { Fabricate(:status, account: local_account) } let!(:remote_status) { Fabricate(:status, account: remote_account) } - subject { described_class.new(viewer, remote: true).get(20).map(&:id) } - context 'without a viewer' do let(:viewer) { nil } it 'does not include local instances statuses' do - expect(subject).not_to include(local_status.id) + expect(subject).to_not include(local_status.id) expect(subject).to include(remote_status.id) end end @@ -128,25 +130,25 @@ RSpec.describe PublicFeed, type: :model do let(:viewer) { Fabricate(:account, username: 'viewer') } it 'does not include local instances statuses' do - expect(subject).not_to include(local_status.id) + expect(subject).to_not include(local_status.id) expect(subject).to include(remote_status.id) end end end describe 'with an account passed in' do + subject { described_class.new(@account).get(20).map(&:id) } + before do @account = Fabricate(:account) end - subject { described_class.new(@account).get(20).map(&:id) } - it 'excludes statuses from accounts blocked by the account' do blocked = Fabricate(:account) @account.block!(blocked) blocked_status = Fabricate(:status, account: blocked) - expect(subject).not_to include(blocked_status.id) + expect(subject).to_not include(blocked_status.id) end it 'excludes statuses from accounts who have blocked the account' do @@ -154,7 +156,7 @@ RSpec.describe PublicFeed, type: :model do blocker.block!(@account) blocked_status = Fabricate(:status, account: blocker) - expect(subject).not_to include(blocked_status.id) + expect(subject).to_not include(blocked_status.id) end it 'excludes statuses from accounts muted by the account' do @@ -162,7 +164,7 @@ RSpec.describe PublicFeed, type: :model do @account.mute!(muted) muted_status = Fabricate(:status, account: muted) - expect(subject).not_to include(muted_status.id) + expect(subject).to_not include(muted_status.id) end it 'excludes statuses from accounts from personally blocked domains' do @@ -170,7 +172,7 @@ RSpec.describe PublicFeed, type: :model do @account.block_domain!(blocked.domain) blocked_status = Fabricate(:status, account: blocked) - expect(subject).not_to include(blocked_status.id) + expect(subject).to_not include(blocked_status.id) end context 'with language preferences' do @@ -182,7 +184,7 @@ RSpec.describe PublicFeed, type: :model do expect(subject).to include(en_status.id) expect(subject).to include(es_status.id) - expect(subject).not_to include(fr_status.id) + expect(subject).to_not include(fr_status.id) end it 'includes all languages when user does not have a setting' do diff --git a/spec/models/relay_spec.rb b/spec/models/relay_spec.rb deleted file mode 100644 index 12dc0f20f..000000000 --- a/spec/models/relay_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe Relay, type: :model do -end diff --git a/spec/models/remote_follow_spec.rb b/spec/models/remote_follow_spec.rb index 5b4c19b5b..81c726a40 100644 --- a/spec/models/remote_follow_spec.rb +++ b/spec/models/remote_follow_spec.rb @@ -13,19 +13,19 @@ RSpec.describe RemoteFollow do describe '.initialize' do subject { remote_follow.acct } - context 'attrs with acct' do + context 'when attrs with acct' do let(:attrs) { { acct: 'gargron@quitter.no' } } it 'returns acct' do - is_expected.to eq 'gargron@quitter.no' + expect(subject).to eq 'gargron@quitter.no' end end - context 'attrs without acct' do + context 'when attrs without acct' do let(:attrs) { {} } it do - is_expected.to be_nil + expect(subject).to be_nil end end end @@ -33,24 +33,26 @@ RSpec.describe RemoteFollow do describe '#valid?' do subject { remote_follow.valid? } - context 'attrs with acct' do + context 'when attrs with acct' do let(:attrs) { { acct: 'gargron@quitter.no' } } it do - is_expected.to be true + expect(subject).to be true end end - context 'attrs without acct' do + context 'when attrs without acct' do let(:attrs) { {} } it do - is_expected.to be false + expect(subject).to be false end end end describe '#subscribe_address_for' do + subject { remote_follow.subscribe_address_for(account) } + before do remote_follow.valid? end @@ -58,10 +60,8 @@ RSpec.describe RemoteFollow do let(:attrs) { { acct: 'gargron@quitter.no' } } let(:account) { Fabricate(:account, username: 'alice') } - subject { remote_follow.subscribe_address_for(account) } - it 'returns subscribe address' do - is_expected.to eq 'https://quitter.no/main/ostatussub?profile=https%3A%2F%2Fcb6e6126.ngrok.io%2Fusers%2Falice' + expect(subject).to eq 'https://quitter.no/main/ostatussub?profile=https%3A%2F%2Fcb6e6126.ngrok.io%2Fusers%2Falice' end end end diff --git a/spec/models/report_filter_spec.rb b/spec/models/report_filter_spec.rb index 099c0731d..6baf0ea42 100644 --- a/spec/models/report_filter_spec.rb +++ b/spec/models/report_filter_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'rails_helper' describe ReportFilter do describe 'with empty params' do it 'defaults to unresolved reports list' do - filter = ReportFilter.new({}) + filter = described_class.new({}) expect(filter.results).to eq Report.unresolved end @@ -11,7 +13,7 @@ describe ReportFilter do describe 'with invalid params' do it 'raises with key error' do - filter = ReportFilter.new(wrong: true) + filter = described_class.new(wrong: true) expect { filter.results }.to raise_error(/wrong/) end @@ -19,10 +21,9 @@ describe ReportFilter do describe 'with valid params' do it 'combines filters on Report' do - filter = ReportFilter.new(account_id: '123', resolved: true, target_account_id: '456') + filter = described_class.new(account_id: '123', resolved: true, target_account_id: '456') - allow(Report).to receive(:where).and_return(Report.none) - allow(Report).to receive(:resolved).and_return(Report.none) + allow(Report).to receive_messages(where: Report.none, resolved: Report.none) filter.results expect(Report).to have_received(:where).with(account_id: '123') expect(Report).to have_received(:where).with(target_account_id: '456') diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb index c485a4a3c..0093dcd8d 100644 --- a/spec/models/report_spec.rb +++ b/spec/models/report_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe Report do @@ -33,7 +35,7 @@ describe Report do end it 'assigns to a given account' do - is_expected.to eq current_account.id + expect(subject).to eq current_account.id end end @@ -48,7 +50,7 @@ describe Report do end it 'unassigns' do - is_expected.to be_nil + expect(subject).to be_nil end end @@ -87,13 +89,13 @@ describe Report do let(:report) { Fabricate(:report, action_taken_at: action_taken) } - context 'if action is taken' do + context 'when action is taken' do let(:action_taken) { Time.now.utc } it { is_expected.to be false } end - context 'if action not is taken' do + context 'when action not is taken' do let(:action_taken) { nil } it { is_expected.to be true } @@ -119,12 +121,6 @@ describe Report do end describe 'validations' do - it 'has a valid fabricator' do - report = Fabricate(:report) - report.valid? - expect(report).to be_valid - end - let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') } it 'is invalid if comment is longer than 1000 characters only if reporter is local' do diff --git a/spec/models/rule_spec.rb b/spec/models/rule_spec.rb index 8666bda71..c9b9c5502 100644 --- a/spec/models/rule_spec.rb +++ b/spec/models/rule_spec.rb @@ -1,5 +1,19 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Rule, type: :model do - pending "add some examples to (or delete) #{__FILE__}" +describe Rule do + describe 'scopes' do + describe 'ordered' do + let(:deleted_rule) { Fabricate(:rule, deleted_at: 10.days.ago) } + let(:first_rule) { Fabricate(:rule, deleted_at: nil, priority: 1) } + let(:last_rule) { Fabricate(:rule, deleted_at: nil, priority: 10) } + + it 'finds the correct records' do + results = described_class.ordered + + expect(results).to eq([first_rule, last_rule]) + end + end + end end diff --git a/spec/models/scheduled_status_spec.rb b/spec/models/scheduled_status_spec.rb deleted file mode 100644 index f8c9d8b81..000000000 --- a/spec/models/scheduled_status_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe ScheduledStatus, type: :model do -end diff --git a/spec/models/session_activation_spec.rb b/spec/models/session_activation_spec.rb index 450dc1399..75842e25b 100644 --- a/spec/models/session_activation_spec.rb +++ b/spec/models/session_activation_spec.rb @@ -2,12 +2,12 @@ require 'rails_helper' -RSpec.describe SessionActivation, type: :model do +RSpec.describe SessionActivation do describe '#detection' do let(:session_activation) { Fabricate(:session_activation, user_agent: 'Chrome/62.0.3202.89') } it 'sets a Browser instance as detection' do - expect(session_activation.detection).to be_kind_of Browser::Chrome + expect(session_activation.detection).to be_a Browser::Chrome end end @@ -16,7 +16,7 @@ RSpec.describe SessionActivation, type: :model do allow(session_activation).to receive(:detection).and_return(detection) end - let(:detection) { double(id: 1) } + let(:detection) { instance_double(Browser::Chrome, id: 1) } let(:session_activation) { Fabricate(:session_activation) } it 'returns detection.id' do @@ -30,7 +30,7 @@ RSpec.describe SessionActivation, type: :model do end let(:session_activation) { Fabricate(:session_activation) } - let(:detection) { double(platform: double(id: 1)) } + let(:detection) { instance_double(Browser::Chrome, platform: instance_double(Browser::Platform, id: 1)) } it 'returns detection.platform.id' do expect(session_activation.platform).to be 1 @@ -40,31 +40,31 @@ RSpec.describe SessionActivation, type: :model do describe '.active?' do subject { described_class.active?(id) } - context 'id is absent' do + context 'when id is absent' do let(:id) { nil } it 'returns nil' do - is_expected.to be nil + expect(subject).to be_nil end end - context 'id is present' do + context 'when id is present' do let(:id) { '1' } let!(:session_activation) { Fabricate(:session_activation, session_id: id) } - context 'id exists as session_id' do + context 'when id exists as session_id' do it 'returns true' do - is_expected.to be true + expect(subject).to be true end end - context 'id does not exist as session_id' do + context 'when id does not exist as session_id' do before do session_activation.update!(session_id: '2') end it 'returns false' do - is_expected.to be false + expect(subject).to be false end end end @@ -80,20 +80,20 @@ RSpec.describe SessionActivation, type: :model do end it 'returns an instance of SessionActivation' do - expect(described_class.activate(**options)).to be_kind_of SessionActivation + expect(described_class.activate(**options)).to be_a described_class end end describe '.deactivate' do - context 'id is absent' do + context 'when id is absent' do let(:id) { nil } it 'returns nil' do - expect(described_class.deactivate(id)).to be nil + expect(described_class.deactivate(id)).to be_nil end end - context 'id exists' do + context 'when id exists' do let(:id) { '1' } it 'calls where.destroy_all' do @@ -118,8 +118,8 @@ RSpec.describe SessionActivation, type: :model do let(:id) { '1' } it 'calls where.destroy_all' do - expect(described_class).to receive_message_chain(:where, :destroy_all) - .with('session_id != ?', id).with(no_args) + expect(described_class).to receive_message_chain(:where, :not, :destroy_all) + .with(session_id: id).with(no_args) described_class.exclusive(id) end diff --git a/spec/models/setting_spec.rb b/spec/models/setting_spec.rb index 3ccc21d6c..97e548e09 100644 --- a/spec/models/setting_spec.rb +++ b/spec/models/setting_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe Setting, type: :model do +RSpec.describe Setting do describe '#to_param' do let(:setting) { Fabricate(:setting, var: var) } let(:var) { 'var' } @@ -19,7 +19,7 @@ RSpec.describe Setting, type: :model do let(:key) { 'key' } - context 'rails_initialized? is falsey' do + context 'when rails_initialized? is falsey' do let(:rails_initialized) { false } it 'calls RailsSettings::Base#[]' do @@ -28,7 +28,7 @@ RSpec.describe Setting, type: :model do end end - context 'rails_initialized? is truthy' do + context 'when rails_initialized? is truthy' do before do allow(RailsSettings::Base).to receive(:cache_key).with(key, nil).and_return(cache_key) end @@ -38,11 +38,11 @@ RSpec.describe Setting, type: :model do let(:cache_value) { 'cache-value' } it 'calls not RailsSettings::Base#[]' do - expect(RailsSettings::Base).not_to receive(:[]).with(key) + expect(RailsSettings::Base).to_not receive(:[]).with(key) described_class[key] end - context 'Rails.cache does not exists' do + context 'when Rails.cache does not exists' do before do allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object) allow(described_class).to receive(:default_settings).and_return(default_settings) @@ -60,11 +60,11 @@ RSpec.describe Setting, type: :model do described_class[key] end - context 'RailsSettings::Settings.object returns truthy' do + context 'when RailsSettings::Settings.object returns truthy' do let(:object) { db_val } - let(:db_val) { double(value: 'db_val') } + let(:db_val) { instance_double(described_class, value: 'db_val') } - context 'default_value is a Hash' do + context 'when default_value is a Hash' do let(:default_value) { { default_value: 'default_value' } } it 'calls default_value.with_indifferent_access.merge!' do @@ -75,7 +75,7 @@ RSpec.describe Setting, type: :model do end end - context 'default_value is not a Hash' do + context 'when default_value is not a Hash' do let(:default_value) { 'default_value' } it 'returns db_val.value' do @@ -84,7 +84,7 @@ RSpec.describe Setting, type: :model do end end - context 'RailsSettings::Settings.object returns falsey' do + context 'when RailsSettings::Settings.object returns falsey' do let(:object) { nil } it 'returns default_settings[key]' do @@ -93,7 +93,7 @@ RSpec.describe Setting, type: :model do end end - context 'Rails.cache exists' do + context 'when Rails.cache exists' do before do Rails.cache.write(cache_key, cache_value) end @@ -104,7 +104,7 @@ RSpec.describe Setting, type: :model do ActiveSupport::Notifications.subscribed callback, 'sql.active_record' do described_class[key] end - expect(callback).not_to have_received(:call) + expect(callback).to_not have_received(:call) end it 'returns the cached value' do @@ -127,10 +127,10 @@ RSpec.describe Setting, type: :model do let(:records) { [original_setting] } it 'returns a Hash' do - expect(described_class.all_as_records).to be_kind_of Hash + expect(described_class.all_as_records).to be_a Hash end - context 'records includes Setting with var as the key' do + context 'when records includes Setting with var as the key' do let(:records) { [original_setting] } it 'includes the original Setting' do @@ -139,49 +139,39 @@ RSpec.describe Setting, type: :model do end end - context 'records includes nothing' do + context 'when records includes nothing' do let(:records) { [] } - context 'default_value is not a Hash' do - it 'includes Setting with value of default_value' do - setting = described_class.all_as_records[key] + it 'includes Setting with value of default_value' do + setting = described_class.all_as_records[key] - expect(setting).to be_kind_of Setting - expect(setting).to have_attributes(var: key) - expect(setting).to have_attributes(value: 'default_value') - end - end - - context 'default_value is a Hash' do - let(:default_value) { { 'foo' => 'fuga' } } - - it 'returns {}' do - expect(described_class.all_as_records).to eq({}) - end + expect(setting).to be_a described_class + expect(setting).to have_attributes(var: key) + expect(setting).to have_attributes(value: default_value) end end end describe '.default_settings' do + subject { described_class.default_settings } + before do allow(RailsSettings::Default).to receive(:enabled?).and_return(enabled) end - subject { described_class.default_settings } - - context 'RailsSettings::Default.enabled? is false' do + context 'when RailsSettings::Default.enabled? is false' do let(:enabled) { false } it 'returns {}' do - is_expected.to eq({}) + expect(subject).to eq({}) end end - context 'RailsSettings::Settings.enabled? is true' do + context 'when RailsSettings::Settings.enabled? is true' do let(:enabled) { true } it 'returns instance of RailsSettings::Default' do - is_expected.to be_kind_of RailsSettings::Default + expect(subject).to be_a RailsSettings::Default end end end diff --git a/spec/models/site_upload_spec.rb b/spec/models/site_upload_spec.rb index f7ea06921..9689bce9e 100644 --- a/spec/models/site_upload_spec.rb +++ b/spec/models/site_upload_spec.rb @@ -2,9 +2,9 @@ require 'rails_helper' -RSpec.describe SiteUpload, type: :model do +RSpec.describe SiteUpload do describe '#cache_key' do - let(:site_upload) { SiteUpload.new(var: 'var') } + let(:site_upload) { described_class.new(var: 'var') } it 'returns cache_key' do expect(site_upload.cache_key).to eq 'site_uploads/var' diff --git a/spec/models/software_update_spec.rb b/spec/models/software_update_spec.rb new file mode 100644 index 000000000..0a494b0c4 --- /dev/null +++ b/spec/models/software_update_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe SoftwareUpdate do + describe '.pending_to_a' do + before do + allow(Mastodon::Version).to receive(:gem_version).and_return(Gem::Version.new(mastodon_version)) + + Fabricate(:software_update, version: '3.4.42', type: 'patch', urgent: true) + Fabricate(:software_update, version: '3.5.0', type: 'minor', urgent: false) + Fabricate(:software_update, version: '4.2.0', type: 'major', urgent: false) + end + + context 'when the Mastodon version is an outdated release' do + let(:mastodon_version) { '3.4.0' } + + it 'returns the expected versions' do + expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('3.4.42', '3.5.0', '4.2.0') + end + end + + context 'when the Mastodon version is more recent than anything last returned by the server' do + let(:mastodon_version) { '5.0.0' } + + it 'returns the expected versions' do + expect(described_class.pending_to_a.pluck(:version)).to eq [] + end + end + + context 'when the Mastodon version is an outdated nightly' do + let(:mastodon_version) { '4.3.0-nightly.2023-09-10' } + + before do + Fabricate(:software_update, version: '4.3.0-nightly.2023-09-12', type: 'major', urgent: true) + end + + it 'returns the expected versions' do + expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.3.0-nightly.2023-09-12') + end + end + + context 'when the Mastodon version is a very outdated nightly' do + let(:mastodon_version) { '4.2.0-nightly.2023-07-10' } + + it 'returns the expected versions' do + expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.2.0') + end + end + + context 'when the Mastodon version is an outdated dev version' do + let(:mastodon_version) { '4.3.0-0.dev.0' } + + before do + Fabricate(:software_update, version: '4.3.0-0.dev.2', type: 'major', urgent: true) + end + + it 'returns the expected versions' do + expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.3.0-0.dev.2') + end + end + + context 'when the Mastodon version is an outdated beta version' do + let(:mastodon_version) { '4.3.0-beta1' } + + before do + Fabricate(:software_update, version: '4.3.0-beta2', type: 'major', urgent: true) + end + + it 'returns the expected versions' do + expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.3.0-beta2') + end + end + + context 'when the Mastodon version is an outdated beta version and there is a rc' do + let(:mastodon_version) { '4.3.0-beta1' } + + before do + Fabricate(:software_update, version: '4.3.0-rc1', type: 'major', urgent: true) + end + + it 'returns the expected versions' do + expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.3.0-rc1') + end + end + end +end diff --git a/spec/models/status_edit_spec.rb b/spec/models/status_edit_spec.rb index 2ecafef73..2d3351452 100644 --- a/spec/models/status_edit_spec.rb +++ b/spec/models/status_edit_spec.rb @@ -1,5 +1,13 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe StatusEdit, type: :model do - pending "add some examples to (or delete) #{__FILE__}" +describe StatusEdit do + describe '#reblog?' do + it 'returns false' do + record = described_class.new + + expect(record).to_not be_a_reblog + end + end end diff --git a/spec/models/status_pin_spec.rb b/spec/models/status_pin_spec.rb index c18faca78..660b2e92a 100644 --- a/spec/models/status_pin_spec.rb +++ b/spec/models/status_pin_spec.rb @@ -1,19 +1,21 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe StatusPin, type: :model do +RSpec.describe StatusPin do describe 'validations' do it 'allows pins of own statuses' do account = Fabricate(:account) status = Fabricate(:status, account: account) - expect(StatusPin.new(account: account, status: status).save).to be true + expect(described_class.new(account: account, status: status).save).to be true end it 'does not allow pins of statuses by someone else' do account = Fabricate(:account) status = Fabricate(:status) - expect(StatusPin.new(account: account, status: status).save).to be false + expect(described_class.new(account: account, status: status).save).to be false end it 'does not allow pins of reblogs' do @@ -21,21 +23,21 @@ RSpec.describe StatusPin, type: :model do status = Fabricate(:status, account: account) reblog = Fabricate(:status, reblog: status) - expect(StatusPin.new(account: account, status: reblog).save).to be false + expect(described_class.new(account: account, status: reblog).save).to be false end it 'does allow pins of direct statuses' do account = Fabricate(:account) status = Fabricate(:status, account: account, visibility: :private) - expect(StatusPin.new(account: account, status: status).save).to be true + expect(described_class.new(account: account, status: status).save).to be true end it 'does not allow pins of direct statuses' do account = Fabricate(:account) status = Fabricate(:status, account: account, visibility: :direct) - expect(StatusPin.new(account: account, status: status).save).to be false + expect(described_class.new(account: account, status: status).save).to be false end max_pins = 5 @@ -48,10 +50,10 @@ RSpec.describe StatusPin, type: :model do end max_pins.times do |i| - expect(StatusPin.new(account: account, status: status[i]).save).to be true + expect(described_class.new(account: account, status: status[i]).save).to be true end - expect(StatusPin.new(account: account, status: status[max_pins]).save).to be false + expect(described_class.new(account: account, status: status[max_pins]).save).to be false end it 'allows pins above the max for remote accounts' do @@ -63,10 +65,10 @@ RSpec.describe StatusPin, type: :model do end max_pins.times do |i| - expect(StatusPin.new(account: account, status: status[i]).save).to be true + expect(described_class.new(account: account, status: status[i]).save).to be true end - expect(StatusPin.new(account: account, status: status[max_pins]).save).to be true + expect(described_class.new(account: account, status: status[max_pins]).save).to be true end end end diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index 78cc05959..8c37b1acc 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Status, type: :model do +RSpec.describe Status do + subject { Fabricate(:status, account: alice) } + let(:alice) { Fabricate(:account, username: 'alice') } let(:bob) { Fabricate(:account, username: 'bob') } let(:other) { Fabricate(:status, account: bob, text: 'Skulls for the skull god! The enemy\'s gates are sideways!') } - subject { Fabricate(:status, account: alice) } - describe '#local?' do it 'returns true when no remote URI is set' do expect(subject.local?).to be true @@ -47,22 +49,22 @@ RSpec.describe Status, type: :model do end describe '#verb' do - context 'if destroyed?' do + context 'when destroyed?' do it 'returns :delete' do subject.destroy! expect(subject.verb).to be :delete end end - context 'unless destroyed?' do - context 'if reblog?' do + context 'when not destroyed?' do + context 'when reblog?' do it 'returns :share' do subject.reblog = other expect(subject.verb).to be :share end end - context 'unless reblog?' do + context 'when not reblog?' do it 'returns :post' do subject.reblog = nil expect(subject.verb).to be :post @@ -83,28 +85,28 @@ RSpec.describe Status, type: :model do end describe '#hidden?' do - context 'if private_visibility?' do + context 'when private_visibility?' do it 'returns true' do subject.visibility = :private expect(subject.hidden?).to be true end end - context 'if direct_visibility?' do + context 'when direct_visibility?' do it 'returns true' do subject.visibility = :direct expect(subject.hidden?).to be true end end - context 'if public_visibility?' do + context 'when public_visibility?' do it 'returns false' do subject.visibility = :public expect(subject.hidden?).to be false end end - context 'if unlisted_visibility?' do + context 'when unlisted_visibility?' do it 'returns false' do subject.visibility = :unlisted expect(subject.hidden?).to be false @@ -158,7 +160,7 @@ RSpec.describe Status, type: :model do reblog = Fabricate(:status, account: bob, reblog: subject) expect(subject.reblogs_count).to eq 1 expect { subject.destroy }.to_not raise_error - expect(Status.find_by(id: reblog.id)).to be_nil + expect(described_class.find_by(id: reblog.id)).to be_nil end end @@ -204,11 +206,11 @@ RSpec.describe Status, type: :model do end describe '.mutes_map' do + subject { described_class.mutes_map([status.conversation.id], account) } + let(:status) { Fabricate(:status) } let(:account) { Fabricate(:account) } - subject { Status.mutes_map([status.conversation.id], account) } - it 'returns a hash' do expect(subject).to be_a Hash end @@ -220,11 +222,11 @@ RSpec.describe Status, type: :model do end describe '.favourites_map' do + subject { described_class.favourites_map([status], account) } + let(:status) { Fabricate(:status) } let(:account) { Fabricate(:account) } - subject { Status.favourites_map([status], account) } - it 'returns a hash' do expect(subject).to be_a Hash end @@ -236,11 +238,11 @@ RSpec.describe Status, type: :model do end describe '.reblogs_map' do + subject { described_class.reblogs_map([status], account) } + let(:status) { Fabricate(:status) } let(:account) { Fabricate(:account) } - subject { Status.reblogs_map([status], account) } - it 'returns a hash' do expect(subject).to be_a Hash end @@ -252,82 +254,82 @@ RSpec.describe Status, type: :model do end describe '.tagged_with' do - let(:tag1) { Fabricate(:tag) } - let(:tag2) { Fabricate(:tag) } - let(:tag3) { Fabricate(:tag) } - let!(:status1) { Fabricate(:status, tags: [tag1]) } - let!(:status2) { Fabricate(:status, tags: [tag2]) } - let!(:status3) { Fabricate(:status, tags: [tag3]) } - let!(:status4) { Fabricate(:status, tags: []) } - let!(:status5) { Fabricate(:status, tags: [tag1, tag2, tag3]) } + let(:tag_cats) { Fabricate(:tag, name: 'cats') } + let(:tag_dogs) { Fabricate(:tag, name: 'dogs') } + let(:tag_zebras) { Fabricate(:tag, name: 'zebras') } + let!(:status_with_tag_cats) { Fabricate(:status, tags: [tag_cats]) } + let!(:status_with_tag_dogs) { Fabricate(:status, tags: [tag_dogs]) } + let!(:status_tagged_with_zebras) { Fabricate(:status, tags: [tag_zebras]) } + let!(:status_without_tags) { Fabricate(:status, tags: []) } + let!(:status_with_all_tags) { Fabricate(:status, tags: [tag_cats, tag_dogs, tag_zebras]) } context 'when given one tag' do it 'returns the expected statuses' do - expect(Status.tagged_with([tag1.id]).reorder(:id).pluck(:id).uniq).to match_array([status1.id, status5.id]) - expect(Status.tagged_with([tag2.id]).reorder(:id).pluck(:id).uniq).to match_array([status2.id, status5.id]) - expect(Status.tagged_with([tag3.id]).reorder(:id).pluck(:id).uniq).to match_array([status3.id, status5.id]) + expect(described_class.tagged_with([tag_cats.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_with_all_tags.id) + expect(described_class.tagged_with([tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_with_all_tags.id) + expect(described_class.tagged_with([tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_tagged_with_zebras.id, status_with_all_tags.id) end end context 'when given multiple tags' do it 'returns the expected statuses' do - expect(Status.tagged_with([tag1.id, tag2.id]).reorder(:id).pluck(:id).uniq).to match_array([status1.id, status2.id, status5.id]) - expect(Status.tagged_with([tag1.id, tag3.id]).reorder(:id).pluck(:id).uniq).to match_array([status1.id, status3.id, status5.id]) - expect(Status.tagged_with([tag2.id, tag3.id]).reorder(:id).pluck(:id).uniq).to match_array([status2.id, status3.id, status5.id]) + expect(described_class.tagged_with([tag_cats.id, tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_with_tag_dogs.id, status_with_all_tags.id) + expect(described_class.tagged_with([tag_cats.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_tagged_with_zebras.id, status_with_all_tags.id) + expect(described_class.tagged_with([tag_dogs.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_tagged_with_zebras.id, status_with_all_tags.id) end end end describe '.tagged_with_all' do - let(:tag1) { Fabricate(:tag) } - let(:tag2) { Fabricate(:tag) } - let(:tag3) { Fabricate(:tag) } - let!(:status1) { Fabricate(:status, tags: [tag1]) } - let!(:status2) { Fabricate(:status, tags: [tag2]) } - let!(:status3) { Fabricate(:status, tags: [tag3]) } - let!(:status4) { Fabricate(:status, tags: []) } - let!(:status5) { Fabricate(:status, tags: [tag1, tag2]) } + let(:tag_cats) { Fabricate(:tag, name: 'cats') } + let(:tag_dogs) { Fabricate(:tag, name: 'dogs') } + let(:tag_zebras) { Fabricate(:tag, name: 'zebras') } + let!(:status_with_tag_cats) { Fabricate(:status, tags: [tag_cats]) } + let!(:status_with_tag_dogs) { Fabricate(:status, tags: [tag_dogs]) } + let!(:status_tagged_with_zebras) { Fabricate(:status, tags: [tag_zebras]) } + let!(:status_without_tags) { Fabricate(:status, tags: []) } + let!(:status_with_all_tags) { Fabricate(:status, tags: [tag_cats, tag_dogs]) } context 'when given one tag' do it 'returns the expected statuses' do - expect(Status.tagged_with_all([tag1.id]).reorder(:id).pluck(:id).uniq).to match_array([status1.id, status5.id]) - expect(Status.tagged_with_all([tag2.id]).reorder(:id).pluck(:id).uniq).to match_array([status2.id, status5.id]) - expect(Status.tagged_with_all([tag3.id]).reorder(:id).pluck(:id).uniq).to match_array([status3.id]) + expect(described_class.tagged_with_all([tag_cats.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_with_all_tags.id) + expect(described_class.tagged_with_all([tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_with_all_tags.id) + expect(described_class.tagged_with_all([tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_tagged_with_zebras.id) end end context 'when given multiple tags' do it 'returns the expected statuses' do - expect(Status.tagged_with_all([tag1.id, tag2.id]).reorder(:id).pluck(:id).uniq).to match_array([status5.id]) - expect(Status.tagged_with_all([tag1.id, tag3.id]).reorder(:id).pluck(:id).uniq).to eq [] - expect(Status.tagged_with_all([tag2.id, tag3.id]).reorder(:id).pluck(:id).uniq).to eq [] + expect(described_class.tagged_with_all([tag_cats.id, tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_all_tags.id) + expect(described_class.tagged_with_all([tag_cats.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to eq [] + expect(described_class.tagged_with_all([tag_dogs.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to eq [] end end end describe '.tagged_with_none' do - let(:tag1) { Fabricate(:tag) } - let(:tag2) { Fabricate(:tag) } - let(:tag3) { Fabricate(:tag) } - let!(:status1) { Fabricate(:status, tags: [tag1]) } - let!(:status2) { Fabricate(:status, tags: [tag2]) } - let!(:status3) { Fabricate(:status, tags: [tag3]) } - let!(:status4) { Fabricate(:status, tags: []) } - let!(:status5) { Fabricate(:status, tags: [tag1, tag2, tag3]) } + let(:tag_cats) { Fabricate(:tag, name: 'cats') } + let(:tag_dogs) { Fabricate(:tag, name: 'dogs') } + let(:tag_zebras) { Fabricate(:tag, name: 'zebras') } + let!(:status_with_tag_cats) { Fabricate(:status, tags: [tag_cats]) } + let!(:status_with_tag_dogs) { Fabricate(:status, tags: [tag_dogs]) } + let!(:status_tagged_with_zebras) { Fabricate(:status, tags: [tag_zebras]) } + let!(:status_without_tags) { Fabricate(:status, tags: []) } + let!(:status_with_all_tags) { Fabricate(:status, tags: [tag_cats, tag_dogs, tag_zebras]) } context 'when given one tag' do it 'returns the expected statuses' do - expect(Status.tagged_with_none([tag1.id]).reorder(:id).pluck(:id).uniq).to match_array([status2.id, status3.id, status4.id]) - expect(Status.tagged_with_none([tag2.id]).reorder(:id).pluck(:id).uniq).to match_array([status1.id, status3.id, status4.id]) - expect(Status.tagged_with_none([tag3.id]).reorder(:id).pluck(:id).uniq).to match_array([status1.id, status2.id, status4.id]) + expect(described_class.tagged_with_none([tag_cats.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_tagged_with_zebras.id, status_without_tags.id) + expect(described_class.tagged_with_none([tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_tagged_with_zebras.id, status_without_tags.id) + expect(described_class.tagged_with_none([tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_with_tag_dogs.id, status_without_tags.id) end end context 'when given multiple tags' do it 'returns the expected statuses' do - expect(Status.tagged_with_none([tag1.id, tag2.id]).reorder(:id).pluck(:id).uniq).to match_array([status3.id, status4.id]) - expect(Status.tagged_with_none([tag1.id, tag3.id]).reorder(:id).pluck(:id).uniq).to match_array([status2.id, status4.id]) - expect(Status.tagged_with_none([tag2.id, tag3.id]).reorder(:id).pluck(:id).uniq).to match_array([status1.id, status4.id]) + expect(described_class.tagged_with_none([tag_cats.id, tag_dogs.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_tagged_with_zebras.id, status_without_tags.id) + expect(described_class.tagged_with_none([tag_cats.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_dogs.id, status_without_tags.id) + expect(described_class.tagged_with_none([tag_dogs.id, tag_zebras.id]).reorder(:id).pluck(:id).uniq).to contain_exactly(status_with_tag_cats.id, status_without_tags.id) end end end @@ -342,21 +344,21 @@ RSpec.describe Status, type: :model do end it 'creates new conversation for stand-alone status' do - expect(Status.create(account: alice, text: 'First').conversation_id).to_not be_nil + expect(described_class.create(account: alice, text: 'First').conversation_id).to_not be_nil end it 'keeps conversation of parent node' do parent = Fabricate(:status, text: 'First') - expect(Status.create(account: alice, thread: parent, text: 'Response').conversation_id).to eq parent.conversation_id + expect(described_class.create(account: alice, thread: parent, text: 'Response').conversation_id).to eq parent.conversation_id end it 'sets `local` to true for status by local account' do - expect(Status.create(account: alice, text: 'foo').local).to be true + expect(described_class.create(account: alice, text: 'foo').local).to be true end it 'sets `local` to false for status by remote account' do alice.update(domain: 'example.com') - expect(Status.create(account: alice, text: 'foo').local).to be false + expect(described_class.create(account: alice, text: 'foo').local).to be false end end @@ -370,7 +372,7 @@ RSpec.describe Status, type: :model do describe 'after_create' do it 'saves ActivityPub uri as uri for local status' do - status = Status.create(account: alice, text: 'foo') + status = described_class.create(account: alice, text: 'foo') status.reload expect(status.uri).to start_with('https://') end diff --git a/spec/models/status_stat_spec.rb b/spec/models/status_stat_spec.rb deleted file mode 100644 index af1a6f288..000000000 --- a/spec/models/status_stat_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe StatusStat, type: :model do -end diff --git a/spec/models/status_trend_spec.rb b/spec/models/status_trend_spec.rb deleted file mode 100644 index 6b82204a6..000000000 --- a/spec/models/status_trend_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe StatusTrend, type: :model do -end diff --git a/spec/models/system_key_spec.rb b/spec/models/system_key_spec.rb deleted file mode 100644 index a138bc131..000000000 --- a/spec/models/system_key_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -RSpec.describe SystemKey, type: :model do - -end diff --git a/spec/models/tag_feed_spec.rb b/spec/models/tag_feed_spec.rb index 55dddfe71..f3e6da074 100644 --- a/spec/models/tag_feed_spec.rb +++ b/spec/models/tag_feed_spec.rb @@ -1,73 +1,75 @@ +# frozen_string_literal: true + require 'rails_helper' describe TagFeed, type: :service do describe '#get' do let(:account) { Fabricate(:account) } - let(:tag1) { Fabricate(:tag) } - let(:tag2) { Fabricate(:tag) } - let!(:status1) { Fabricate(:status, tags: [tag1]) } - let!(:status2) { Fabricate(:status, tags: [tag2]) } - let!(:both) { Fabricate(:status, tags: [tag1, tag2]) } + let(:tag_cats) { Fabricate(:tag, name: 'cats') } + let(:tag_dogs) { Fabricate(:tag, name: 'dogs') } + let!(:status_tagged_with_cats) { Fabricate(:status, tags: [tag_cats]) } + let!(:status_tagged_with_dogs) { Fabricate(:status, tags: [tag_dogs]) } + let!(:both) { Fabricate(:status, tags: [tag_cats, tag_dogs]) } it 'can add tags in "any" mode' do - results = described_class.new(tag1, nil, any: [tag2.name]).get(20) - expect(results).to include status1 - expect(results).to include status2 + results = described_class.new(tag_cats, nil, any: [tag_dogs.name]).get(20) + expect(results).to include status_tagged_with_cats + expect(results).to include status_tagged_with_dogs expect(results).to include both end it 'can remove tags in "all" mode' do - results = described_class.new(tag1, nil, all: [tag2.name]).get(20) - expect(results).to_not include status1 - expect(results).to_not include status2 + results = described_class.new(tag_cats, nil, all: [tag_dogs.name]).get(20) + expect(results).to_not include status_tagged_with_cats + expect(results).to_not include status_tagged_with_dogs expect(results).to include both end it 'can remove tags in "none" mode' do - results = described_class.new(tag1, nil, none: [tag2.name]).get(20) - expect(results).to include status1 - expect(results).to_not include status2 + results = described_class.new(tag_cats, nil, none: [tag_dogs.name]).get(20) + expect(results).to include status_tagged_with_cats + expect(results).to_not include status_tagged_with_dogs expect(results).to_not include both end it 'ignores an invalid mode' do - results = described_class.new(tag1, nil, wark: [tag2.name]).get(20) - expect(results).to include status1 - expect(results).to_not include status2 + results = described_class.new(tag_cats, nil, wark: [tag_dogs.name]).get(20) + expect(results).to include status_tagged_with_cats + expect(results).to_not include status_tagged_with_dogs expect(results).to include both end it 'handles being passed non existent tag names' do - results = described_class.new(tag1, nil, any: ['wark']).get(20) - expect(results).to include status1 - expect(results).to_not include status2 + results = described_class.new(tag_cats, nil, any: ['wark']).get(20) + expect(results).to include status_tagged_with_cats + expect(results).to_not include status_tagged_with_dogs expect(results).to include both end it 'can restrict to an account' do - BlockService.new.call(account, status1.account) - results = described_class.new(tag1, account, none: [tag2.name]).get(20) - expect(results).to_not include status1 + BlockService.new.call(account, status_tagged_with_cats.account) + results = described_class.new(tag_cats, account, none: [tag_dogs.name]).get(20) + expect(results).to_not include status_tagged_with_cats end it 'can restrict to local' do - status1.account.update(domain: 'example.com') - status1.update(local: false, uri: 'example.com/toot') - results = described_class.new(tag1, nil, any: [tag2.name], local: true).get(20) - expect(results).to_not include status1 + status_tagged_with_cats.account.update(domain: 'example.com') + status_tagged_with_cats.update(local: false, uri: 'example.com/toot') + results = described_class.new(tag_cats, nil, any: [tag_dogs.name], local: true).get(20) + expect(results).to_not include status_tagged_with_cats end it 'excludes local-only posts when specified' do - status1.update(local_only: true) - results = described_class.new(tag1, nil, any: [tag2.name], without_local_only: true).get(20) - expect(results).to_not include status1 + status_tagged_with_cats.update(local_only: true) + results = described_class.new(tag_cats, nil, any: [tag_dogs.name], without_local_only: true).get(20) + expect(results).to_not include status_tagged_with_cats end it 'allows replies to be included' do original = Fabricate(:status) - status = Fabricate(:status, tags: [tag1], in_reply_to_id: original.id) + status = Fabricate(:status, tags: [tag_cats], in_reply_to_id: original.id) - results = described_class.new(tag1, nil).get(20) + results = described_class.new(tag_cats, nil).get(20) expect(results).to include(status) end end diff --git a/spec/models/tag_follow_spec.rb b/spec/models/tag_follow_spec.rb deleted file mode 100644 index 50c04d2e4..000000000 --- a/spec/models/tag_follow_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe TagFollow, type: :model do -end diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index b16f99a79..6177b7a25 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -1,21 +1,23 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Tag, type: :model do +RSpec.describe Tag do describe 'validations' do it 'invalid with #' do - expect(Tag.new(name: '#hello_world')).to_not be_valid + expect(described_class.new(name: '#hello_world')).to_not be_valid end it 'invalid with .' do - expect(Tag.new(name: '.abcdef123')).to_not be_valid + expect(described_class.new(name: '.abcdef123')).to_not be_valid end it 'invalid with spaces' do - expect(Tag.new(name: 'hello world')).to_not be_valid + expect(described_class.new(name: 'hello world')).to_not be_valid end it 'valid with aesthetic' do - expect(Tag.new(name: 'aesthetic')).to be_valid + expect(described_class.new(name: 'aesthetic')).to be_valid end end @@ -30,40 +32,52 @@ RSpec.describe Tag, type: :model do expect(subject.match('https://en.wikipedia.org/wiki/Ghostbusters_(song)#Lawsuit')).to be_nil end + it 'does not match URLs with hashtag-like anchors after a numeral' do + expect(subject.match('https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111895#c4')).to be_nil + end + + it 'does not match URLs with hashtag-like anchors after an empty query parameter' do + expect(subject.match('https://en.wikipedia.org/wiki/Ghostbusters_(song)?foo=#Lawsuit')).to be_nil + end + it 'matches #aesthetic' do - expect(subject.match('this is #aesthetic').to_s).to eq ' #aesthetic' + expect(subject.match('this is #aesthetic').to_s).to eq '#aesthetic' end it 'matches digits at the start' do - expect(subject.match('hello #3d').to_s).to eq ' #3d' + expect(subject.match('hello #3d').to_s).to eq '#3d' end it 'matches digits in the middle' do - expect(subject.match('hello #l33ts35k').to_s).to eq ' #l33ts35k' + expect(subject.match('hello #l33ts35k').to_s).to eq '#l33ts35k' end it 'matches digits at the end' do - expect(subject.match('hello #world2016').to_s).to eq ' #world2016' + expect(subject.match('hello #world2016').to_s).to eq '#world2016' end it 'matches underscores at the beginning' do - expect(subject.match('hello #_test').to_s).to eq ' #_test' + expect(subject.match('hello #_test').to_s).to eq '#_test' end it 'matches underscores at the end' do - expect(subject.match('hello #test_').to_s).to eq ' #test_' + expect(subject.match('hello #test_').to_s).to eq '#test_' end it 'matches underscores in the middle' do - expect(subject.match('hello #one_two_three').to_s).to eq ' #one_two_three' + expect(subject.match('hello #one_two_three').to_s).to eq '#one_two_three' end it 'matches middle dots' do - expect(subject.match('hello #one·two·three').to_s).to eq ' #one·two·three' + expect(subject.match('hello #one·two·three').to_s).to eq '#one·two·three' + end + + it 'matches ・unicode in ぼっち・ざ・ろっく correctly' do + expect(subject.match('testing #ぼっち・ざ・ろっく').to_s).to eq '#ぼっち・ざ・ろっく' end it 'matches ZWNJ' do - expect(subject.match('just add #نرم‌افزار and').to_s).to eq ' #نرم‌افزار' + expect(subject.match('just add #نرم‌افزار and').to_s).to eq '#نرم‌افزار' end it 'does not match middle dots at the start' do @@ -71,7 +85,7 @@ RSpec.describe Tag, type: :model do end it 'does not match middle dots at the end' do - expect(subject.match('hello #one·two·three·').to_s).to eq ' #one·two·three' + expect(subject.match('hello #one·two·three·').to_s).to eq '#one·two·three' end it 'does not match purely-numeric hashtags' do @@ -89,44 +103,46 @@ RSpec.describe Tag, type: :model do describe '.find_normalized' do it 'returns tag for a multibyte case-insensitive name' do upcase_string = 'abcABCabcABCやゆよ' - downcase_string = 'abcabcabcabcやゆよ'; + downcase_string = 'abcabcabcabcやゆよ' tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string)) - expect(Tag.find_normalized(upcase_string)).to eq tag + expect(described_class.find_normalized(upcase_string)).to eq tag end end describe '.matches_name' do it 'returns tags for multibyte case-insensitive names' do upcase_string = 'abcABCabcABCやゆよ' - downcase_string = 'abcabcabcabcやゆよ'; + downcase_string = 'abcabcabcabcやゆよ' tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string)) - expect(Tag.matches_name(upcase_string)).to eq [tag] + expect(described_class.matches_name(upcase_string)).to eq [tag] end it 'uses the LIKE operator' do - expect(Tag.matches_name('100%abc').to_sql).to eq %q[SELECT "tags".* FROM "tags" WHERE LOWER("tags"."name") LIKE LOWER('100abc%')] + result = %q[SELECT "tags".* FROM "tags" WHERE LOWER("tags"."name") LIKE LOWER('100abc%')] + expect(described_class.matches_name('100%abc').to_sql).to eq result end end describe '.matching_name' do it 'returns tags for multibyte case-insensitive names' do upcase_string = 'abcABCabcABCやゆよ' - downcase_string = 'abcabcabcabcやゆよ'; + downcase_string = 'abcabcabcabcやゆよ' tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string)) - expect(Tag.matching_name(upcase_string)).to eq [tag] + expect(described_class.matching_name(upcase_string)).to eq [tag] end end describe '.find_or_create_by_names' do - it 'runs a passed block once per tag regardless of duplicates' do - upcase_string = 'abcABCabcABCやゆよ' - downcase_string = 'abcabcabcabcやゆよ'; - count = 0 + let(:upcase_string) { 'abcABCabcABCやゆよ' } + let(:downcase_string) { 'abcabcabcabcやゆよ' } - Tag.find_or_create_by_names([upcase_string, downcase_string]) do |tag| + it 'runs a passed block once per tag regardless of duplicates' do + count = 0 + + described_class.find_or_create_by_names([upcase_string, downcase_string]) do |_tag| count += 1 end @@ -136,28 +152,28 @@ RSpec.describe Tag, type: :model do describe '.search_for' do it 'finds tag records with matching names' do - tag = Fabricate(:tag, name: "match") - _miss_tag = Fabricate(:tag, name: "miss") + tag = Fabricate(:tag, name: 'match') + _miss_tag = Fabricate(:tag, name: 'miss') - results = Tag.search_for("match") + results = described_class.search_for('match') expect(results).to eq [tag] end it 'finds tag records in case insensitive' do - tag = Fabricate(:tag, name: "MATCH") - _miss_tag = Fabricate(:tag, name: "miss") + tag = Fabricate(:tag, name: 'MATCH') + _miss_tag = Fabricate(:tag, name: 'miss') - results = Tag.search_for("match") + results = described_class.search_for('match') expect(results).to eq [tag] end it 'finds the exact matching tag as the first item' do - similar_tag = Fabricate(:tag, name: "matchlater", reviewed_at: Time.now.utc) - tag = Fabricate(:tag, name: "match", reviewed_at: Time.now.utc) + similar_tag = Fabricate(:tag, name: 'matchlater', reviewed_at: Time.now.utc) + tag = Fabricate(:tag, name: 'match', reviewed_at: Time.now.utc) - results = Tag.search_for("match") + results = described_class.search_for('match') expect(results).to eq [tag, similar_tag] end diff --git a/spec/models/trends/statuses_spec.rb b/spec/models/trends/statuses_spec.rb index 5f338a65e..7c30b5b99 100644 --- a/spec/models/trends/statuses_spec.rb +++ b/spec/models/trends/statuses_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Trends::Statuses do @@ -9,12 +11,12 @@ RSpec.describe Trends::Statuses do let!(:query) { subject.query } let!(:today) { at_time } - let!(:status1) { Fabricate(:status, text: 'Foo', language: 'en', trendable: true, created_at: today) } - let!(:status2) { Fabricate(:status, text: 'Bar', language: 'en', trendable: true, created_at: today) } + let!(:status_foo) { Fabricate(:status, text: 'Foo', language: 'en', trendable: true, created_at: today) } + let!(:status_bar) { Fabricate(:status, text: 'Bar', language: 'en', trendable: true, created_at: today) } before do - 15.times { reblog(status1, today) } - 12.times { reblog(status2, today) } + default_threshold_value.times { reblog(status_foo, today) } + default_threshold_value.times { reblog(status_bar, today) } subject.refresh(today) end @@ -27,18 +29,18 @@ RSpec.describe Trends::Statuses do end it 'filters out blocked accounts' do - account.block!(status1.account) - expect(query.filtered_for(account).to_a).to eq [status2] + account.block!(status_foo.account) + expect(query.filtered_for(account).to_a).to eq [status_bar] end it 'filters out muted accounts' do - account.mute!(status2.account) - expect(query.filtered_for(account).to_a).to eq [status1] + account.mute!(status_bar.account) + expect(query.filtered_for(account).to_a).to eq [status_foo] end it 'filters out blocked-by accounts' do - status1.account.block!(account) - expect(query.filtered_for(account).to_a).to eq [status2] + status_foo.account.block!(account) + expect(query.filtered_for(account).to_a).to eq [status_bar] end end end @@ -69,36 +71,35 @@ RSpec.describe Trends::Statuses do let!(:today) { at_time } let!(:yesterday) { today - 1.day } - let!(:status1) { Fabricate(:status, text: 'Foo', language: 'en', trendable: true, created_at: yesterday) } - let!(:status2) { Fabricate(:status, text: 'Bar', language: 'en', trendable: true, created_at: today) } - let!(:status3) { Fabricate(:status, text: 'Baz', language: 'en', trendable: true, created_at: today) } + let!(:status_foo) { Fabricate(:status, text: 'Foo', language: 'en', trendable: true, created_at: yesterday) } + let!(:status_bar) { Fabricate(:status, text: 'Bar', language: 'en', trendable: true, created_at: today) } + let!(:status_baz) { Fabricate(:status, text: 'Baz', language: 'en', trendable: true, created_at: today) } before do - 13.times { reblog(status1, today) } - 13.times { reblog(status2, today) } - 4.times { reblog(status3, today) } + default_threshold_value.times { reblog(status_foo, today) } + default_threshold_value.times { reblog(status_bar, today) } + (default_threshold_value - 1).times { reblog(status_baz, today) } end - context do + context 'when status trends are refreshed' do before do subject.refresh(today) end - it 'calculates and re-calculates scores' do - expect(subject.query.limit(10).to_a).to eq [status2, status1] - end + it 'returns correct statuses from query' do + results = subject.query.limit(10).to_a - it 'omits statuses below threshold' do - expect(subject.query.limit(10).to_a).to_not include(status3) + expect(results).to eq [status_bar, status_foo] + expect(results).to_not include(status_baz) end end it 'decays scores' do subject.refresh(today) - original_score = status2.trend.score + original_score = status_bar.trend.score expect(original_score).to be_a Float subject.refresh(today + subject.options[:score_halflife]) - decayed_score = status2.trend.reload.score + decayed_score = status_bar.trend.reload.score expect(decayed_score).to be <= original_score / 2 end end @@ -107,4 +108,8 @@ RSpec.describe Trends::Statuses do reblog = Fabricate(:status, reblog: status, created_at: at_time) subject.add(status, reblog.account_id, at_time) end + + def default_threshold_value + described_class.default_options[:threshold] + end end diff --git a/spec/models/trends/tags_spec.rb b/spec/models/trends/tags_spec.rb index f48c73503..f2818fca8 100644 --- a/spec/models/trends/tags_spec.rb +++ b/spec/models/trends/tags_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe Trends::Tags do @@ -22,45 +24,47 @@ RSpec.describe Trends::Tags do end describe '#query' do - pending + it 'returns a composable query scope' do + expect(subject.query).to be_a Trends::Query + end end describe '#refresh' do let!(:today) { at_time } let!(:yesterday) { today - 1.day } - let!(:tag1) { Fabricate(:tag, name: 'Catstodon', trendable: true) } - let!(:tag2) { Fabricate(:tag, name: 'DogsOfMastodon', trendable: true) } - let!(:tag3) { Fabricate(:tag, name: 'OCs', trendable: true) } + let!(:tag_cats) { Fabricate(:tag, name: 'Catstodon', trendable: true) } + let!(:tag_dogs) { Fabricate(:tag, name: 'DogsOfMastodon', trendable: true) } + let!(:tag_ocs) { Fabricate(:tag, name: 'OCs', trendable: true) } before do - 2.times { |i| subject.add(tag1, i, yesterday) } - 13.times { |i| subject.add(tag3, i, yesterday) } - 16.times { |i| subject.add(tag1, i, today) } - 4.times { |i| subject.add(tag2, i, today) } + 2.times { |i| subject.add(tag_cats, i, yesterday) } + 13.times { |i| subject.add(tag_ocs, i, yesterday) } + 16.times { |i| subject.add(tag_cats, i, today) } + 4.times { |i| subject.add(tag_dogs, i, today) } end - context do + context 'when tag trends are refreshed' do before do subject.refresh(yesterday + 12.hours) subject.refresh(at_time) end it 'calculates and re-calculates scores' do - expect(subject.query.limit(10).to_a).to eq [tag1, tag3] + expect(subject.query.limit(10).to_a).to eq [tag_cats, tag_ocs] end it 'omits hashtags below threshold' do - expect(subject.query.limit(10).to_a).to_not include(tag2) + expect(subject.query.limit(10).to_a).to_not include(tag_dogs) end end it 'decays scores' do subject.refresh(yesterday + 12.hours) - original_score = subject.score(tag3.id) + original_score = subject.score(tag_ocs.id) expect(original_score).to eq 144.0 subject.refresh(yesterday + 12.hours + subject.options[:max_score_halflife]) - decayed_score = subject.score(tag3.id) + decayed_score = subject.score(tag_ocs.id) expect(decayed_score).to be <= original_score / 2 end end diff --git a/spec/models/unavailable_domain_spec.rb b/spec/models/unavailable_domain_spec.rb deleted file mode 100644 index 3f2621034..000000000 --- a/spec/models/unavailable_domain_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe UnavailableDomain, type: :model do -end diff --git a/spec/models/user_invite_request_spec.rb b/spec/models/user_invite_request_spec.rb deleted file mode 100644 index 1be38d8a4..000000000 --- a/spec/models/user_invite_request_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe UserInviteRequest, type: :model do -end diff --git a/spec/models/user_role_spec.rb b/spec/models/user_role_spec.rb index 28019593e..f7cfe9bb0 100644 --- a/spec/models/user_role_spec.rb +++ b/spec/models/user_role_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe UserRole, type: :model do +RSpec.describe UserRole do subject { described_class.create(name: 'Foo', position: 1) } describe '#can?' do @@ -58,7 +60,7 @@ RSpec.describe UserRole, type: :model do end describe '#permissions_as_keys=' do - let(:input) { } + let(:input) {} before do subject.permissions_as_keys = input @@ -91,7 +93,7 @@ RSpec.describe UserRole, type: :model do describe '#computed_permissions' do context 'when the role is nobody' do - let(:subject) { described_class.nobody } + subject { described_class.nobody } it 'returns none' do expect(subject.computed_permissions).to eq UserRole::Flags::NONE @@ -99,7 +101,7 @@ RSpec.describe UserRole, type: :model do end context 'when the role is everyone' do - let(:subject) { described_class.everyone } + subject { described_class.everyone } it 'returns permissions' do expect(subject.computed_permissions).to eq subject.permissions @@ -116,10 +118,8 @@ RSpec.describe UserRole, type: :model do end end - context do - it 'returns permissions combined with the everyone role' do - expect(subject.computed_permissions).to eq described_class.everyone.permissions - end + it 'returns permissions combined with the everyone role' do + expect(subject.computed_permissions).to eq described_class.everyone.permissions end end @@ -127,7 +127,7 @@ RSpec.describe UserRole, type: :model do subject { described_class.everyone } it 'returns a role' do - expect(subject).to be_kind_of(described_class) + expect(subject).to be_a(described_class) end it 'is identified as the everyone role' do @@ -139,7 +139,7 @@ RSpec.describe UserRole, type: :model do end it 'has negative position' do - expect(subject.position).to eq -1 + expect(subject.position).to eq(-1) end end @@ -147,7 +147,7 @@ RSpec.describe UserRole, type: :model do subject { described_class.nobody } it 'returns a role' do - expect(subject).to be_kind_of(described_class) + expect(subject).to be_a(described_class) end it 'is identified as the nobody role' do @@ -159,7 +159,7 @@ RSpec.describe UserRole, type: :model do end it 'has negative position' do - expect(subject.position).to eq -1 + expect(subject.position).to eq(-1) end end diff --git a/spec/models/user_settings/namespace_spec.rb b/spec/models/user_settings/namespace_spec.rb new file mode 100644 index 000000000..ae2fa7b48 --- /dev/null +++ b/spec/models/user_settings/namespace_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe UserSettings::Namespace do + subject { described_class.new(name) } + + let(:name) { :foo } + + describe '#setting' do + before do + subject.setting :bar, default: 'baz' + end + + it 'adds setting to definitions' do + expect(subject.definitions[:'foo.bar']).to have_attributes(name: :bar, namespace: :foo, default_value: 'baz') + end + end + + describe '#definitions' do + it 'returns a hash' do + expect(subject.definitions).to be_a Hash + end + end +end diff --git a/spec/models/user_settings/setting_spec.rb b/spec/models/user_settings/setting_spec.rb new file mode 100644 index 000000000..8c8d31ec5 --- /dev/null +++ b/spec/models/user_settings/setting_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe UserSettings::Setting do + subject { described_class.new(name, options) } + + let(:name) { :foo } + let(:options) { { default: default, namespace: namespace } } + let(:default) { false } + let(:namespace) { nil } + + describe '#default_value' do + context 'when default value is a primitive value' do + it 'returns default value' do + expect(subject.default_value).to eq default + end + end + + context 'when default value is a proc' do + let(:default) { -> { 'bar' } } + + it 'returns value from proc' do + expect(subject.default_value).to eq 'bar' + end + end + end + + describe '#type' do + it 'returns a type' do + expect(subject.type).to be_a ActiveModel::Type::Value + end + + context 'when default value is a boolean' do + let(:default) { false } + + it 'returns boolean' do + expect(subject.type).to be_a ActiveModel::Type::Boolean + end + end + + context 'when default value is a string' do + let(:default) { '' } + + it 'returns string' do + expect(subject.type).to be_a ActiveModel::Type::String + end + end + + context 'when default value is a lambda returning a boolean' do + let(:default) { -> { false } } + + it 'returns boolean' do + expect(subject.type).to be_a ActiveModel::Type::Boolean + end + end + + context 'when default value is a lambda returning a string' do + let(:default) { -> { '' } } + + it 'returns boolean' do + expect(subject.type).to be_a ActiveModel::Type::String + end + end + end + + describe '#type_cast' do + context 'when default value is a boolean' do + let(:default) { false } + + it 'returns boolean' do + expect(subject.type_cast('1')).to be true + end + end + + context 'when default value is a string' do + let(:default) { '' } + + it 'returns string' do + expect(subject.type_cast(1)).to eq '1' + end + end + end + + describe '#to_a' do + it 'returns an array' do + expect(subject.to_a).to eq [name, default] + end + end + + describe '#key' do + context 'when there is no namespace' do + it 'returns a symbol' do + expect(subject.key).to eq :foo + end + end + + context 'when there is a namespace' do + let(:namespace) { :bar } + + it 'returns a symbol' do + expect(subject.key).to eq :'bar.foo' + end + end + end +end diff --git a/spec/models/user_settings_spec.rb b/spec/models/user_settings_spec.rb new file mode 100644 index 000000000..653597c90 --- /dev/null +++ b/spec/models/user_settings_spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe UserSettings do + subject { described_class.new(json) } + + let(:json) { {} } + + describe '#[]' do + context 'when setting is not set' do + it 'returns default value' do + expect(subject[:always_send_emails]).to be false + end + end + + context 'when setting is set' do + let(:json) { { default_language: 'fr' } } + + it 'returns value' do + expect(subject[:default_language]).to eq 'fr' + end + end + + context 'when setting was not defined' do + it 'raises error' do + expect { subject[:foo] }.to raise_error UserSettings::KeyError + end + end + end + + describe '#[]=' do + context 'when value matches type' do + before do + subject[:always_send_emails] = true + end + + it 'updates value' do + expect(subject[:always_send_emails]).to be true + end + end + + context 'when value needs to be type-cast' do + before do + subject[:always_send_emails] = '1' + end + + it 'updates value with a type-cast' do + expect(subject[:always_send_emails]).to be true + end + end + + context 'when the setting has a closed set of values' do + it 'updates the attribute when given a valid value' do + expect { subject[:'web.display_media'] = :show_all }.to change { subject[:'web.display_media'] }.from('default').to('show_all') + end + + it 'raises an error when given an invalid value' do + expect { subject[:'web.display_media'] = 'invalid value' }.to raise_error ArgumentError + end + end + end + + describe '#update' do + before do + subject.update(always_send_emails: true, default_language: 'fr', default_privacy: nil) + end + + it 'updates values' do + expect(subject[:always_send_emails]).to be true + expect(subject[:default_language]).to eq 'fr' + end + + it 'does not set values that are nil' do + expect(subject.as_json).to_not include(default_privacy: nil) + end + end + + describe '#as_json' do + let(:json) { { default_language: 'fr' } } + + it 'returns hash' do + expect(subject.as_json).to eq json + end + end + + describe '.keys' do + it 'returns an array' do + expect(described_class.keys).to be_a Array + end + end + + describe '.definition_for' do + context 'when key is defined' do + it 'returns a setting' do + expect(described_class.definition_for(:always_send_emails)).to be_a UserSettings::Setting + end + end + + context 'when key is not defined' do + it 'returns nil' do + expect(described_class.definition_for(:foo)).to be_nil + end + end + end + + describe '.definition_for?' do + context 'when key is defined' do + it 'returns true' do + expect(described_class.definition_for?(:always_send_emails)).to be true + end + end + + context 'when key is not defined' do + it 'returns false' do + expect(described_class.definition_for?(:foo)).to be false + end + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 0d6f16070..f06150f02 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,7 +1,12 @@ +# frozen_string_literal: true + require 'rails_helper' require 'devise_two_factor/spec_helpers' -RSpec.describe User, type: :model do +RSpec.describe User do + let(:password) { 'abcd1234' } + let(:account) { Fabricate(:account, username: 'alice') } + it_behaves_like 'two_factor_backupable' describe 'otp_secret' do @@ -22,12 +27,6 @@ RSpec.describe User, type: :model do expect(user).to model_have_error_on_field(:account) end - it 'is invalid without a valid locale' do - user = Fabricate.build(:user, locale: 'toto') - user.valid? - expect(user).to model_have_error_on_field(:locale) - end - it 'is invalid without a valid email' do user = Fabricate.build(:user, email: 'john@') user.valid? @@ -40,10 +39,28 @@ RSpec.describe User, type: :model do expect(user.valid?).to be true end + it 'is valid with a localhost e-mail address' do + user = Fabricate.build(:user, email: 'admin@localhost') + user.valid? + expect(user.valid?).to be true + end + + it 'cleans out invalid locale' do + user = Fabricate.build(:user, locale: 'toto') + expect(user.valid?).to be true + expect(user.locale).to be_nil + end + + it 'cleans out invalid timezone' do + user = Fabricate.build(:user, time_zone: 'toto') + expect(user.valid?).to be true + expect(user.time_zone).to be_nil + end + it 'cleans out empty string from languages' do user = Fabricate.build(:user, chosen_languages: ['']) user.valid? - expect(user.chosen_languages).to eq nil + expect(user.chosen_languages).to be_nil end end @@ -52,7 +69,7 @@ RSpec.describe User, type: :model do it 'returns an array of recent users ordered by id' do user_1 = Fabricate(:user) user_2 = Fabricate(:user) - expect(User.recent).to eq [user_2, user_1] + expect(described_class.recent).to eq [user_2, user_1] end end @@ -60,7 +77,7 @@ RSpec.describe User, type: :model do it 'returns an array of users who are confirmed' do user_1 = Fabricate(:user, confirmed_at: nil) user_2 = Fabricate(:user, confirmed_at: Time.zone.now) - expect(User.confirmed).to match_array([user_2]) + expect(described_class.confirmed).to contain_exactly(user_2) end end @@ -69,7 +86,7 @@ RSpec.describe User, type: :model do specified = Fabricate(:user, current_sign_in_at: 15.days.ago) Fabricate(:user, current_sign_in_at: 6.days.ago) - expect(User.inactive).to match_array([specified]) + expect(described_class.inactive).to contain_exactly(specified) end end @@ -78,7 +95,7 @@ RSpec.describe User, type: :model do specified = Fabricate(:user, email: 'specified@spec') Fabricate(:user, email: 'unspecified@spec') - expect(User.matches_email('specified')).to match_array([specified]) + expect(described_class.matches_email('specified')).to contain_exactly(specified) end end @@ -91,14 +108,11 @@ RSpec.describe User, type: :model do Fabricate(:session_activation, user: user2, ip: '2160:8888::24', session_id: '3') Fabricate(:session_activation, user: user2, ip: '2160:8888::25', session_id: '4') - expect(User.matches_ip('2160:2160::/32')).to match_array([user1]) + expect(described_class.matches_ip('2160:2160::/32')).to contain_exactly(user1) end end end - let(:account) { Fabricate(:account, username: 'alice') } - let(:password) { 'abcd1234' } - describe 'blacklist' do around(:each) do |example| old_blacklist = Rails.configuration.x.email_blacklist @@ -110,22 +124,22 @@ RSpec.describe User, type: :model do Rails.configuration.x.email_domains_blacklist = old_blacklist end - it 'should allow a non-blacklisted user to be created' do - user = User.new(email: 'foo@example.com', account: account, password: password, agreement: true) + it 'allows a non-blacklisted user to be created' do + user = described_class.new(email: 'foo@example.com', account: account, password: password, agreement: true) - expect(user.valid?).to be_truthy + expect(user).to be_valid end - it 'should not allow a blacklisted user to be created' do - user = User.new(email: 'foo@mvrht.com', account: account, password: password, agreement: true) + it 'does not allow a blacklisted user to be created' do + user = described_class.new(email: 'foo@mvrht.com', account: account, password: password, agreement: true) - expect(user.valid?).to be_falsey + expect(user).to_not be_valid end - it 'should not allow a subdomain blacklisted user to be created' do - user = User.new(email: 'foo@mvrht.com.topdomain.tld', account: account, password: password, agreement: true) + it 'does not allow a subdomain blacklisted user to be created' do + user = described_class.new(email: 'foo@mvrht.com.topdomain.tld', account: account, password: password, agreement: true) - expect(user.valid?).to be_falsey + expect(user).to_not be_valid end end @@ -142,10 +156,136 @@ RSpec.describe User, type: :model do end describe '#confirm' do - it 'sets email to unconfirmed_email' do - user = Fabricate.build(:user, confirmed_at: Time.now.utc, unconfirmed_email: 'new-email@example.com') - user.confirm - expect(user.email).to eq 'new-email@example.com' + subject { user.confirm } + + let(:new_email) { 'new-email@example.com' } + + before do + allow(TriggerWebhookWorker).to receive(:perform_async) + end + + context 'when the user is already confirmed' do + let!(:user) { Fabricate(:user, confirmed_at: Time.now.utc, approved: true, unconfirmed_email: new_email) } + + it 'sets email to unconfirmed_email' do + expect { subject }.to change { user.reload.email }.to(new_email) + end + + it 'does not trigger the account.approved Web Hook' do + subject + expect(TriggerWebhookWorker).to_not have_received(:perform_async).with('account.approved', 'Account', user.account_id) + end + end + + context 'when the user is a new user' do + let(:user) { Fabricate(:user, confirmed_at: nil, unconfirmed_email: new_email) } + + context 'when the user is already approved' do + around(:example) do |example| + registrations_mode = Setting.registrations_mode + Setting.registrations_mode = 'approved' + + example.run + + Setting.registrations_mode = registrations_mode + end + + before do + user.approve! + end + + it 'sets email to unconfirmed_email' do + expect { subject }.to change { user.reload.email }.to(new_email) + end + + it 'triggers the account.approved Web Hook' do + user.confirm + expect(TriggerWebhookWorker).to have_received(:perform_async).with('account.approved', 'Account', user.account_id).once + end + end + + context 'when the user does not require explicit approval' do + around(:example) do |example| + registrations_mode = Setting.registrations_mode + Setting.registrations_mode = 'open' + + example.run + + Setting.registrations_mode = registrations_mode + end + + it 'sets email to unconfirmed_email' do + expect { subject }.to change { user.reload.email }.to(new_email) + end + + it 'triggers the account.approved Web Hook' do + user.confirm + expect(TriggerWebhookWorker).to have_received(:perform_async).with('account.approved', 'Account', user.account_id).once + end + end + + context 'when the user requires explicit approval but is not approved' do + around(:example) do |example| + registrations_mode = Setting.registrations_mode + Setting.registrations_mode = 'approved' + + example.run + + Setting.registrations_mode = registrations_mode + end + + it 'sets email to unconfirmed_email' do + expect { subject }.to change { user.reload.email }.to(new_email) + end + + it 'does not trigger the account.approved Web Hook' do + subject + expect(TriggerWebhookWorker).to_not have_received(:perform_async).with('account.approved', 'Account', user.account_id) + end + end + end + end + + describe '#approve!' do + subject { user.approve! } + + around(:example) do |example| + registrations_mode = Setting.registrations_mode + Setting.registrations_mode = 'approved' + + example.run + + Setting.registrations_mode = registrations_mode + end + + before do + allow(TriggerWebhookWorker).to receive(:perform_async) + end + + context 'when the user is already confirmed' do + let(:user) { Fabricate(:user, confirmed_at: Time.now.utc, approved: false) } + + it 'sets the approved flag' do + expect { subject }.to change { user.reload.approved? }.to(true) + end + + it 'triggers the account.approved Web Hook' do + subject + expect(TriggerWebhookWorker).to have_received(:perform_async).with('account.approved', 'Account', user.account_id).once + end + end + + context 'when the user is not confirmed' do + let(:user) { Fabricate(:user, confirmed_at: nil, approved: false) } + + it 'sets the approved flag' do + expect { subject }.to change { user.reload.approved? }.to(true) + end + + it 'does not trigger the account.approved Web Hook' do + subject + expect(TriggerWebhookWorker).to_not have_received(:perform_async).with('account.approved', 'Account', user.account_id) + end end end @@ -159,7 +299,7 @@ RSpec.describe User, type: :model do it 'saves nil for otp_secret' do user = Fabricate.build(:user, otp_secret: 'oldotpcode') user.disable_two_factor! - expect(user.reload.otp_secret).to be nil + expect(user.reload.otp_secret).to be_nil end it 'saves cleared otp_backup_codes' do @@ -185,9 +325,9 @@ RSpec.describe User, type: :model do end describe 'settings' do - it 'is instance of Settings::ScopedSettings' do + it 'is instance of UserSettings' do user = Fabricate(:user) - expect(user.settings).to be_kind_of Settings::ScopedSettings + expect(user.settings).to be_a UserSettings end end @@ -220,47 +360,37 @@ RSpec.describe User, type: :model do Rails.configuration.x.email_domains_whitelist = old_whitelist end - it 'should not allow a user to be created unless they are whitelisted' do - user = User.new(email: 'foo@example.com', account: account, password: password, agreement: true) - expect(user.valid?).to be_falsey + it 'does not allow a user to be created unless they are whitelisted' do + user = described_class.new(email: 'foo@example.com', account: account, password: password, agreement: true) + expect(user).to_not be_valid end - it 'should allow a user to be created if they are whitelisted' do - user = User.new(email: 'foo@mastodon.space', account: account, password: password, agreement: true) - expect(user.valid?).to be_truthy + it 'allows a user to be created if they are whitelisted' do + user = described_class.new(email: 'foo@mastodon.space', account: account, password: password, agreement: true) + expect(user).to be_valid end - it 'should not allow a user with a whitelisted top domain as subdomain in their email address to be created' do - user = User.new(email: 'foo@mastodon.space.userdomain.com', account: account, password: password, agreement: true) - expect(user.valid?).to be_falsey + it 'does not allow a user with a whitelisted top domain as subdomain in their email address to be created' do + user = described_class.new(email: 'foo@mastodon.space.userdomain.com', account: account, password: password, agreement: true) + expect(user).to_not be_valid end - context do + context 'with a blacklisted subdomain' do around do |example| old_blacklist = Rails.configuration.x.email_blacklist example.run Rails.configuration.x.email_domains_blacklist = old_blacklist end - it 'should not allow a user to be created with a specific blacklisted subdomain even if the top domain is whitelisted' do + it 'does not allow a user to be created with a specific blacklisted subdomain even if the top domain is whitelisted' do Rails.configuration.x.email_domains_blacklist = 'blacklisted.mastodon.space' - user = User.new(email: 'foo@blacklisted.mastodon.space', account: account, password: password) - expect(user.valid?).to be_falsey + user = described_class.new(email: 'foo@blacklisted.mastodon.space', account: account, password: password) + expect(user).to_not be_valid end end end - it_behaves_like 'Settings-extended' do - def create! - User.create!(account: Fabricate(:account, user: nil), email: 'foo@mastodon.space', password: 'abcd1234', agreement: true) - end - - def fabricate - Fabricate(:user) - end - end - describe 'token_for_app' do let(:user) { Fabricate(:user) } let(:app) { Fabricate(:application, owner: user) } @@ -283,6 +413,7 @@ RSpec.describe User, type: :model do describe '#disable!' do subject(:user) { Fabricate(:user, disabled: false, current_sign_in_at: current_sign_in_at, last_sign_in_at: nil) } + let(:current_sign_in_at) { Time.zone.now } before do @@ -378,6 +509,7 @@ RSpec.describe User, type: :model do describe '#active_for_authentication?' do subject { user.active_for_authentication? } + let(:user) { Fabricate(:user, disabled: disabled, confirmed_at: confirmed_at) } context 'when user is disabled' do @@ -414,6 +546,28 @@ RSpec.describe User, type: :model do end describe '.those_who_can' do - pending + before { Fabricate(:user, role: UserRole.find_by(name: 'Moderator')) } + + context 'when there are not any user roles' do + before { UserRole.destroy_all } + + it 'returns an empty list' do + expect(described_class.those_who_can(:manage_blocks)).to eq([]) + end + end + + context 'when there are not users with the needed role' do + it 'returns an empty list' do + expect(described_class.those_who_can(:manage_blocks)).to eq([]) + end + end + + context 'when there are users with roles' do + let!(:admin_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } + + it 'returns the users with the role' do + expect(described_class.those_who_can(:manage_blocks)).to eq([admin_user]) + end + end end end diff --git a/spec/models/web/push_subscription_spec.rb b/spec/models/web/push_subscription_spec.rb index bd5719593..3c2cd3bac 100644 --- a/spec/models/web/push_subscription_spec.rb +++ b/spec/models/web/push_subscription_spec.rb @@ -1,6 +1,10 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Web::PushSubscription, type: :model do +RSpec.describe Web::PushSubscription do + subject { described_class.new(data: data) } + let(:account) { Fabricate(:account) } let(:policy) { 'all' } @@ -19,8 +23,6 @@ RSpec.describe Web::PushSubscription, type: :model do } end - subject { described_class.new(data: data) } - describe '#pushable?' do let(:notification_type) { :mention } let(:notification) { Fabricate(:notification, account: account, type: notification_type) } @@ -29,7 +31,7 @@ RSpec.describe Web::PushSubscription, type: :model do context "when notification is a #{type}" do let(:notification_type) { type } - it "returns boolean corresponding to alert setting" do + it 'returns boolean corresponding to alert setting' do expect(subject.pushable?(notification)).to eq data[:alerts][type] end end @@ -39,7 +41,7 @@ RSpec.describe Web::PushSubscription, type: :model do let(:policy) { 'all' } it 'returns true' do - expect(subject.pushable?(notification)).to eq true + expect(subject.pushable?(notification)).to be true end end @@ -47,26 +49,26 @@ RSpec.describe Web::PushSubscription, type: :model do let(:policy) { 'none' } it 'returns false' do - expect(subject.pushable?(notification)).to eq false + expect(subject.pushable?(notification)).to be false end end context 'when policy is followed' do let(:policy) { 'followed' } - context 'and notification is from someone you follow' do + context 'when notification is from someone you follow' do before do account.follow!(notification.from_account) end it 'returns true' do - expect(subject.pushable?(notification)).to eq true + expect(subject.pushable?(notification)).to be true end end - context 'and notification is not from someone you follow' do + context 'when notification is not from someone you follow' do it 'returns false' do - expect(subject.pushable?(notification)).to eq false + expect(subject.pushable?(notification)).to be false end end end @@ -74,19 +76,19 @@ RSpec.describe Web::PushSubscription, type: :model do context 'when policy is follower' do let(:policy) { 'follower' } - context 'and notification is from someone who follows you' do + context 'when notification is from someone who follows you' do before do notification.from_account.follow!(account) end it 'returns true' do - expect(subject.pushable?(notification)).to eq true + expect(subject.pushable?(notification)).to be true end end - context 'and notification is not from someone who follows you' do + context 'when notification is not from someone who follows you' do it 'returns false' do - expect(subject.pushable?(notification)).to eq false + expect(subject.pushable?(notification)).to be false end end end diff --git a/spec/models/web/setting_spec.rb b/spec/models/web/setting_spec.rb deleted file mode 100644 index 6657d4030..000000000 --- a/spec/models/web/setting_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rails_helper' - -RSpec.describe Web::Setting, type: :model do -end diff --git a/spec/models/webauthn_credentials_spec.rb b/spec/models/webauthn_credentials_spec.rb index a63ae6cd2..4579ebb82 100644 --- a/spec/models/webauthn_credentials_spec.rb +++ b/spec/models/webauthn_credentials_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe WebauthnCredential, type: :model do +RSpec.describe WebauthnCredential do describe 'validations' do it 'is invalid without an external id' do webauthn_credential = Fabricate.build(:webauthn_credential, external_id: nil) @@ -35,8 +37,8 @@ RSpec.describe WebauthnCredential, type: :model do end it 'is invalid if already exist a webauthn credential with the same external id' do - existing_webauthn_credential = Fabricate(:webauthn_credential, external_id: "_Typ0ygudDnk9YUVWLQayw") - new_webauthn_credential = Fabricate.build(:webauthn_credential, external_id: "_Typ0ygudDnk9YUVWLQayw") + existing_webauthn_credential = Fabricate(:webauthn_credential, external_id: '_Typ0ygudDnk9YUVWLQayw') + new_webauthn_credential = Fabricate.build(:webauthn_credential, external_id: '_Typ0ygudDnk9YUVWLQayw') new_webauthn_credential.valid? diff --git a/spec/models/webhook_spec.rb b/spec/models/webhook_spec.rb index 60c3d9524..715dd7574 100644 --- a/spec/models/webhook_spec.rb +++ b/spec/models/webhook_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Webhook, type: :model do +RSpec.describe Webhook do let(:webhook) { Fabricate(:webhook) } describe '#rotate_secret!' do diff --git a/spec/policies/account_moderation_note_policy_spec.rb b/spec/policies/account_moderation_note_policy_spec.rb index 846747346..8c37acc39 100644 --- a/spec/policies/account_moderation_note_policy_spec.rb +++ b/spec/policies/account_moderation_note_policy_spec.rb @@ -4,20 +4,21 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe AccountModerationNotePolicy do - let(:subject) { described_class } + subject { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:john) { Fabricate(:account) } permissions :create? do - context 'staff' do + context 'when staff' do it 'grants to create' do - expect(subject).to permit(admin, AccountModerationNotePolicy) + expect(subject).to permit(admin, described_class) end end - context 'not staff' do + context 'when not staff' do it 'denies to create' do - expect(subject).to_not permit(john, AccountModerationNotePolicy) + expect(subject).to_not permit(john, described_class) end end end @@ -29,19 +30,19 @@ RSpec.describe AccountModerationNotePolicy do target_account: Fabricate(:account)) end - context 'admin' do + context 'when admin' do it 'grants to destroy' do expect(subject).to permit(admin, account_moderation_note) end end - context 'owner' do + context 'when owner' do it 'grants to destroy' do expect(subject).to permit(john, account_moderation_note) end end - context 'neither admin nor owner' do + context 'when neither admin nor owner' do let(:kevin) { Fabricate(:account) } it 'denies to destroy' do diff --git a/spec/policies/account_policy_spec.rb b/spec/policies/account_policy_spec.rb index 0f23fd97e..d7a21d8e3 100644 --- a/spec/policies/account_policy_spec.rb +++ b/spec/policies/account_policy_spec.rb @@ -4,19 +4,20 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe AccountPolicy do - let(:subject) { described_class } + subject { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:john) { Fabricate(:account) } let(:alice) { Fabricate(:account) } permissions :index? do - context 'staff' do + context 'when staff' do it 'permits' do expect(subject).to permit(admin) end end - context 'not staff' do + context 'when not staff' do it 'denies' do expect(subject).to_not permit(john) end @@ -24,13 +25,13 @@ RSpec.describe AccountPolicy do end permissions :show?, :unsilence?, :unsensitive?, :remove_avatar?, :remove_header? do - context 'staff' do + context 'when staff' do it 'permits' do expect(subject).to permit(admin, alice) end end - context 'not staff' do + context 'when not staff' do it 'denies' do expect(subject).to_not permit(john, alice) end @@ -42,13 +43,13 @@ RSpec.describe AccountPolicy do alice.suspend! end - context 'staff' do + context 'when staff' do it 'permits' do expect(subject).to permit(admin, alice) end end - context 'not staff' do + context 'when not staff' do it 'denies' do expect(subject).to_not permit(john, alice) end @@ -56,13 +57,13 @@ RSpec.describe AccountPolicy do end permissions :redownload? do - context 'admin' do + context 'when admin' do it 'permits' do expect(subject).to permit(admin) end end - context 'not admin' do + context 'when not admin' do it 'denies' do expect(subject).to_not permit(john) end @@ -72,21 +73,21 @@ RSpec.describe AccountPolicy do permissions :suspend?, :silence? do let(:staff) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } - context 'staff' do - context 'record is staff' do + context 'when staff' do + context 'when record is staff' do it 'denies' do expect(subject).to_not permit(admin, staff) end end - context 'record is not staff' do + context 'when record is not staff' do it 'permits' do expect(subject).to permit(admin, john) end end end - context 'not staff' do + context 'when not staff' do it 'denies' do expect(subject).to_not permit(john, Account) end @@ -96,24 +97,64 @@ RSpec.describe AccountPolicy do permissions :memorialize? do let(:other_admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } - context 'admin' do - context 'record is admin' do + context 'when admin' do + context 'when record is admin' do it 'denies' do expect(subject).to_not permit(admin, other_admin) end end - context 'record is not admin' do + context 'when record is not admin' do it 'permits' do expect(subject).to permit(admin, john) end end end - context 'not admin' do + context 'when not admin' do it 'denies' do expect(subject).to_not permit(john, Account) end end end + + permissions :review? do + context 'when admin' do + it 'permits' do + expect(subject).to permit(admin) + end + end + + context 'when not admin' do + it 'denies' do + expect(subject).to_not permit(john) + end + end + end + + permissions :destroy? do + context 'when admin' do + context 'with a temporarily suspended account' do + before { allow(alice).to receive(:suspended_temporarily?).and_return(true) } + + it 'permits' do + expect(subject).to permit(admin, alice) + end + end + + context 'with a not temporarily suspended account' do + before { allow(alice).to receive(:suspended_temporarily?).and_return(false) } + + it 'denies' do + expect(subject).to_not permit(admin, alice) + end + end + end + + context 'when not admin' do + it 'denies' do + expect(subject).to_not permit(john, alice) + end + end + end end diff --git a/spec/policies/account_warning_preset_policy_spec.rb b/spec/policies/account_warning_preset_policy_spec.rb new file mode 100644 index 000000000..63bf33de2 --- /dev/null +++ b/spec/policies/account_warning_preset_policy_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'pundit/rspec' + +describe AccountWarningPresetPolicy do + let(:policy) { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:john) { Fabricate(:account) } + + permissions :index?, :create?, :update?, :destroy? do + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, Tag) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, Tag) + end + end + end +end diff --git a/spec/policies/admin/status_policy_spec.rb b/spec/policies/admin/status_policy_spec.rb new file mode 100644 index 000000000..af9f7716b --- /dev/null +++ b/spec/policies/admin/status_policy_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'pundit/rspec' + +describe Admin::StatusPolicy do + let(:policy) { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:john) { Fabricate(:account) } + let(:status) { Fabricate(:status, visibility: status_visibility) } + let(:status_visibility) { :public } + + permissions :index?, :update?, :review?, :destroy? do + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, Tag) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, Tag) + end + end + end + + permissions :show? do + context 'with an admin' do + context 'with a public visible status' do + let(:status_visibility) { :public } + + it 'permits' do + expect(policy).to permit(admin, status) + end + end + + context 'with a not public visible status' do + let(:status_visibility) { :direct } + + it 'denies' do + expect(policy).to_not permit(admin, status) + end + + context 'when the status mentions the admin' do + before do + status.mentions.create!(account: admin) + end + + it 'permits' do + expect(policy).to permit(admin, status) + end + end + end + end + + context 'with a non admin' do + it 'denies' do + expect(policy).to_not permit(john, status) + end + end + end +end diff --git a/spec/policies/announcement_policy_spec.rb b/spec/policies/announcement_policy_spec.rb new file mode 100644 index 000000000..3d230b3cb --- /dev/null +++ b/spec/policies/announcement_policy_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'pundit/rspec' + +describe AnnouncementPolicy do + let(:policy) { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:john) { Fabricate(:account) } + + permissions :index?, :create?, :update?, :destroy? do + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, Tag) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, Tag) + end + end + end +end diff --git a/spec/policies/appeal_policy_spec.rb b/spec/policies/appeal_policy_spec.rb new file mode 100644 index 000000000..d7498eb9f --- /dev/null +++ b/spec/policies/appeal_policy_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'pundit/rspec' + +describe AppealPolicy do + let(:policy) { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:john) { Fabricate(:account) } + let(:appeal) { Fabricate(:appeal) } + + permissions :index? do + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, Tag) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, Tag) + end + end + end + + permissions :reject? do + context 'with an admin' do + context 'with a pending appeal' do + before { allow(appeal).to receive(:pending?).and_return(true) } + + it 'permits' do + expect(policy).to permit(admin, appeal) + end + end + + context 'with a not pending appeal' do + before { allow(appeal).to receive(:pending?).and_return(false) } + + it 'denies' do + expect(policy).to_not permit(admin, appeal) + end + end + end + + context 'with a non admin' do + it 'denies' do + expect(policy).to_not permit(john, appeal) + end + end + end +end diff --git a/spec/policies/backup_policy_spec.rb b/spec/policies/backup_policy_spec.rb index 6b31c6f7c..28cb65d78 100644 --- a/spec/policies/backup_policy_spec.rb +++ b/spec/policies/backup_policy_spec.rb @@ -4,24 +4,25 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe BackupPolicy do - let(:subject) { described_class } - let(:john) { Fabricate(:account) } + subject { described_class } + + let(:john) { Fabricate(:account) } permissions :create? do - context 'not user_signed_in?' do + context 'when not user_signed_in?' do it 'denies' do expect(subject).to_not permit(nil, Backup) end end - context 'user_signed_in?' do - context 'no backups' do + context 'when user_signed_in?' do + context 'with no backups' do it 'permits' do expect(subject).to permit(john, Backup) end end - context 'backups are too old' do + context 'when backups are too old' do it 'permits' do travel(-8.days) do Fabricate(:backup, user: john.user) @@ -31,7 +32,7 @@ RSpec.describe BackupPolicy do end end - context 'backups are newer' do + context 'when backups are newer' do it 'denies' do travel(-3.days) do Fabricate(:backup, user: john.user) diff --git a/spec/policies/canonical_email_block_policy_spec.rb b/spec/policies/canonical_email_block_policy_spec.rb new file mode 100644 index 000000000..0e55febfa --- /dev/null +++ b/spec/policies/canonical_email_block_policy_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'pundit/rspec' + +describe CanonicalEmailBlockPolicy do + let(:policy) { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:john) { Fabricate(:account) } + + permissions :index?, :show?, :test?, :create?, :destroy? do + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, Tag) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, Tag) + end + end + end +end diff --git a/spec/policies/custom_emoji_policy_spec.rb b/spec/policies/custom_emoji_policy_spec.rb index 6a6ef6694..cb869c7d9 100644 --- a/spec/policies/custom_emoji_policy_spec.rb +++ b/spec/policies/custom_emoji_policy_spec.rb @@ -4,18 +4,19 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe CustomEmojiPolicy do - let(:subject) { described_class } + subject { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:john) { Fabricate(:account) } permissions :index?, :enable?, :disable? do - context 'staff' do + context 'when staff' do it 'permits' do expect(subject).to permit(admin, CustomEmoji) end end - context 'not staff' do + context 'when not staff' do it 'denies' do expect(subject).to_not permit(john, CustomEmoji) end @@ -23,13 +24,13 @@ RSpec.describe CustomEmojiPolicy do end permissions :create?, :update?, :copy?, :destroy? do - context 'admin' do + context 'when admin' do it 'permits' do expect(subject).to permit(admin, CustomEmoji) end end - context 'not admin' do + context 'when not admin' do it 'denies' do expect(subject).to_not permit(john, CustomEmoji) end diff --git a/spec/policies/delivery_policy_spec.rb b/spec/policies/delivery_policy_spec.rb new file mode 100644 index 000000000..fbcbf390d --- /dev/null +++ b/spec/policies/delivery_policy_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'pundit/rspec' + +describe DeliveryPolicy do + let(:policy) { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:john) { Fabricate(:account) } + + permissions :clear_delivery_errors?, :restart_delivery?, :stop_delivery? do + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, Tag) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, Tag) + end + end + end +end diff --git a/spec/policies/domain_block_policy_spec.rb b/spec/policies/domain_block_policy_spec.rb index 01b97e823..4c89f3f37 100644 --- a/spec/policies/domain_block_policy_spec.rb +++ b/spec/policies/domain_block_policy_spec.rb @@ -4,18 +4,19 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe DomainBlockPolicy do - let(:subject) { described_class } + subject { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:john) { Fabricate(:account) } permissions :index?, :show?, :create?, :destroy? do - context 'admin' do + context 'when admin' do it 'permits' do expect(subject).to permit(admin, DomainBlock) end end - context 'not admin' do + context 'when not admin' do it 'denies' do expect(subject).to_not permit(john, DomainBlock) end diff --git a/spec/policies/email_domain_block_policy_spec.rb b/spec/policies/email_domain_block_policy_spec.rb index 913075c3d..7ecff4be4 100644 --- a/spec/policies/email_domain_block_policy_spec.rb +++ b/spec/policies/email_domain_block_policy_spec.rb @@ -4,18 +4,19 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe EmailDomainBlockPolicy do - let(:subject) { described_class } + subject { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:john) { Fabricate(:account) } - permissions :index?, :create?, :destroy? do - context 'admin' do + permissions :index?, :show?, :create?, :destroy? do + context 'when admin' do it 'permits' do expect(subject).to permit(admin, EmailDomainBlock) end end - context 'not admin' do + context 'when not admin' do it 'denies' do expect(subject).to_not permit(john, EmailDomainBlock) end diff --git a/spec/policies/follow_recommendation_policy_spec.rb b/spec/policies/follow_recommendation_policy_spec.rb new file mode 100644 index 000000000..01f4da0be --- /dev/null +++ b/spec/policies/follow_recommendation_policy_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'pundit/rspec' + +describe FollowRecommendationPolicy do + let(:policy) { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:john) { Fabricate(:account) } + + permissions :show?, :suppress?, :unsuppress? do + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, Tag) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, Tag) + end + end + end +end diff --git a/spec/policies/instance_policy_spec.rb b/spec/policies/instance_policy_spec.rb index f6f51af06..a0d9a008b 100644 --- a/spec/policies/instance_policy_spec.rb +++ b/spec/policies/instance_policy_spec.rb @@ -4,18 +4,19 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe InstancePolicy do - let(:subject) { described_class } + subject { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:john) { Fabricate(:account) } permissions :index?, :show?, :destroy? do - context 'admin' do + context 'when admin' do it 'permits' do expect(subject).to permit(admin, Instance) end end - context 'not admin' do + context 'when not admin' do it 'denies' do expect(subject).to_not permit(john, Instance) end diff --git a/spec/policies/invite_policy_spec.rb b/spec/policies/invite_policy_spec.rb index 01660322f..cbe3735d8 100644 --- a/spec/policies/invite_policy_spec.rb +++ b/spec/policies/invite_policy_spec.rb @@ -4,12 +4,13 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe InvitePolicy do - let(:subject) { described_class } + subject { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:john) { Fabricate(:user).account } permissions :index? do - context 'staff?' do + context 'when staff?' do it 'permits' do expect(subject).to permit(admin, Invite) end @@ -17,7 +18,7 @@ RSpec.describe InvitePolicy do end permissions :create? do - context 'has privilege' do + context 'with privilege' do before do UserRole.everyone.update(permissions: UserRole::FLAGS[:invite_users]) end @@ -27,7 +28,7 @@ RSpec.describe InvitePolicy do end end - context 'does not have privilege' do + context 'when does not have privilege' do before do UserRole.everyone.update(permissions: UserRole::Flags::NONE) end @@ -39,13 +40,13 @@ RSpec.describe InvitePolicy do end permissions :deactivate_all? do - context 'admin?' do + context 'when admin?' do it 'permits' do expect(subject).to permit(admin, Invite) end end - context 'not admin?' do + context 'when not admin?' do it 'denies' do expect(subject).to_not permit(john, Invite) end @@ -53,20 +54,20 @@ RSpec.describe InvitePolicy do end permissions :destroy? do - context 'owner?' do + context 'when owner?' do it 'permits' do expect(subject).to permit(john, Fabricate(:invite, user: john.user)) end end - context 'not owner?' do - context 'admin?' do + context 'when not owner?' do + context 'when admin?' do it 'permits' do expect(subject).to permit(admin, Fabricate(:invite)) end end - context 'not admin?' do + context 'when not admin?' do it 'denies' do expect(subject).to_not permit(john, Fabricate(:invite)) end diff --git a/spec/policies/ip_block_policy_spec.rb b/spec/policies/ip_block_policy_spec.rb new file mode 100644 index 000000000..3cfa85863 --- /dev/null +++ b/spec/policies/ip_block_policy_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'pundit/rspec' + +describe IpBlockPolicy do + let(:policy) { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:john) { Fabricate(:account) } + + permissions :index?, :show?, :create?, :update?, :destroy? do + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, Tag) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, Tag) + end + end + end +end diff --git a/spec/policies/preview_card_policy_spec.rb b/spec/policies/preview_card_policy_spec.rb new file mode 100644 index 000000000..d6675c5b3 --- /dev/null +++ b/spec/policies/preview_card_policy_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'pundit/rspec' + +describe PreviewCardPolicy do + let(:policy) { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:john) { Fabricate(:account) } + + permissions :index?, :review? do + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, Tag) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, Tag) + end + end + end +end diff --git a/spec/policies/preview_card_provider_policy_spec.rb b/spec/policies/preview_card_provider_policy_spec.rb new file mode 100644 index 000000000..8d3715de9 --- /dev/null +++ b/spec/policies/preview_card_provider_policy_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'pundit/rspec' + +describe PreviewCardProviderPolicy do + let(:policy) { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:john) { Fabricate(:account) } + + permissions :index?, :review? do + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, Tag) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, Tag) + end + end + end +end diff --git a/spec/policies/relay_policy_spec.rb b/spec/policies/relay_policy_spec.rb index 2c50ba1e9..29ba02c26 100644 --- a/spec/policies/relay_policy_spec.rb +++ b/spec/policies/relay_policy_spec.rb @@ -4,18 +4,19 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe RelayPolicy do - let(:subject) { described_class } + subject { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:john) { Fabricate(:account) } permissions :update? do - context 'admin?' do + context 'when admin?' do it 'permits' do expect(subject).to permit(admin, Relay) end end - context '!admin?' do + context 'with !admin?' do it 'denies' do expect(subject).to_not permit(john, Relay) end diff --git a/spec/policies/report_note_policy_spec.rb b/spec/policies/report_note_policy_spec.rb index 99f5ffb8e..b40a87888 100644 --- a/spec/policies/report_note_policy_spec.rb +++ b/spec/policies/report_note_policy_spec.rb @@ -4,18 +4,19 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe ReportNotePolicy do - let(:subject) { described_class } + subject { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:john) { Fabricate(:account) } permissions :create? do - context 'staff?' do + context 'when staff?' do it 'permits' do expect(subject).to permit(admin, ReportNote) end end - context '!staff?' do + context 'with !staff?' do it 'denies' do expect(subject).to_not permit(john, ReportNote) end @@ -23,26 +24,24 @@ RSpec.describe ReportNotePolicy do end permissions :destroy? do - context 'admin?' do + context 'when admin?' do it 'permit' do report_note = Fabricate(:report_note, account: john) expect(subject).to permit(admin, report_note) end end - context 'admin?' do - context 'owner?' do - it 'permit' do - report_note = Fabricate(:report_note, account: john) - expect(subject).to permit(john, report_note) - end + context 'when owner?' do + it 'permit' do + report_note = Fabricate(:report_note, account: john) + expect(subject).to permit(john, report_note) end + end - context '!owner?' do - it 'denies' do - report_note = Fabricate(:report_note) - expect(subject).to_not permit(john, report_note) - end + context 'with !owner?' do + it 'denies' do + report_note = Fabricate(:report_note) + expect(subject).to_not permit(john, report_note) end end end diff --git a/spec/policies/report_policy_spec.rb b/spec/policies/report_policy_spec.rb index 8b005d8dd..4fc417807 100644 --- a/spec/policies/report_policy_spec.rb +++ b/spec/policies/report_policy_spec.rb @@ -4,18 +4,19 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe ReportPolicy do - let(:subject) { described_class } + subject { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:john) { Fabricate(:account) } permissions :update?, :index?, :show? do - context 'staff?' do + context 'when staff?' do it 'permits' do expect(subject).to permit(admin, Report) end end - context '!staff?' do + context 'with !staff?' do it 'denies' do expect(subject).to_not permit(john, Report) end diff --git a/spec/policies/rule_policy_spec.rb b/spec/policies/rule_policy_spec.rb new file mode 100644 index 000000000..0e45f6df0 --- /dev/null +++ b/spec/policies/rule_policy_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'pundit/rspec' + +describe RulePolicy do + let(:policy) { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:john) { Fabricate(:account) } + + permissions :index?, :create?, :update?, :destroy? do + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, Tag) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, Tag) + end + end + end +end diff --git a/spec/policies/settings_policy_spec.rb b/spec/policies/settings_policy_spec.rb index e16ee51a4..4a9931490 100644 --- a/spec/policies/settings_policy_spec.rb +++ b/spec/policies/settings_policy_spec.rb @@ -4,18 +4,19 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe SettingsPolicy do - let(:subject) { described_class } + subject { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:john) { Fabricate(:account) } - permissions :update?, :show? do - context 'admin?' do + permissions :update?, :show?, :destroy? do + context 'when admin?' do it 'permits' do expect(subject).to permit(admin, Settings) end end - context '!admin?' do + context 'with !admin?' do it 'denies' do expect(subject).to_not permit(john, Settings) end diff --git a/spec/policies/software_update_policy_spec.rb b/spec/policies/software_update_policy_spec.rb new file mode 100644 index 000000000..e19ba6161 --- /dev/null +++ b/spec/policies/software_update_policy_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'pundit/rspec' + +RSpec.describe SoftwareUpdatePolicy do + subject { described_class } + + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Owner')).account } + let(:john) { Fabricate(:account) } + + permissions :index? do + context 'when owner' do + it 'permits' do + expect(subject).to permit(admin, SoftwareUpdate) + end + end + + context 'when not owner' do + it 'denies' do + expect(subject).to_not permit(john, SoftwareUpdate) + end + end + end +end diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb index 2afcfe96e..725bd0bbb 100644 --- a/spec/policies/status_policy_spec.rb +++ b/spec/policies/status_policy_spec.rb @@ -11,131 +11,151 @@ RSpec.describe StatusPolicy, type: :model do let(:bob) { Fabricate(:account, username: 'bob') } let(:status) { Fabricate(:status, account: alice) } - permissions :show?, :reblog? do - it 'grants access when no viewer' do - expect(subject).to permit(nil, status) - end + context 'with the permissions of show? and reblog?' do + permissions :show?, :reblog? do + it 'grants access when no viewer' do + expect(subject).to permit(nil, status) + end - it 'denies access when viewer is blocked' do - block = Fabricate(:block) - status.visibility = :private - status.account = block.target_account + it 'denies access when viewer is blocked' do + block = Fabricate(:block) + status.visibility = :private + status.account = block.target_account - expect(subject).to_not permit(block.account, status) + expect(subject).to_not permit(block.account, status) + end end end - permissions :show? do - it 'grants access when direct and account is viewer' do - status.visibility = :direct + context 'with the permission of show?' do + permissions :show? do + it 'grants access when direct and account is viewer' do + status.visibility = :direct - expect(subject).to permit(status.account, status) - end + expect(subject).to permit(status.account, status) + end - it 'grants access when direct and viewer is mentioned' do - status.visibility = :direct - status.mentions = [Fabricate(:mention, account: alice)] + it 'grants access when direct and viewer is mentioned' do + status.visibility = :direct + status.mentions = [Fabricate(:mention, account: alice)] - expect(subject).to permit(alice, status) - end + expect(subject).to permit(alice, status) + end - it 'denies access when direct and viewer is not mentioned' do - viewer = Fabricate(:account) - status.visibility = :direct + it 'grants access when direct and non-owner viewer is mentioned and mentions are loaded' do + status.visibility = :direct + status.mentions = [Fabricate(:mention, account: bob)] + status.mentions.load - expect(subject).to_not permit(viewer, status) - end + expect(subject).to permit(bob, status) + end - it 'grants access when private and account is viewer' do - status.visibility = :private + it 'denies access when direct and viewer is not mentioned' do + viewer = Fabricate(:account) + status.visibility = :direct - expect(subject).to permit(status.account, status) - end + expect(subject).to_not permit(viewer, status) + end - it 'grants access when private and account is following viewer' do - follow = Fabricate(:follow) - status.visibility = :private - status.account = follow.target_account + it 'grants access when private and account is viewer' do + status.visibility = :private - expect(subject).to permit(follow.account, status) - end + expect(subject).to permit(status.account, status) + end - it 'grants access when private and viewer is mentioned' do - status.visibility = :private - status.mentions = [Fabricate(:mention, account: alice)] + it 'grants access when private and account is following viewer' do + follow = Fabricate(:follow) + status.visibility = :private + status.account = follow.target_account - expect(subject).to permit(alice, status) - end + expect(subject).to permit(follow.account, status) + end - it 'denies access when private and viewer is not mentioned or followed' do - viewer = Fabricate(:account) - status.visibility = :private + it 'grants access when private and viewer is mentioned' do + status.visibility = :private + status.mentions = [Fabricate(:mention, account: alice)] - expect(subject).to_not permit(viewer, status) - end + expect(subject).to permit(alice, status) + end - it 'denies access when local-only and the viewer is not logged in' do - allow(status).to receive(:local_only?) { true } + it 'denies access when private and viewer is not mentioned or followed' do + viewer = Fabricate(:account) + status.visibility = :private - expect(subject).to_not permit(nil, status) - end + expect(subject).to_not permit(viewer, status) + end - it 'denies access when local-only and the viewer is from another domain' do - viewer = Fabricate(:account, domain: 'remote-domain') - allow(status).to receive(:local_only?) { true } - expect(subject).to_not permit(viewer, status) + it 'denies access when local-only and the viewer is not logged in' do + allow(status).to receive(:local_only?).and_return(true) + + expect(subject).to_not permit(nil, status) + end + + it 'denies access when local-only and the viewer is from another domain' do + viewer = Fabricate(:account, domain: 'remote-domain') + allow(status).to receive(:local_only?).and_return(true) + expect(subject).to_not permit(viewer, status) + end end end - permissions :reblog? do - it 'denies access when private' do - viewer = Fabricate(:account) - status.visibility = :private + context 'with the permission of reblog?' do + permissions :reblog? do + it 'denies access when private' do + viewer = Fabricate(:account) + status.visibility = :private - expect(subject).to_not permit(viewer, status) - end + expect(subject).to_not permit(viewer, status) + end - it 'denies access when direct' do - viewer = Fabricate(:account) - status.visibility = :direct + it 'denies access when direct' do + viewer = Fabricate(:account) + status.visibility = :direct - expect(subject).to_not permit(viewer, status) + expect(subject).to_not permit(viewer, status) + end end end - permissions :destroy?, :unreblog? do - it 'grants access when account is deleter' do - expect(subject).to permit(status.account, status) - end + context 'with the permissions of destroy? and unreblog?' do + permissions :destroy?, :unreblog? do + it 'grants access when account is deleter' do + expect(subject).to permit(status.account, status) + end - it 'denies access when account is not deleter' do - expect(subject).to_not permit(bob, status) - end + it 'denies access when account is not deleter' do + expect(subject).to_not permit(bob, status) + end - it 'denies access when no deleter' do - expect(subject).to_not permit(nil, status) + it 'denies access when no deleter' do + expect(subject).to_not permit(nil, status) + end end end - permissions :favourite? do - it 'grants access when viewer is not blocked' do - follow = Fabricate(:follow) - status.account = follow.target_account + context 'with the permission of favourite?' do + permissions :favourite? do + it 'grants access when viewer is not blocked' do + follow = Fabricate(:follow) + status.account = follow.target_account - expect(subject).to permit(follow.account, status) - end + expect(subject).to permit(follow.account, status) + end - it 'denies when viewer is blocked' do - block = Fabricate(:block) - status.account = block.target_account + it 'denies when viewer is blocked' do + block = Fabricate(:block) + status.account = block.target_account - expect(subject).to_not permit(block.account, status) + expect(subject).to_not permit(block.account, status) + end end end - permissions :update? do - it 'grants access if owner' do - expect(subject).to permit(status.account, status) + context 'with the permission of update?' do + permissions :update? do + it 'grants access if owner' do + expect(subject).to permit(status.account, status) + end end end end diff --git a/spec/policies/tag_policy_spec.rb b/spec/policies/tag_policy_spec.rb index 9be7140fc..35da3cc62 100644 --- a/spec/policies/tag_policy_spec.rb +++ b/spec/policies/tag_policy_spec.rb @@ -4,18 +4,19 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe TagPolicy do - let(:subject) { described_class } + subject { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:john) { Fabricate(:account) } - permissions :index?, :show?, :update? do - context 'staff?' do + permissions :index?, :show?, :update?, :review? do + context 'when staff?' do it 'permits' do expect(subject).to permit(admin, Tag) end end - context '!staff?' do + context 'with !staff?' do it 'denies' do expect(subject).to_not permit(john, Tag) end diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb index ff0916674..fa476a9fc 100644 --- a/spec/policies/user_policy_spec.rb +++ b/spec/policies/user_policy_spec.rb @@ -4,26 +4,27 @@ require 'rails_helper' require 'pundit/rspec' RSpec.describe UserPolicy do - let(:subject) { described_class } + subject { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } let(:john) { Fabricate(:account) } permissions :reset_password?, :change_email? do - context 'staff?' do - context '!record.staff?' do + context 'when staff?' do + context 'with !record.staff?' do it 'permits' do expect(subject).to permit(admin, john.user) end end - context 'record.staff?' do + context 'when record.staff?' do it 'denies' do expect(subject).to_not permit(admin, admin.user) end end end - context '!staff?' do + context 'with !staff?' do it 'denies' do expect(subject).to_not permit(john, User) end @@ -31,21 +32,21 @@ RSpec.describe UserPolicy do end permissions :disable_2fa? do - context 'admin?' do - context '!record.staff?' do + context 'when admin?' do + context 'with !record.staff?' do it 'permits' do expect(subject).to permit(admin, john.user) end end - context 'record.staff?' do + context 'when record.staff?' do it 'denies' do expect(subject).to_not permit(admin, admin.user) end end end - context '!admin?' do + context 'with !admin?' do it 'denies' do expect(subject).to_not permit(john, User) end @@ -53,15 +54,15 @@ RSpec.describe UserPolicy do end permissions :confirm? do - context 'staff?' do - context '!record.confirmed?' do + context 'when staff?' do + context 'with !record.confirmed?' do it 'permits' do john.user.update(confirmed_at: nil) expect(subject).to permit(admin, john.user) end end - context 'record.confirmed?' do + context 'when record.confirmed?' do it 'denies' do john.user.confirm! expect(subject).to_not permit(admin, john.user) @@ -69,7 +70,7 @@ RSpec.describe UserPolicy do end end - context '!staff?' do + context 'with !staff?' do it 'denies' do expect(subject).to_not permit(john, User) end @@ -77,13 +78,13 @@ RSpec.describe UserPolicy do end permissions :enable? do - context 'staff?' do + context 'when staff?' do it 'permits' do expect(subject).to permit(admin, User) end end - context '!staff?' do + context 'with !staff?' do it 'denies' do expect(subject).to_not permit(john, User) end @@ -91,21 +92,21 @@ RSpec.describe UserPolicy do end permissions :disable? do - context 'staff?' do - context '!record.admin?' do + context 'when staff?' do + context 'with !record.admin?' do it 'permits' do expect(subject).to permit(admin, john.user) end end - context 'record.admin?' do + context 'when record.admin?' do it 'denies' do expect(subject).to_not permit(admin, admin.user) end end end - context '!staff?' do + context 'with !staff?' do it 'denies' do expect(subject).to_not permit(john, User) end diff --git a/spec/policies/webhook_policy_spec.rb b/spec/policies/webhook_policy_spec.rb new file mode 100644 index 000000000..909311461 --- /dev/null +++ b/spec/policies/webhook_policy_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'pundit/rspec' + +describe WebhookPolicy do + let(:policy) { described_class } + let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:john) { Fabricate(:account) } + + permissions :index?, :create? do + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, Webhook) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, Webhook) + end + end + end + + permissions :show?, :update?, :enable?, :disable?, :rotate_secret?, :destroy? do + let(:webhook) { Fabricate(:webhook, events: ['account.created', 'report.created']) } + + context 'with an admin' do + it 'permits' do + expect(policy).to permit(admin, webhook) + end + end + + context 'with a non-admin' do + it 'denies' do + expect(policy).to_not permit(john, webhook) + end + end + end +end diff --git a/spec/presenters/account_relationships_presenter_spec.rb b/spec/presenters/account_relationships_presenter_spec.rb index edfbbb354..282cae4f0 100644 --- a/spec/presenters/account_relationships_presenter_spec.rb +++ b/spec/presenters/account_relationships_presenter_spec.rb @@ -5,33 +5,61 @@ require 'rails_helper' RSpec.describe AccountRelationshipsPresenter do describe '.initialize' do before do - allow(Account).to receive(:following_map).with(account_ids, current_account_id).and_return(default_map) - allow(Account).to receive(:followed_by_map).with(account_ids, current_account_id).and_return(default_map) - allow(Account).to receive(:blocking_map).with(account_ids, current_account_id).and_return(default_map) - allow(Account).to receive(:muting_map).with(account_ids, current_account_id).and_return(default_map) - allow(Account).to receive(:requested_map).with(account_ids, current_account_id).and_return(default_map) - allow(Account).to receive(:domain_blocking_map).with(account_ids, current_account_id).and_return(default_map) + allow(Account).to receive(:following_map).with(accounts.pluck(:id), current_account_id).and_return(default_map) + allow(Account).to receive(:followed_by_map).with(accounts.pluck(:id), current_account_id).and_return(default_map) + allow(Account).to receive(:blocking_map).with(accounts.pluck(:id), current_account_id).and_return(default_map) + allow(Account).to receive(:muting_map).with(accounts.pluck(:id), current_account_id).and_return(default_map) + allow(Account).to receive(:requested_map).with(accounts.pluck(:id), current_account_id).and_return(default_map) + allow(Account).to receive(:requested_by_map).with(accounts.pluck(:id), current_account_id).and_return(default_map) end - let(:presenter) { AccountRelationshipsPresenter.new(account_ids, current_account_id, **options) } + let(:presenter) { described_class.new(accounts, current_account_id, **options) } let(:current_account_id) { Fabricate(:account).id } - let(:account_ids) { [Fabricate(:account).id] } - let(:default_map) { { 1 => true } } + let(:accounts) { [Fabricate(:account)] } + let(:default_map) { { accounts[0].id => true } } - context 'options are not set' do + context 'when options are not set' do let(:options) { {} } it 'sets default maps' do - expect(presenter.following).to eq default_map - expect(presenter.followed_by).to eq default_map - expect(presenter.blocking).to eq default_map - expect(presenter.muting).to eq default_map - expect(presenter.requested).to eq default_map - expect(presenter.domain_blocking).to eq default_map + expect(presenter).to have_attributes( + following: default_map, + followed_by: default_map, + blocking: default_map, + muting: default_map, + requested: default_map, + domain_blocking: { accounts[0].id => nil } + ) end end - context 'options[:following_map] is set' do + context 'with a warm cache' do + let(:options) { {} } + + before do + described_class.new(accounts, current_account_id, **options) + + allow(Account).to receive(:following_map).with([], current_account_id).and_return({}) + allow(Account).to receive(:followed_by_map).with([], current_account_id).and_return({}) + allow(Account).to receive(:blocking_map).with([], current_account_id).and_return({}) + allow(Account).to receive(:muting_map).with([], current_account_id).and_return({}) + allow(Account).to receive(:requested_map).with([], current_account_id).and_return({}) + allow(Account).to receive(:requested_by_map).with([], current_account_id).and_return({}) + end + + it 'sets returns expected values' do + expect(presenter).to have_attributes( + following: default_map, + followed_by: default_map, + blocking: default_map, + muting: default_map, + requested: default_map, + domain_blocking: { accounts[0].id => nil } + ) + end + end + + context 'when options[:following_map] is set' do let(:options) { { following_map: { 2 => true } } } it 'sets @following merged with default_map and options[:following_map]' do @@ -39,7 +67,7 @@ RSpec.describe AccountRelationshipsPresenter do end end - context 'options[:followed_by_map] is set' do + context 'when options[:followed_by_map] is set' do let(:options) { { followed_by_map: { 3 => true } } } it 'sets @followed_by merged with default_map and options[:followed_by_map]' do @@ -47,7 +75,7 @@ RSpec.describe AccountRelationshipsPresenter do end end - context 'options[:blocking_map] is set' do + context 'when options[:blocking_map] is set' do let(:options) { { blocking_map: { 4 => true } } } it 'sets @blocking merged with default_map and options[:blocking_map]' do @@ -55,7 +83,7 @@ RSpec.describe AccountRelationshipsPresenter do end end - context 'options[:muting_map] is set' do + context 'when options[:muting_map] is set' do let(:options) { { muting_map: { 5 => true } } } it 'sets @muting merged with default_map and options[:muting_map]' do @@ -63,7 +91,7 @@ RSpec.describe AccountRelationshipsPresenter do end end - context 'options[:requested_map] is set' do + context 'when options[:requested_map] is set' do let(:options) { { requested_map: { 6 => true } } } it 'sets @requested merged with default_map and options[:requested_map]' do @@ -71,11 +99,19 @@ RSpec.describe AccountRelationshipsPresenter do end end - context 'options[:domain_blocking_map] is set' do + context 'when options[:requested_by_map] is set' do + let(:options) { { requested_by_map: { 6 => true } } } + + it 'sets @requested merged with default_map and options[:requested_by_map]' do + expect(presenter.requested_by).to eq default_map.merge(options[:requested_by_map]) + end + end + + context 'when options[:domain_blocking_map] is set' do let(:options) { { domain_blocking_map: { 7 => true } } } it 'sets @domain_blocking merged with default_map and options[:domain_blocking_map]' do - expect(presenter.domain_blocking).to eq default_map.merge(options[:domain_blocking_map]) + expect(presenter.domain_blocking).to eq({ accounts[0].id => nil }.merge(options[:domain_blocking_map])) end end end diff --git a/spec/presenters/familiar_followers_presenter_spec.rb b/spec/presenters/familiar_followers_presenter_spec.rb index 17be4b971..c21ffd36e 100644 --- a/spec/presenters/familiar_followers_presenter_spec.rb +++ b/spec/presenters/familiar_followers_presenter_spec.rb @@ -4,12 +4,12 @@ require 'rails_helper' RSpec.describe FamiliarFollowersPresenter do describe '#accounts' do + subject { described_class.new(requested_accounts, account.id) } + let(:account) { Fabricate(:account) } let(:familiar_follower) { Fabricate(:account) } let(:requested_accounts) { Fabricate.times(2, :account) } - subject { described_class.new(requested_accounts, account.id) } - before do familiar_follower.follow!(requested_accounts.first) account.follow!(familiar_follower) @@ -24,7 +24,7 @@ RSpec.describe FamiliarFollowersPresenter do expect(result).to_not be_nil expect(result.id).to eq requested_accounts.first.id - expect(result.accounts).to match_array([familiar_follower]) + expect(result.accounts).to contain_exactly(familiar_follower) end context 'when requested account hides followers' do diff --git a/spec/presenters/instance_presenter_spec.rb b/spec/presenters/instance_presenter_spec.rb index 659c2cc2f..2a1d668ce 100644 --- a/spec/presenters/instance_presenter_spec.rb +++ b/spec/presenters/instance_presenter_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require 'rails_helper' describe InstancePresenter do - let(:instance_presenter) { InstancePresenter.new } + let(:instance_presenter) { described_class.new } describe '#description' do around do |example| @@ -10,9 +12,9 @@ describe InstancePresenter do Setting.site_short_description = site_description end - it "delegates site_description to Setting" do - Setting.site_short_description = "Site desc" - expect(instance_presenter.description).to eq "Site desc" + it 'delegates site_description to Setting' do + Setting.site_short_description = 'Site desc' + expect(instance_presenter.description).to eq 'Site desc' end end @@ -23,9 +25,9 @@ describe InstancePresenter do Setting.site_extended_description = site_extended_description end - it "delegates site_extended_description to Setting" do - Setting.site_extended_description = "Extended desc" - expect(instance_presenter.extended_description).to eq "Extended desc" + it 'delegates site_extended_description to Setting' do + Setting.site_extended_description = 'Extended desc' + expect(instance_presenter.extended_description).to eq 'Extended desc' end end @@ -36,9 +38,9 @@ describe InstancePresenter do Setting.site_contact_email = site_contact_email end - it "delegates contact_email to Setting" do - Setting.site_contact_email = "admin@example.com" - expect(instance_presenter.contact.email).to eq "admin@example.com" + it 'delegates contact_email to Setting' do + Setting.site_contact_email = 'admin@example.com' + expect(instance_presenter.contact.email).to eq 'admin@example.com' end end @@ -49,15 +51,15 @@ describe InstancePresenter do Setting.site_contact_username = site_contact_username end - it "returns the account for the site contact username" do - Setting.site_contact_username = "aaa" - account = Fabricate(:account, username: "aaa") + it 'returns the account for the site contact username' do + Setting.site_contact_username = 'aaa' + account = Fabricate(:account, username: 'aaa') expect(instance_presenter.contact.account).to eq(account) end end describe '#user_count' do - it "returns the number of site users" do + it 'returns the number of site users' do Rails.cache.write 'user_count', 123 expect(instance_presenter.user_count).to eq(123) @@ -65,7 +67,7 @@ describe InstancePresenter do end describe '#status_count' do - it "returns the number of local statuses" do + it 'returns the number of local statuses' do Rails.cache.write 'local_status_count', 234 expect(instance_presenter.status_count).to eq(234) @@ -73,7 +75,7 @@ describe InstancePresenter do end describe '#domain_count' do - it "returns the number of known domains" do + it 'returns the number of known domains' do Rails.cache.write 'distinct_domain_count', 345 expect(instance_presenter.domain_count).to eq(345) @@ -87,8 +89,28 @@ describe InstancePresenter do end describe '#source_url' do - it 'returns "https://github.com/mastodon/mastodon"' do - expect(instance_presenter.source_url).to eq('https://github.com/mastodon/mastodon') + context 'with the GITHUB_REPOSITORY env variable set' do + around do |example| + ClimateControl.modify GITHUB_REPOSITORY: 'other/repo' do + example.run + end + end + + it 'uses the env variable to build a repo URL' do + expect(instance_presenter.source_url).to eq('https://github.com/other/repo') + end + end + + context 'without the GITHUB_REPOSITORY env variable set' do + around do |example| + ClimateControl.modify GITHUB_REPOSITORY: nil do + example.run + end + end + + it 'defaults to the core mastodon repo URL' do + expect(instance_presenter.source_url).to eq('https://github.com/mastodon/mastodon') + end end end diff --git a/spec/presenters/status_relationships_presenter_spec.rb b/spec/presenters/status_relationships_presenter_spec.rb index eaab922fd..7746c8cd7 100644 --- a/spec/presenters/status_relationships_presenter_spec.rb +++ b/spec/presenters/status_relationships_presenter_spec.rb @@ -12,13 +12,13 @@ RSpec.describe StatusRelationshipsPresenter do allow(Status).to receive(:pins_map).with(anything, current_account_id).and_return(default_map) end - let(:presenter) { StatusRelationshipsPresenter.new(statuses, current_account_id, **options) } + let(:presenter) { described_class.new(statuses, current_account_id, **options) } let(:current_account_id) { Fabricate(:account).id } let(:statuses) { [Fabricate(:status)] } - let(:status_ids) { statuses.map(&:id) + statuses.map(&:reblog_of_id).compact } + let(:status_ids) { statuses.map(&:id) + statuses.filter_map(&:reblog_of_id) } let(:default_map) { { 1 => true } } - context 'options are not set' do + context 'when options are not set' do let(:options) { {} } it 'sets default maps' do @@ -30,7 +30,7 @@ RSpec.describe StatusRelationshipsPresenter do end end - context 'options[:reblogs_map] is set' do + context 'when options[:reblogs_map] is set' do let(:options) { { reblogs_map: { 2 => true } } } it 'sets @reblogs_map merged with default_map and options[:reblogs_map]' do @@ -38,7 +38,7 @@ RSpec.describe StatusRelationshipsPresenter do end end - context 'options[:favourites_map] is set' do + context 'when options[:favourites_map] is set' do let(:options) { { favourites_map: { 3 => true } } } it 'sets @favourites_map merged with default_map and options[:favourites_map]' do @@ -46,7 +46,7 @@ RSpec.describe StatusRelationshipsPresenter do end end - context 'options[:bookmarks_map] is set' do + context 'when options[:bookmarks_map] is set' do let(:options) { { bookmarks_map: { 4 => true } } } it 'sets @bookmarks_map merged with default_map and options[:bookmarks_map]' do @@ -54,7 +54,7 @@ RSpec.describe StatusRelationshipsPresenter do end end - context 'options[:mutes_map] is set' do + context 'when options[:mutes_map] is set' do let(:options) { { mutes_map: { 5 => true } } } it 'sets @mutes_map merged with default_map and options[:mutes_map]' do @@ -62,7 +62,7 @@ RSpec.describe StatusRelationshipsPresenter do end end - context 'options[:pins_map] is set' do + context 'when options[:pins_map] is set' do let(:options) { { pins_map: { 6 => true } } } it 'sets @pins_map merged with default_map and options[:pins_map]' do diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 02827a388..17067c58f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -1,21 +1,43 @@ -ENV['RAILS_ENV'] ||= 'test' -require File.expand_path('../../config/environment', __FILE__) +# frozen_string_literal: true -abort("The Rails environment is running in production mode!") if Rails.env.production? +ENV['RAILS_ENV'] ||= 'test' + +# This needs to be defined before Rails is initialized +RUN_SYSTEM_SPECS = ENV.fetch('RUN_SYSTEM_SPECS', false) +RUN_SEARCH_SPECS = ENV.fetch('RUN_SEARCH_SPECS', false) + +if RUN_SYSTEM_SPECS + STREAMING_PORT = ENV.fetch('TEST_STREAMING_PORT', '4020') + ENV['STREAMING_API_BASE_URL'] = "http://localhost:#{STREAMING_PORT}" +end + +if RUN_SEARCH_SPECS + # Include any configuration or setups specific to search tests here +end + +require File.expand_path('../config/environment', __dir__) + +abort('The Rails environment is running in production mode!') if Rails.env.production? require 'spec_helper' require 'rspec/rails' require 'webmock/rspec' require 'paperclip/matchers' require 'capybara/rspec' +require 'chewy/rspec' -Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } +Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f } ActiveRecord::Migration.maintain_test_schema! -WebMock.disable_net_connect!(allow: Chewy.settings[:host]) +WebMock.disable_net_connect!(allow: Chewy.settings[:host], allow_localhost: RUN_SYSTEM_SPECS) Sidekiq::Testing.inline! Sidekiq.logger = nil +# System tests config +DatabaseCleaner.strategy = [:deletion] +streaming_server_manager = StreamingServerManager.new +search_data_manager = SearchDataManager.new + Devise::Test::ControllerHelpers.module_eval do alias_method :original_sign_in, :sign_in @@ -32,22 +54,64 @@ Devise::Test::ControllerHelpers.module_eval do end end +module SignedRequestHelpers + def get(path, headers: nil, sign_with: nil, **args) + return super path, headers: headers, **args if sign_with.nil? + + headers ||= {} + headers['Date'] = Time.now.utc.httpdate + headers['Host'] = ENV.fetch('LOCAL_DOMAIN') + signed_headers = headers.merge('(request-target)' => "get #{path}").slice('(request-target)', 'Host', 'Date') + + key_id = ActivityPub::TagManager.instance.key_uri_for(sign_with) + keypair = sign_with.keypair + signed_string = signed_headers.map { |key, value| "#{key.downcase}: #{value}" }.join("\n") + signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string)) + + headers['Signature'] = "keyId=\"#{key_id}\",algorithm=\"rsa-sha256\",headers=\"#{signed_headers.keys.join(' ').downcase}\",signature=\"#{signature}\"" + + super path, headers: headers, **args + end +end + RSpec.configure do |config| - config.fixture_path = "#{::Rails.root}/spec/fixtures" + # This is set before running spec:system, see lib/tasks/tests.rake + config.filter_run_excluding type: lambda { |type| + case type + when :system + !RUN_SYSTEM_SPECS + when :search + !RUN_SEARCH_SPECS + end + } + config.fixture_path = Rails.root.join('spec', 'fixtures') config.use_transactional_fixtures = true config.order = 'random' config.infer_spec_type_from_file_location! config.filter_rails_from_backtrace! + config.define_derived_metadata(file_path: Regexp.new('spec/lib/mastodon/cli')) do |metadata| + metadata[:type] = :cli + end + config.include Devise::Test::ControllerHelpers, type: :controller + config.include Devise::Test::ControllerHelpers, type: :helper config.include Devise::Test::ControllerHelpers, type: :view + config.include Devise::Test::IntegrationHelpers, type: :feature + config.include Devise::Test::IntegrationHelpers, type: :request config.include Paperclip::Shoulda::Matchers config.include ActiveSupport::Testing::TimeHelpers + config.include Chewy::Rspec::Helpers config.include Redisable + config.include SignedRequestHelpers, type: :request + + config.before :each, type: :cli do + stub_stdout + stub_reset_connection_pools + end config.before :each, type: :feature do - https = ENV['LOCAL_HTTPS'] == 'true' - Capybara.app_host = "http#{https ? 's' : ''}://#{ENV.fetch('LOCAL_DOMAIN')}" + Capybara.current_driver = :rack_test end config.before :each, type: :controller do @@ -58,10 +122,73 @@ RSpec.configure do |config| stub_jsonld_contexts! end + config.before :suite do + if RUN_SYSTEM_SPECS + Webpacker.compile + streaming_server_manager.start(port: STREAMING_PORT) + end + + if RUN_SEARCH_SPECS + Chewy.strategy(:urgent) + search_data_manager.prepare_test_data + end + end + + config.after :suite do + streaming_server_manager.stop + + search_data_manager.cleanup_test_data if RUN_SEARCH_SPECS + end + + config.around :each, type: :system do |example| + # driven_by :selenium, using: :chrome, screen_size: [1600, 1200] + driven_by :selenium, using: :headless_chrome, screen_size: [1600, 1200] + + # The streaming server needs access to the database + # but with use_transactional_tests every transaction + # is rolled-back, so the streaming server never sees the data + # So we disable this feature for system tests, and use DatabaseCleaner to clean + # the database tables between each test + self.use_transactional_tests = false + + DatabaseCleaner.cleaning do + # NOTE: we switched registrations mode to closed by default, but the specs + # very heavily rely on having it enabled by default, as it relies on users + # being approved by default except in select cases where explicitly testing + # other registration modes + # Also needs to be set per-example here because of the database cleaner. + Setting.registrations_mode = 'open' + + example.run + end + + self.use_transactional_tests = true + end + + config.around :each, type: :search do |example| + search_data_manager.populate_indexes + example.run + search_data_manager.remove_indexes + end + + config.before(:each) do |example| + unless example.metadata[:paperclip_processing] + allow_any_instance_of(Paperclip::Attachment).to receive(:post_process).and_return(true) # rubocop:disable RSpec/AnyInstance + end + end + config.after :each do Rails.cache.clear redis.del(redis.keys) end + + # Assign types based on dir name for non-inferred types + config.define_derived_metadata(file_path: %r{/spec/}) do |metadata| + unless metadata.key?(:type) + match = metadata[:location].match(%r{/spec/([^/]+)/}) + metadata[:type] = match[1].singularize.to_sym + end + end end RSpec::Sidekiq.configure do |config| @@ -71,11 +198,26 @@ end RSpec::Matchers.define_negated_matcher :not_change, :change def request_fixture(name) - File.read(Rails.root.join('spec', 'fixtures', 'requests', name)) + Rails.root.join('spec', 'fixtures', 'requests', name).read end def attachment_fixture(name) - File.open(Rails.root.join('spec', 'fixtures', 'files', name)) + Rails.root.join('spec', 'fixtures', 'files', name).open +end + +def stub_stdout + # TODO: Is there a bettery way to: + # - Avoid CLI command output being printed out + # - Allow rspec to assert things against STDOUT + # - Avoid disabling stdout for other desirable output (deprecation warnings, for example) + allow($stdout).to receive(:write) +end + +def stub_reset_connection_pools + # TODO: Is there a better way to correctly run specs without stubbing this? + # (Avoids reset_connection_pools! in test env) + allow(ActiveRecord::Base).to receive(:establish_connection) + allow(RedisConfiguration).to receive(:establish_pool) end def stub_jsonld_contexts! diff --git a/spec/requests/anonymous_cookies_spec.rb b/spec/requests/anonymous_cookies_spec.rb new file mode 100644 index 000000000..427f54e44 --- /dev/null +++ b/spec/requests/anonymous_cookies_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'rails_helper' + +context 'when visited anonymously' do + around do |example| + old = ActionController::Base.allow_forgery_protection + ActionController::Base.allow_forgery_protection = true + + example.run + + ActionController::Base.allow_forgery_protection = old + end + + describe 'account pages' do + it 'do not set cookies' do + alice = Fabricate(:account, username: 'alice', display_name: 'Alice') + _status = Fabricate(:status, account: alice, text: 'Hello World') + + get '/@alice' + + expect(response.cookies).to be_empty + end + end + + describe 'status pages' do + it 'do not set cookies' do + alice = Fabricate(:account, username: 'alice', display_name: 'Alice') + status = Fabricate(:status, account: alice, text: 'Hello World') + + get short_account_status_url(alice, status) + + expect(response.cookies).to be_empty + end + end + + describe 'the /about page' do + it 'does not set cookies' do + get '/about' + + expect(response.cookies).to be_empty + end + end +end diff --git a/spec/requests/api/v1/accounts/credentials_spec.rb b/spec/requests/api/v1/accounts/credentials_spec.rb new file mode 100644 index 000000000..b13e79b12 --- /dev/null +++ b/spec/requests/api/v1/accounts/credentials_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'credentials API' do + let(:user) { Fabricate(:user, account_attributes: { discoverable: false, locked: true, indexable: false }) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:accounts write:accounts' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/accounts/verify_credentials' do + subject do + get '/api/v1/accounts/verify_credentials', headers: headers + end + + it_behaves_like 'forbidden for wrong scope', 'write write:accounts' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the expected content' do + subject + + expect(body_as_json).to include({ + source: hash_including({ + discoverable: false, + indexable: false, + }), + locked: true, + }) + end + end + + describe 'POST /api/v1/accounts/update_credentials' do + subject do + patch '/api/v1/accounts/update_credentials', headers: headers, params: params + end + + let(:params) { { discoverable: true, locked: false, indexable: true } } + + it_behaves_like 'forbidden for wrong scope', 'read read:accounts' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns JSON with updated attributes' do + subject + + expect(body_as_json).to include({ + source: hash_including({ + discoverable: true, + indexable: true, + }), + locked: false, + }) + end + end +end diff --git a/spec/requests/api/v1/accounts/featured_tags_spec.rb b/spec/requests/api/v1/accounts/featured_tags_spec.rb new file mode 100644 index 000000000..bae7d448b --- /dev/null +++ b/spec/requests/api/v1/accounts/featured_tags_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'account featured tags API' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:accounts' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + let(:account) { Fabricate(:account) } + + describe 'GET /api/v1/accounts/:id/featured_tags' do + subject do + get "/api/v1/accounts/#{account.id}/featured_tags", headers: headers + end + + before do + account.featured_tags.create!(name: 'foo') + account.featured_tags.create!(name: 'bar') + end + + it 'returns the expected tags', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to contain_exactly(a_hash_including({ + name: 'bar', + url: "https://cb6e6126.ngrok.io/@#{account.username}/tagged/bar", + }), a_hash_including({ + name: 'foo', + url: "https://cb6e6126.ngrok.io/@#{account.username}/tagged/foo", + })) + end + + context 'when the account is remote' do + it 'returns the expected tags', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to contain_exactly(a_hash_including({ + name: 'bar', + url: "https://cb6e6126.ngrok.io/@#{account.pretty_acct}/tagged/bar", + }), a_hash_including({ + name: 'foo', + url: "https://cb6e6126.ngrok.io/@#{account.pretty_acct}/tagged/foo", + })) + end + end + end +end diff --git a/spec/requests/api/v1/accounts_show_spec.rb b/spec/requests/api/v1/accounts_show_spec.rb new file mode 100644 index 000000000..ee6e925aa --- /dev/null +++ b/spec/requests/api/v1/accounts_show_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'GET /api/v1/accounts/{account_id}' do + it 'returns account entity as 200 OK' do + account = Fabricate(:account) + + get "/api/v1/accounts/#{account.id}" + + aggregate_failures do + expect(response).to have_http_status(200) + expect(body_as_json[:id]).to eq(account.id.to_s) + end + end + + it 'returns 404 if account not found' do + get '/api/v1/accounts/1' + + aggregate_failures do + expect(response).to have_http_status(404) + expect(body_as_json[:error]).to eq('Record not found') + end + end + + context 'when with token' do + it 'returns account entity as 200 OK if token is valid' do + account = Fabricate(:account) + user = Fabricate(:user, account: account) + token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts').token + + get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" } + + aggregate_failures do + expect(response).to have_http_status(200) + expect(body_as_json[:id]).to eq(account.id.to_s) + end + end + + it 'returns 403 if scope of token is invalid' do + account = Fabricate(:account) + user = Fabricate(:user, account: account) + token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:statuses').token + + get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" } + + aggregate_failures do + expect(response).to have_http_status(403) + expect(body_as_json[:error]).to eq('This action is outside the authorized scopes') + end + end + end +end diff --git a/spec/requests/api/v1/admin/account_actions_spec.rb b/spec/requests/api/v1/admin/account_actions_spec.rb new file mode 100644 index 000000000..9295d262d --- /dev/null +++ b/spec/requests/api/v1/admin/account_actions_spec.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Account actions' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:write admin:write:accounts' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + let(:mailer) { instance_double(ActionMailer::MessageDelivery, deliver_later!: nil) } + + before do + allow(UserMailer).to receive(:warning).with(target_account.user, anything).and_return(mailer) + end + + shared_examples 'a successful notification delivery' do + it 'notifies the user about the action taken' do + subject + + expect(UserMailer).to have_received(:warning).with(target_account.user, anything).once + expect(mailer).to have_received(:deliver_later!).once + end + end + + shared_examples 'a successful logged action' do |action_type, target_type| + it 'logs action' do + subject + + log_item = Admin::ActionLog.last + + expect(log_item).to be_present + expect(log_item.action).to eq(action_type) + expect(log_item.account_id).to eq(user.account_id) + expect(log_item.target_id).to eq(target_type == :user ? target_account.user.id : target_account.id) + end + end + + describe 'POST /api/v1/admin/accounts/:id/action' do + subject do + post "/api/v1/admin/accounts/#{target_account.id}/action", headers: headers, params: params + end + + let(:target_account) { Fabricate(:account) } + + context 'with type of disable' do + let(:params) { { type: 'disable' } } + + it_behaves_like 'forbidden for wrong scope', 'admin:read admin:read:accounts' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'a successful notification delivery' + it_behaves_like 'a successful logged action', :disable, :user + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'disables the target account' do + expect { subject }.to change { target_account.reload.user_disabled? }.from(false).to(true) + end + end + + context 'with type of sensitive' do + let(:params) { { type: 'sensitive' } } + + it_behaves_like 'forbidden for wrong scope', 'admin:read admin:read:accounts' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'a successful notification delivery' + it_behaves_like 'a successful logged action', :sensitive, :account + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'marks the target account as sensitive' do + expect { subject }.to change { target_account.reload.sensitized? }.from(false).to(true) + end + end + + context 'with type of silence' do + let(:params) { { type: 'silence' } } + + it_behaves_like 'forbidden for wrong scope', 'admin:read admin:read:accounts' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'a successful notification delivery' + it_behaves_like 'a successful logged action', :silence, :account + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'marks the target account as silenced' do + expect { subject }.to change { target_account.reload.silenced? }.from(false).to(true) + end + end + + context 'with type of suspend' do + let(:params) { { type: 'suspend' } } + + it_behaves_like 'forbidden for wrong scope', 'admin:read admin:read:accounts' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'a successful notification delivery' + it_behaves_like 'a successful logged action', :suspend, :account + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'marks the target account as suspended' do + expect { subject }.to change { target_account.reload.suspended? }.from(false).to(true) + end + end + + context 'with type of none' do + let(:params) { { type: 'none' } } + + it_behaves_like 'a successful notification delivery' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end + + context 'with no type' do + let(:params) { {} } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'with invalid type' do + let(:params) { { type: 'invalid' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end +end diff --git a/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb new file mode 100644 index 000000000..4382cb84e --- /dev/null +++ b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb @@ -0,0 +1,285 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Canonical Email Blocks' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'admin:read:canonical_email_blocks admin:write:canonical_email_blocks' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/admin/canonical_email_blocks' do + subject do + get '/api/v1/admin/canonical_email_blocks', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + context 'when there is no canonical email block' do + it 'returns an empty list' do + subject + + expect(body_as_json).to be_empty + end + end + + context 'when there are canonical email blocks' do + let!(:canonical_email_blocks) { Fabricate.times(5, :canonical_email_block) } + let(:expected_email_hashes) { canonical_email_blocks.pluck(:canonical_email_hash) } + + it 'returns the correct canonical email hashes' do + subject + + expect(body_as_json.pluck(:canonical_email_hash)).to match_array(expected_email_hashes) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of canonical email blocks' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + end + + context 'with since_id param' do + let(:params) { { since_id: canonical_email_blocks[1].id } } + + it 'returns only the canonical email blocks after since_id' do + subject + + canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s) + + expect(body_as_json.pluck(:id)).to match_array(canonical_email_blocks_ids[2..]) + end + end + + context 'with max_id param' do + let(:params) { { max_id: canonical_email_blocks[3].id } } + + it 'returns only the canonical email blocks before max_id' do + subject + + canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s) + + expect(body_as_json.pluck(:id)).to match_array(canonical_email_blocks_ids[..2]) + end + end + end + end + + describe 'GET /api/v1/admin/canonical_email_blocks/:id' do + subject do + get "/api/v1/admin/canonical_email_blocks/#{canonical_email_block.id}", headers: headers + end + + let!(:canonical_email_block) { Fabricate(:canonical_email_block) } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + context 'when the requested canonical email block exists' do + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the requested canonical email block data correctly' do + subject + + json = body_as_json + + expect(json[:id]).to eq(canonical_email_block.id.to_s) + expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) + end + end + + context 'when the requested canonical block does not exist' do + it 'returns http not found' do + get '/api/v1/admin/canonical_email_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/canonical_email_blocks/test' do + subject do + post '/api/v1/admin/canonical_email_blocks/test', headers: headers, params: params + end + + let(:params) { { email: 'email@example.com' } } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + context 'when the required email param is not provided' do + let(:params) { {} } + + it 'returns http bad request' do + subject + + expect(response).to have_http_status(400) + end + end + + context 'when the required email param is provided' do + context 'when there is a matching canonical email block' do + let!(:canonical_email_block) { CanonicalEmailBlock.create(params) } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the expected canonical email hash' do + subject + + expect(body_as_json[0][:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) + end + end + + context 'when there is no matching canonical email block' do + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns an empty list' do + subject + + expect(body_as_json).to be_empty + end + end + end + end + + describe 'POST /api/v1/admin/canonical_email_blocks' do + subject do + post '/api/v1/admin/canonical_email_blocks', headers: headers, params: params + end + + let(:params) { { email: 'example@email.com' } } + let(:canonical_email_block) { CanonicalEmailBlock.new(email: params[:email]) } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the canonical_email_hash correctly' do + subject + + expect(body_as_json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) + end + + context 'when the required email param is not provided' do + let(:params) { {} } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the canonical_email_hash param is provided instead of email' do + let(:params) { { canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the correct canonical_email_hash' do + subject + + expect(body_as_json[:canonical_email_hash]).to eq(params[:canonical_email_hash]) + end + end + + context 'when both email and canonical_email_hash params are provided' do + let(:params) { { email: 'example@email.com', canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'ignores the canonical_email_hash param' do + subject + + expect(body_as_json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) + end + end + + context 'when the given canonical email was already blocked' do + before do + canonical_email_block.save + end + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + + describe 'DELETE /api/v1/admin/canonical_email_blocks/:id' do + subject do + delete "/api/v1/admin/canonical_email_blocks/#{canonical_email_block.id}", headers: headers + end + + let!(:canonical_email_block) { Fabricate(:canonical_email_block) } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'deletes the canonical email block' do + subject + + expect(CanonicalEmailBlock.find_by(id: canonical_email_block.id)).to be_nil + end + + context 'when the canonical email block is not found' do + it 'returns http not found' do + delete '/api/v1/admin/canonical_email_blocks/0', headers: headers + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/api/v1/admin/domain_allows_spec.rb b/spec/requests/api/v1/admin/domain_allows_spec.rb new file mode 100644 index 000000000..96000e3ef --- /dev/null +++ b/spec/requests/api/v1/admin/domain_allows_spec.rb @@ -0,0 +1,194 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Domain Allows' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/admin/domain_allows' do + subject do + get '/api/v1/admin/domain_allows', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + context 'when there is no allowed domains' do + it 'returns an empty body' do + subject + + expect(body_as_json).to be_empty + end + end + + context 'when there are allowed domains' do + let!(:domain_allows) { Fabricate.times(5, :domain_allow) } + let(:expected_response) do + domain_allows.map do |domain_allow| + { + id: domain_allow.id.to_s, + domain: domain_allow.domain, + created_at: domain_allow.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), + } + end + end + + it 'returns the correct allowed domains' do + subject + + expect(body_as_json).to match_array(expected_response) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of allowed domains' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + end + end + end + + describe 'GET /api/v1/admin/domain_allows/:id' do + subject do + get "/api/v1/admin/domain_allows/#{domain_allow.id}", headers: headers + end + + let!(:domain_allow) { Fabricate(:domain_allow) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the expected allowed domain name' do + subject + + expect(body_as_json[:domain]).to eq domain_allow.domain + end + + context 'when the requested allowed domain does not exist' do + it 'returns http not found' do + get '/api/v1/admin/domain_allows/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/domain_allows' do + subject do + post '/api/v1/admin/domain_allows', headers: headers, params: params + end + + let(:params) { { domain: 'foo.bar.com' } } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + context 'with a valid domain name' do + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the expected domain name' do + subject + + expect(body_as_json[:domain]).to eq 'foo.bar.com' + end + + it 'creates a domain allow' do + subject + + expect(DomainAllow.find_by(domain: 'foo.bar.com')).to be_present + end + end + + context 'with invalid domain name' do + let(:params) { 'foo bar' } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when domain name is not specified' do + let(:params) { {} } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the domain is already allowed' do + before do + DomainAllow.create(params) + end + + it 'returns the existing allowed domain name' do + subject + + expect(body_as_json[:domain]).to eq(params[:domain]) + end + end + end + + describe 'DELETE /api/v1/admin/domain_allows/:id' do + subject do + delete "/api/v1/admin/domain_allows/#{domain_allow.id}", headers: headers + end + + let!(:domain_allow) { Fabricate(:domain_allow) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'deletes the allowed domain' do + subject + + expect(DomainAllow.find_by(id: domain_allow.id)).to be_nil + end + + context 'when the allowed domain does not exist' do + it 'returns http not found' do + delete '/api/v1/admin/domain_allows/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/api/v1/admin/domain_blocks_spec.rb b/spec/requests/api/v1/admin/domain_blocks_spec.rb new file mode 100644 index 000000000..58641c20d --- /dev/null +++ b/spec/requests/api/v1/admin/domain_blocks_spec.rb @@ -0,0 +1,300 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Domain Blocks' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read:domain_blocks admin:write:domain_blocks' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/admin/domain_blocks' do + subject do + get '/api/v1/admin/domain_blocks', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + context 'when there are no domain blocks' do + it 'returns an empty list' do + subject + + expect(body_as_json).to be_empty + end + end + + context 'when there are domain blocks' do + let!(:domain_blocks) do + [ + Fabricate(:domain_block, severity: :silence, reject_media: true), + Fabricate(:domain_block, severity: :suspend, obfuscate: true), + Fabricate(:domain_block, severity: :noop, reject_reports: true), + Fabricate(:domain_block, public_comment: 'Spam'), + Fabricate(:domain_block, private_comment: 'Spam'), + ] + end + let(:expected_responde) do + domain_blocks.map do |domain_block| + { + id: domain_block.id.to_s, + domain: domain_block.domain, + digest: domain_block.domain_digest, + created_at: domain_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), + severity: domain_block.severity.to_s, + reject_media: domain_block.reject_media, + reject_reports: domain_block.reject_reports, + private_comment: domain_block.private_comment, + public_comment: domain_block.public_comment, + obfuscate: domain_block.obfuscate, + } + end + end + + it 'returns the expected domain blocks' do + subject + + expect(body_as_json).to match_array(expected_responde) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of domain blocks' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + end + end + end + + describe 'GET /api/v1/admin/domain_blocks/:id' do + subject do + get "/api/v1/admin/domain_blocks/#{domain_block.id}", headers: headers + end + + let!(:domain_block) { Fabricate(:domain_block) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the expected domain block content' do + subject + + expect(body_as_json).to eq( + { + id: domain_block.id.to_s, + domain: domain_block.domain, + digest: domain_block.domain_digest, + created_at: domain_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), + severity: domain_block.severity.to_s, + reject_media: domain_block.reject_media, + reject_reports: domain_block.reject_reports, + private_comment: domain_block.private_comment, + public_comment: domain_block.public_comment, + obfuscate: domain_block.obfuscate, + } + ) + end + + context 'when the requested domain block does not exist' do + it 'returns http not found' do + get '/api/v1/admin/domain_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/domain_blocks' do + subject do + post '/api/v1/admin/domain_blocks', headers: headers, params: params + end + + let(:params) { { domain: 'foo.bar.com', severity: :silence } } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'creates a domain block with the expected domain name and severity', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + + body = body_as_json + + expect(body).to match a_hash_including( + { + domain: 'foo.bar.com', + severity: 'silence', + } + ) + end + + it 'creates a domain block' do + subject + + expect(DomainBlock.find_by(domain: 'foo.bar.com')).to be_present + end + + context 'when a looser domain block already exists on a higher level domain' do + let(:params) { { domain: 'foo.bar.com', severity: :suspend } } + + before do + Fabricate(:domain_block, domain: 'bar.com', severity: :silence) + end + + it 'creates a domain block with the expected domain name and severity', :aggregate_failures do + subject + + body = body_as_json + + expect(response).to have_http_status(200) + expect(body).to match a_hash_including( + { + domain: 'foo.bar.com', + severity: 'suspend', + } + ) + + expect(DomainBlock.find_by(domain: 'foo.bar.com')).to be_present + end + end + + context 'when a domain block already exists on the same domain' do + before do + Fabricate(:domain_block, domain: 'foo.bar.com', severity: :silence) + end + + it 'returns existing domain block in error', :aggregate_failures do + subject + + expect(response).to have_http_status(422) + expect(body_as_json[:existing_domain_block][:domain]).to eq('foo.bar.com') + end + end + + context 'when a stricter domain block already exists on a higher level domain' do + before do + Fabricate(:domain_block, domain: 'bar.com', severity: :suspend) + end + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + + it 'returns existing domain block in error' do + subject + + expect(body_as_json[:existing_domain_block][:domain]).to eq('bar.com') + end + end + + context 'when given domain name is invalid' do + let(:params) { { domain: 'foo bar', severity: :silence } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + + describe 'PUT /api/v1/admin/domain_blocks/:id' do + subject do + put "/api/v1/admin/domain_blocks/#{domain_block.id}", headers: headers, params: params + end + + let!(:domain_block) { Fabricate(:domain_block, domain: 'example.com', severity: :silence) } + let(:params) { { domain: 'example.com', severity: 'suspend' } } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the updated domain block' do + subject + + expect(body_as_json).to match a_hash_including( + { + id: domain_block.id.to_s, + domain: domain_block.domain, + digest: domain_block.domain_digest, + severity: 'suspend', + } + ) + end + + it 'updates the block severity' do + expect { subject }.to change { domain_block.reload.severity }.from('silence').to('suspend') + end + + context 'when domain block does not exist' do + it 'returns http not found' do + put '/api/v1/admin/domain_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'DELETE /api/v1/admin/domain_blocks/:id' do + subject do + delete "/api/v1/admin/domain_blocks/#{domain_block.id}", headers: headers + end + + let!(:domain_block) { Fabricate(:domain_block) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'deletes the domain block' do + subject + + expect(DomainBlock.find_by(id: domain_block.id)).to be_nil + end + + context 'when domain block does not exist' do + it 'returns http not found' do + delete '/api/v1/admin/domain_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/api/v1/admin/email_domain_blocks_spec.rb b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb new file mode 100644 index 000000000..d512def86 --- /dev/null +++ b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb @@ -0,0 +1,211 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Email Domain Blocks' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:account) { Fabricate(:account) } + let(:scopes) { 'admin:read:email_domain_blocks admin:write:email_domain_blocks' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/admin/email_domain_blocks' do + subject do + get '/api/v1/admin/email_domain_blocks', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + context 'when there is no email domain block' do + it 'returns an empty list' do + subject + + expect(body_as_json).to be_empty + end + end + + context 'when there are email domain blocks' do + let!(:email_domain_blocks) { Fabricate.times(5, :email_domain_block) } + let(:blocked_email_domains) { email_domain_blocks.pluck(:domain) } + + it 'return the correct blocked email domains' do + subject + + expect(body_as_json.pluck(:domain)).to match_array(blocked_email_domains) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of email domain blocks' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + end + + context 'with since_id param' do + let(:params) { { since_id: email_domain_blocks[1].id } } + + it 'returns only the email domain blocks after since_id' do + subject + + email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s) + + expect(body_as_json.pluck(:id)).to match_array(email_domain_blocks_ids[2..]) + end + end + + context 'with max_id param' do + let(:params) { { max_id: email_domain_blocks[3].id } } + + it 'returns only the email domain blocks before max_id' do + subject + + email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s) + + expect(body_as_json.pluck(:id)).to match_array(email_domain_blocks_ids[..2]) + end + end + end + end + + describe 'GET /api/v1/admin/email_domain_blocks/:id' do + subject do + get "/api/v1/admin/email_domain_blocks/#{email_domain_block.id}", headers: headers + end + + let!(:email_domain_block) { Fabricate(:email_domain_block) } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + context 'when email domain block exists' do + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the correct blocked domain' do + subject + + expect(body_as_json[:domain]).to eq(email_domain_block.domain) + end + end + + context 'when email domain block does not exist' do + it 'returns http not found' do + get '/api/v1/admin/email_domain_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/email_domain_blocks' do + subject do + post '/api/v1/admin/email_domain_blocks', headers: headers, params: params + end + + let(:params) { { domain: 'example.com' } } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the correct blocked email domain' do + subject + + expect(body_as_json[:domain]).to eq(params[:domain]) + end + + context 'when domain param is not provided' do + let(:params) { { domain: '' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when provided domain name has an invalid character' do + let(:params) { { domain: 'do\uD800.com' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when provided domain is already blocked' do + before do + EmailDomainBlock.create(params) + end + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + + describe 'DELETE /api/v1/admin/email_domain_blocks' do + subject do + delete "/api/v1/admin/email_domain_blocks/#{email_domain_block.id}", headers: headers + end + + let!(:email_domain_block) { Fabricate(:email_domain_block) } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns an empty body' do + subject + + expect(body_as_json).to be_empty + end + + it 'deletes email domain block' do + subject + + expect(EmailDomainBlock.find_by(id: email_domain_block.id)).to be_nil + end + + context 'when email domain block does not exist' do + it 'returns http not found' do + delete '/api/v1/admin/email_domain_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/api/v1/admin/ip_blocks_spec.rb b/spec/requests/api/v1/admin/ip_blocks_spec.rb new file mode 100644 index 000000000..d03886c51 --- /dev/null +++ b/spec/requests/api/v1/admin/ip_blocks_spec.rb @@ -0,0 +1,255 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'IP Blocks' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'admin:read:ip_blocks admin:write:ip_blocks' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/admin/ip_blocks' do + subject do + get '/api/v1/admin/ip_blocks', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + context 'when there is no ip block' do + it 'returns an empty body' do + subject + + expect(body_as_json).to be_empty + end + end + + context 'when there are ip blocks' do + let!(:ip_blocks) do + [ + IpBlock.create(ip: '192.0.2.0/24', severity: :no_access), + IpBlock.create(ip: '172.16.0.1', severity: :sign_up_requires_approval, comment: 'Spam'), + IpBlock.create(ip: '2001:0db8::/32', severity: :sign_up_block, expires_in: 10.days), + ] + end + let(:expected_response) do + ip_blocks.map do |ip_block| + { + id: ip_block.id.to_s, + ip: ip_block.ip, + severity: ip_block.severity.to_s, + comment: ip_block.comment, + created_at: ip_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), + expires_at: ip_block.expires_at&.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), + } + end + end + + it 'returns the correct blocked ips' do + subject + + expect(body_as_json).to match_array(expected_response) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of ip blocks' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + end + end + end + + describe 'GET /api/v1/admin/ip_blocks/:id' do + subject do + get "/api/v1/admin/ip_blocks/#{ip_block.id}", headers: headers + end + + let!(:ip_block) { IpBlock.create(ip: '192.0.2.0/24', severity: :no_access) } + + it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the correct ip block' do + subject + + json = body_as_json + + expect(json[:ip]).to eq("#{ip_block.ip}/#{ip_block.ip.prefix}") + expect(json[:severity]).to eq(ip_block.severity.to_s) + end + + context 'when ip block does not exist' do + it 'returns http not found' do + get '/api/v1/admin/ip_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/ip_blocks' do + subject do + post '/api/v1/admin/ip_blocks', headers: headers, params: params + end + + let(:params) { { ip: '151.0.32.55', severity: 'no_access', comment: 'Spam' } } + + it_behaves_like 'forbidden for wrong scope', 'admin:read:ip_blocks' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the correct ip block' do + subject + + json = body_as_json + + expect(json[:ip]).to eq("#{params[:ip]}/32") + expect(json[:severity]).to eq(params[:severity]) + expect(json[:comment]).to eq(params[:comment]) + end + + context 'when the required ip param is not provided' do + let(:params) { { ip: '', severity: 'no_access' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the required severity param is not provided' do + let(:params) { { ip: '173.65.23.1', severity: '' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the given ip address is already blocked' do + before do + IpBlock.create(params) + end + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the given ip address is invalid' do + let(:params) { { ip: '520.13.54.120', severity: 'no_access' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + + describe 'PUT /api/v1/admin/ip_blocks/:id' do + subject do + put "/api/v1/admin/ip_blocks/#{ip_block.id}", headers: headers, params: params + end + + let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access', comment: 'Spam', expires_in: 48.hours) } + let(:params) { { severity: 'sign_up_requires_approval', comment: 'Decreasing severity' } } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the correct ip block' do + subject + + expect(body_as_json).to match(hash_including({ + ip: "#{ip_block.ip}/#{ip_block.ip.prefix}", + severity: 'sign_up_requires_approval', + comment: 'Decreasing severity', + })) + end + + it 'updates the severity correctly' do + expect { subject }.to change { ip_block.reload.severity }.from('no_access').to('sign_up_requires_approval') + end + + it 'updates the comment correctly' do + expect { subject }.to change { ip_block.reload.comment }.from('Spam').to('Decreasing severity') + end + + context 'when ip block does not exist' do + it 'returns http not found' do + put '/api/v1/admin/ip_blocks/-1', headers: headers, params: params + + expect(response).to have_http_status(404) + end + end + end + + describe 'DELETE /api/v1/admin/ip_blocks/:id' do + subject do + delete "/api/v1/admin/ip_blocks/#{ip_block.id}", headers: headers + end + + let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access') } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns an empty body' do + subject + + expect(body_as_json).to be_empty + end + + it 'deletes the ip block' do + subject + + expect(IpBlock.find_by(id: ip_block.id)).to be_nil + end + + context 'when ip block does not exist' do + it 'returns http not found' do + delete '/api/v1/admin/ip_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/api/v1/admin/reports_spec.rb b/spec/requests/api/v1/admin/reports_spec.rb new file mode 100644 index 000000000..91c3c11f5 --- /dev/null +++ b/spec/requests/api/v1/admin/reports_spec.rb @@ -0,0 +1,272 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Reports' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read:reports admin:write:reports' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/admin/reports' do + subject do + get '/api/v1/admin/reports', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + context 'when there are no reports' do + it 'returns an empty list' do + subject + + expect(body_as_json).to be_empty + end + end + + context 'when there are reports' do + let!(:reporter) { Fabricate(:account) } + let!(:spammer) { Fabricate(:account) } + let(:expected_response) do + scope.map do |report| + hash_including({ + id: report.id.to_s, + action_taken: report.action_taken?, + category: report.category, + comment: report.comment, + account: hash_including(id: report.account.id.to_s), + target_account: hash_including(id: report.target_account.id.to_s), + statuses: report.statuses, + rules: report.rules, + forwarded: report.forwarded, + }) + end + end + let(:scope) { Report.unresolved } + + before do + Fabricate(:report) + Fabricate(:report, target_account: spammer) + Fabricate(:report, account: reporter, target_account: spammer) + Fabricate(:report, action_taken_at: 4.days.ago, account: reporter) + Fabricate(:report, action_taken_at: 20.days.ago) + end + + it 'returns all unresolved reports' do + subject + + expect(body_as_json).to match_array(expected_response) + end + + context 'with resolved param' do + let(:params) { { resolved: true } } + let(:scope) { Report.resolved } + + it 'returns only the resolved reports' do + subject + + expect(body_as_json).to match_array(expected_response) + end + end + + context 'with account_id param' do + let(:params) { { account_id: reporter.id } } + let(:scope) { Report.unresolved.where(account: reporter) } + + it 'returns all unresolved reports filed by the specified account' do + subject + + expect(body_as_json).to match_array(expected_response) + end + end + + context 'with target_account_id param' do + let(:params) { { target_account_id: spammer.id } } + let(:scope) { Report.unresolved.where(target_account: spammer) } + + it 'returns all unresolved reports targeting the specified account' do + subject + + expect(body_as_json).to match_array(expected_response) + end + end + + context 'with limit param' do + let(:params) { { limit: 1 } } + + it 'returns only the requested number of reports' do + subject + + expect(body_as_json.size).to eq(1) + end + end + end + end + + describe 'GET /api/v1/admin/reports/:id' do + subject do + get "/api/v1/admin/reports/#{report.id}", headers: headers + end + + let(:report) { Fabricate(:report) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the requested report content' do + subject + + expect(body_as_json).to include( + { + id: report.id.to_s, + action_taken: report.action_taken?, + category: report.category, + comment: report.comment, + account: a_hash_including(id: report.account.id.to_s), + target_account: a_hash_including(id: report.target_account.id.to_s), + statuses: report.statuses, + rules: report.rules, + forwarded: report.forwarded, + } + ) + end + end + + describe 'PUT /api/v1/admin/reports/:id' do + subject do + put "/api/v1/admin/reports/#{report.id}", headers: headers, params: params + end + + let!(:report) { Fabricate(:report, category: :other) } + let(:params) { { category: 'spam' } } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'updates the report category' do + expect { subject }.to change { report.reload.category }.from('other').to('spam') + end + + it 'returns the updated report content' do + subject + + report.reload + + expect(body_as_json).to include( + { + id: report.id.to_s, + action_taken: report.action_taken?, + category: report.category, + comment: report.comment, + account: a_hash_including(id: report.account.id.to_s), + target_account: a_hash_including(id: report.target_account.id.to_s), + statuses: report.statuses, + rules: report.rules, + forwarded: report.forwarded, + } + ) + end + end + + describe 'POST #resolve' do + subject do + post "/api/v1/admin/reports/#{report.id}/resolve", headers: headers + end + + let(:report) { Fabricate(:report, action_taken_at: nil) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'marks report as resolved' do + expect { subject }.to change { report.reload.unresolved? }.from(true).to(false) + end + end + + describe 'POST #reopen' do + subject do + post "/api/v1/admin/reports/#{report.id}/reopen", headers: headers + end + + let(:report) { Fabricate(:report, action_taken_at: 10.days.ago) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'marks report as unresolved' do + expect { subject }.to change { report.reload.unresolved? }.from(false).to(true) + end + end + + describe 'POST #assign_to_self' do + subject do + post "/api/v1/admin/reports/#{report.id}/assign_to_self", headers: headers + end + + let(:report) { Fabricate(:report) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'assigns report to the requesting user' do + expect { subject }.to change { report.reload.assigned_account_id }.from(nil).to(user.account.id) + end + end + + describe 'POST #unassign' do + subject do + post "/api/v1/admin/reports/#{report.id}/unassign", headers: headers + end + + let(:report) { Fabricate(:report, assigned_account_id: user.account.id) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'unassigns report from assignee' do + expect { subject }.to change { report.reload.assigned_account_id }.from(user.account.id).to(nil) + end + end +end diff --git a/spec/requests/api/v1/admin/tags_spec.rb b/spec/requests/api/v1/admin/tags_spec.rb new file mode 100644 index 000000000..031be17f5 --- /dev/null +++ b/spec/requests/api/v1/admin/tags_spec.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Tags' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:tag) { Fabricate(:tag) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/admin/tags' do + subject do + get '/api/v1/admin/tags', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + context 'when there are no tags' do + it 'returns an empty list' do + subject + + expect(body_as_json).to be_empty + end + end + + context 'when there are tagss' do + let!(:tags) do + [ + Fabricate(:tag), + Fabricate(:tag), + Fabricate(:tag), + Fabricate(:tag), + ] + end + + it 'returns the expected tags' do + subject + tags.each do |tag| + expect(body_as_json.find { |item| item[:id] == tag.id.to_s && item[:name] == tag.name }).to_not be_nil + end + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of tags' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + end + end + end + + describe 'GET /api/v1/admin/tags/:id' do + subject do + get "/api/v1/admin/tags/#{tag.id}", headers: headers + end + + let!(:tag) { Fabricate(:tag) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns expected tag content' do + subject + + expect(body_as_json[:id].to_i).to eq(tag.id) + expect(body_as_json[:name]).to eq(tag.name) + end + + context 'when the requested tag does not exist' do + it 'returns http not found' do + get '/api/v1/admin/tags/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'PUT /api/v1/admin/tags/:id' do + subject do + put "/api/v1/admin/tags/#{tag.id}", headers: headers, params: params + end + + let!(:tag) { Fabricate(:tag) } + let(:params) { { display_name: tag.name.upcase } } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong scope', 'admin:read' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns updated tag' do + subject + + expect(body_as_json[:id].to_i).to eq(tag.id) + expect(body_as_json[:name]).to eq(tag.name.upcase) + end + + context 'when the updated display name is invalid' do + let(:params) { { display_name: tag.name + tag.id.to_s } } + + it 'returns http unprocessable content' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the requested tag does not exist' do + it 'returns http not found' do + get '/api/v1/admin/tags/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/api/v1/apps/credentials_spec.rb b/spec/requests/api/v1/apps/credentials_spec.rb new file mode 100644 index 000000000..dafe168c5 --- /dev/null +++ b/spec/requests/api/v1/apps/credentials_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Credentials' do + describe 'GET /api/v1/apps/verify_credentials' do + subject do + get '/api/v1/apps/verify_credentials', headers: headers + end + + context 'with an oauth token' do + let(:token) { Fabricate(:accessible_access_token, scopes: 'read', application: Fabricate(:application)) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the app information correctly' do + subject + + expect(body_as_json).to match( + a_hash_including( + name: token.application.name, + website: token.application.website, + vapid_key: Rails.configuration.x.vapid_public_key + ) + ) + end + end + + context 'without an oauth token' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end +end diff --git a/spec/controllers/api/v1/apps_controller_spec.rb b/spec/requests/api/v1/apps_spec.rb similarity index 63% rename from spec/controllers/api/v1/apps_controller_spec.rb rename to spec/requests/api/v1/apps_spec.rb index 70cd62d48..88f9eee36 100644 --- a/spec/controllers/api/v1/apps_controller_spec.rb +++ b/spec/requests/api/v1/apps_spec.rb @@ -1,15 +1,19 @@ +# frozen_string_literal: true + require 'rails_helper' -RSpec.describe Api::V1::AppsController, type: :controller do - render_views +RSpec.describe 'Apps' do + describe 'POST /api/v1/apps' do + subject do + post '/api/v1/apps', params: params + end - describe 'POST #create' do - let(:client_name) { 'Test app' } - let(:scopes) { nil } + let(:client_name) { 'Test app' } + let(:scopes) { nil } let(:redirect_uris) { 'urn:ietf:wg:oauth:2.0:oob' } - let(:website) { nil } + let(:website) { nil } - let(:app_params) do + let(:params) do { client_name: client_name, redirect_uris: redirect_uris, @@ -18,24 +22,26 @@ RSpec.describe Api::V1::AppsController, type: :controller do } end - before do - post :create, params: app_params - end - context 'with valid params' do it 'returns http success' do + subject + expect(response).to have_http_status(200) end it 'creates an OAuth app' do - expect(Doorkeeper::Application.find_by(name: client_name)).to_not be nil + subject + + expect(Doorkeeper::Application.find_by(name: client_name)).to be_present end it 'returns client ID and client secret' do - json = body_as_json + subject - expect(json[:client_id]).to_not be_blank - expect(json[:client_secret]).to_not be_blank + body = body_as_json + + expect(body[:client_id]).to be_present + expect(body[:client_secret]).to be_present end end @@ -43,6 +49,8 @@ RSpec.describe Api::V1::AppsController, type: :controller do let(:scopes) { 'hoge' } it 'returns http unprocessable entity' do + subject + expect(response).to have_http_status(422) end end @@ -51,10 +59,14 @@ RSpec.describe Api::V1::AppsController, type: :controller do let(:scopes) { (%w(read) * 40).join(' ') } it 'returns http success' do + subject + expect(response).to have_http_status(200) end it 'only saves the scope once' do + subject + expect(Doorkeeper::Application.find_by(name: client_name).scopes.to_s).to eq 'read' end end @@ -63,22 +75,39 @@ RSpec.describe Api::V1::AppsController, type: :controller do let(:client_name) { 'hoge' * 20 } it 'returns http unprocessable entity' do + subject + expect(response).to have_http_status(422) end end context 'with a too-long website' do - let(:website) { 'https://foo.bar/' + ('hoge' * 2_000) } + let(:website) { "https://foo.bar/#{'hoge' * 2_000}" } it 'returns http unprocessable entity' do + subject + expect(response).to have_http_status(422) end end context 'with a too-long redirect_uris' do - let(:redirect_uris) { 'https://foo.bar/' + ('hoge' * 2_000) } + let(:redirect_uris) { "https://foo.bar/#{'hoge' * 2_000}" } it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'without required params' do + let(:client_name) { '' } + let(:redirect_uris) { '' } + + it 'returns http unprocessable entity' do + subject + expect(response).to have_http_status(422) end end diff --git a/spec/requests/api/v1/bookmarks_spec.rb b/spec/requests/api/v1/bookmarks_spec.rb new file mode 100644 index 000000000..1f1cd35ca --- /dev/null +++ b/spec/requests/api/v1/bookmarks_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Bookmarks' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:bookmarks' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/bookmarks' do + subject do + get '/api/v1/bookmarks', headers: headers, params: params + end + + let(:params) { {} } + let!(:bookmarks) { Fabricate.times(3, :bookmark, account: user.account) } + + let(:expected_response) do + bookmarks.map do |bookmark| + a_hash_including(id: bookmark.status.id.to_s, account: a_hash_including(id: bookmark.status.account.id.to_s)) + end + end + + it_behaves_like 'forbidden for wrong scope', 'write' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the bookmarked statuses' do + subject + + expect(body_as_json).to match_array(expected_response) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'paginates correctly', :aggregate_failures do + subject + + expect(body_as_json.size).to eq(params[:limit]) + expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_bookmarks_url(limit: params[:limit], min_id: bookmarks.last.id)) + expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_bookmarks_url(limit: params[:limit], max_id: bookmarks[1].id)) + end + end + + context 'without the authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end +end diff --git a/spec/requests/api/v1/domain_blocks_spec.rb b/spec/requests/api/v1/domain_blocks_spec.rb new file mode 100644 index 000000000..0f4fd4e90 --- /dev/null +++ b/spec/requests/api/v1/domain_blocks_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Domain blocks' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:blocks write:blocks' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/domain_blocks' do + subject do + get '/api/v1/domain_blocks', headers: headers, params: params + end + + let(:blocked_domains) { ['example.com', 'example.net', 'example.org', 'example.com.br'] } + let(:params) { {} } + + before do + blocked_domains.each { |domain| user.account.block_domain!(domain) } + end + + it_behaves_like 'forbidden for wrong scope', 'write:blocks' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the domains blocked by the requesting user' do + subject + + expect(body_as_json).to match_array(blocked_domains) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of blocked domains' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + end + end + + describe 'POST /api/v1/domain_blocks' do + subject do + post '/api/v1/domain_blocks', headers: headers, params: params + end + + let(:params) { { domain: 'example.com' } } + + it_behaves_like 'forbidden for wrong scope', 'read read:blocks' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'creates a domain block' do + subject + + expect(user.account.domain_blocking?(params[:domain])).to be(true) + end + + context 'when no domain name is given' do + let(:params) { { domain: '' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the given domain name is invalid' do + let(:params) { { domain: 'example com' } } + + it 'returns unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + + describe 'DELETE /api/v1/domain_blocks' do + subject do + delete '/api/v1/domain_blocks/', headers: headers, params: params + end + + let(:params) { { domain: 'example.com' } } + + before do + user.account.block_domain!('example.com') + end + + it_behaves_like 'forbidden for wrong scope', 'read read:blocks' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'deletes the specified domain block' do + subject + + expect(user.account.domain_blocking?('example.com')).to be(false) + end + + context 'when the given domain name is not blocked' do + let(:params) { { domain: 'example.org' } } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end + end +end diff --git a/spec/requests/api/v1/emails/confirmations_spec.rb b/spec/requests/api/v1/emails/confirmations_spec.rb new file mode 100644 index 000000000..8f5171ee7 --- /dev/null +++ b/spec/requests/api/v1/emails/confirmations_spec.rb @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Confirmations' do + let(:confirmed_at) { nil } + let(:user) { Fabricate(:user, confirmed_at: confirmed_at) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:accounts write:accounts' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'POST /api/v1/emails/confirmations' do + subject do + post '/api/v1/emails/confirmations', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'read read:accounts' + + context 'with an oauth token' do + context 'when user was created by a different application' do + let(:user) { Fabricate(:user, confirmed_at: confirmed_at, created_by_application: Fabricate(:application)) } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + context 'when user was created by the same application' do + before do + user.update(created_by_application: token.application) + end + + context 'when the account is already confirmed' do + let(:confirmed_at) { Time.now.utc } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + + context 'when user changed e-mail and has not confirmed it' do + before do + user.update(email: 'foo@bar.com') + end + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end + end + + context 'when the account is unconfirmed' do + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end + + context 'with email param' do + let(:params) { { email: 'foo@bar.com' } } + + it "updates the user's e-mail address", :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.reload.unconfirmed_email).to eq('foo@bar.com') + end + end + + context 'with invalid email param' do + let(:params) { { email: 'invalid' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + end + + context 'without an oauth token' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end + + describe 'GET /api/v1/emails/check_confirmation' do + subject do + get '/api/v1/emails/check_confirmation', headers: headers + end + + it_behaves_like 'forbidden for wrong scope', 'write' + + context 'with an oauth token' do + context 'when the account is not confirmed' do + it 'returns the confirmation status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to be false + end + end + + context 'when the account is confirmed' do + let(:confirmed_at) { Time.now.utc } + + it 'returns the confirmation status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to be true + end + end + end + + context 'with an authentication cookie' do + let(:headers) { {} } + + before do + sign_in user, scope: :user + end + + context 'when the account is not confirmed' do + it 'returns the confirmation status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to be false + end + end + + context 'when the account is confirmed' do + let(:confirmed_at) { Time.now.utc } + + it 'returns the confirmation status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to be true + end + end + end + + context 'without an oauth token and an authentication cookie' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end +end diff --git a/spec/requests/api/v1/featured_tags_spec.rb b/spec/requests/api/v1/featured_tags_spec.rb new file mode 100644 index 000000000..6c171f6e4 --- /dev/null +++ b/spec/requests/api/v1/featured_tags_spec.rb @@ -0,0 +1,193 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'FeaturedTags' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:accounts write:accounts' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/featured_tags' do + context 'with wrong scope' do + before do + get '/api/v1/featured_tags', headers: headers + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'when Authorization header is missing' do + it 'returns http unauthorized' do + get '/api/v1/featured_tags' + + expect(response).to have_http_status(401) + end + end + + it 'returns http success' do + get '/api/v1/featured_tags', headers: headers + + expect(response).to have_http_status(200) + end + + context 'when the requesting user has no featured tag' do + before { Fabricate.times(3, :featured_tag) } + + it 'returns an empty body' do + get '/api/v1/featured_tags', headers: headers + + body = body_as_json + + expect(body).to be_empty + end + end + + context 'when the requesting user has featured tags' do + let!(:user_featured_tags) { Fabricate.times(5, :featured_tag, account: user.account) } + + it 'returns only the featured tags belonging to the requesting user' do + get '/api/v1/featured_tags', headers: headers + + body = body_as_json + expected_ids = user_featured_tags.pluck(:id).map(&:to_s) + + expect(body.pluck(:id)).to match_array(expected_ids) + end + end + end + + describe 'POST /api/v1/featured_tags' do + let(:params) { { name: 'tag' } } + + it 'returns http success' do + post '/api/v1/featured_tags', headers: headers, params: params + + expect(response).to have_http_status(200) + end + + it 'returns the correct tag name' do + post '/api/v1/featured_tags', headers: headers, params: params + + body = body_as_json + + expect(body[:name]).to eq(params[:name]) + end + + it 'creates a new featured tag for the requesting user' do + post '/api/v1/featured_tags', headers: headers, params: params + + featured_tag = FeaturedTag.find_by(name: params[:name], account: user.account) + + expect(featured_tag).to be_present + end + + context 'with wrong scope' do + before do + post '/api/v1/featured_tags', headers: headers, params: params + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'when Authorization header is missing' do + it 'returns http unauthorized' do + post '/api/v1/featured_tags', params: params + + expect(response).to have_http_status(401) + end + end + + context 'when required param "name" is not provided' do + it 'returns http bad request' do + post '/api/v1/featured_tags', headers: headers + + expect(response).to have_http_status(400) + end + end + + context 'when provided tag name is invalid' do + let(:params) { { name: 'asj&*!' } } + + it 'returns http unprocessable entity' do + post '/api/v1/featured_tags', headers: headers, params: params + + expect(response).to have_http_status(422) + end + end + + context 'when tag name is already taken' do + before do + FeaturedTag.create(name: params[:name], account: user.account) + end + + it 'returns http unprocessable entity' do + post '/api/v1/featured_tags', headers: headers, params: params + + expect(response).to have_http_status(422) + end + end + end + + describe 'DELETE /api/v1/featured_tags' do + let!(:featured_tag) { FeaturedTag.create(name: 'tag', account: user.account) } + let(:id) { featured_tag.id } + + it 'returns http success' do + delete "/api/v1/featured_tags/#{id}", headers: headers + + expect(response).to have_http_status(200) + end + + it 'returns an empty body' do + delete "/api/v1/featured_tags/#{id}", headers: headers + + body = body_as_json + + expect(body).to be_empty + end + + it 'deletes the featured tag' do + delete "/api/v1/featured_tags/#{id}", headers: headers + + featured_tag = FeaturedTag.find_by(id: id) + + expect(featured_tag).to be_nil + end + + context 'with wrong scope' do + before do + delete "/api/v1/featured_tags/#{id}", headers: headers + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'when Authorization header is missing' do + it 'returns http unauthorized' do + delete "/api/v1/featured_tags/#{id}" + + expect(response).to have_http_status(401) + end + end + + context 'when featured tag with given id does not exist' do + it 'returns http not found' do + delete '/api/v1/featured_tags/0', headers: headers + + expect(response).to have_http_status(404) + end + end + + context 'when deleting a featured tag of another user' do + let!(:other_user_featured_tag) { Fabricate(:featured_tag) } + let(:id) { other_user_featured_tag.id } + + it 'returns http not found' do + delete "/api/v1/featured_tags/#{id}", headers: headers + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/api/v1/follow_requests_spec.rb b/spec/requests/api/v1/follow_requests_spec.rb new file mode 100644 index 000000000..9d4ef8cd5 --- /dev/null +++ b/spec/requests/api/v1/follow_requests_spec.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Follow requests' do + let(:user) { Fabricate(:user, account_attributes: { locked: true }) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:follows write:follows' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/follow_requests' do + subject do + get '/api/v1/follow_requests', headers: headers, params: params + end + + let(:accounts) { Fabricate.times(5, :account) } + let(:params) { {} } + + let(:expected_response) do + accounts.map do |account| + a_hash_including( + id: account.id.to_s, + username: account.username, + acct: account.acct + ) + end + end + + before do + accounts.each { |account| FollowService.new.call(account, user.account) } + end + + it_behaves_like 'forbidden for wrong scope', 'write write:follows' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the expected content from accounts requesting to follow' do + subject + + expect(body_as_json).to match_array(expected_response) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of follow requests' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + end + end + + describe 'POST /api/v1/follow_requests/:account_id/authorize' do + subject do + post "/api/v1/follow_requests/#{follower.id}/authorize", headers: headers + end + + let(:follower) { Fabricate(:account) } + + before do + FollowService.new.call(follower, user.account) + end + + it_behaves_like 'forbidden for wrong scope', 'read read:follows' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'allows the requesting follower to follow' do + expect { subject }.to change { follower.following?(user.account) }.from(false).to(true) + end + + it 'returns JSON with followed_by set to true' do + subject + + expect(body_as_json[:followed_by]).to be true + end + end + + describe 'POST /api/v1/follow_requests/:account_id/reject' do + subject do + post "/api/v1/follow_requests/#{follower.id}/reject", headers: headers + end + + let(:follower) { Fabricate(:account) } + + before do + FollowService.new.call(follower, user.account) + end + + it_behaves_like 'forbidden for wrong scope', 'read read:follows' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'removes the follow request' do + subject + + expect(FollowRequest.where(target_account: user.account, account: follower)).to_not exist + end + + it 'returns JSON with followed_by set to false' do + subject + + expect(body_as_json[:followed_by]).to be false + end + end +end diff --git a/spec/requests/api/v1/instances/languages_spec.rb b/spec/requests/api/v1/instances/languages_spec.rb new file mode 100644 index 000000000..8ab8bf99c --- /dev/null +++ b/spec/requests/api/v1/instances/languages_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Languages' do + describe 'GET /api/v1/instance/languages' do + before do + get '/api/v1/instance/languages' + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'returns the supported languages' do + expect(body_as_json.pluck(:code)).to match_array LanguagesHelper::SUPPORTED_LOCALES.keys.map(&:to_s) + end + end +end diff --git a/spec/requests/api/v1/lists_spec.rb b/spec/requests/api/v1/lists_spec.rb new file mode 100644 index 000000000..383e09d0c --- /dev/null +++ b/spec/requests/api/v1/lists_spec.rb @@ -0,0 +1,247 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Lists' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:lists write:lists' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/lists' do + subject do + get '/api/v1/lists', headers: headers + end + + let!(:lists) do + [ + Fabricate(:list, account: user.account, title: 'first list', replies_policy: :followed), + Fabricate(:list, account: user.account, title: 'second list', replies_policy: :list), + Fabricate(:list, account: user.account, title: 'third list', replies_policy: :none), + Fabricate(:list, account: user.account, title: 'fourth list', exclusive: true), + ] + end + + let(:expected_response) do + lists.map do |list| + { + id: list.id.to_s, + title: list.title, + replies_policy: list.replies_policy, + exclusive: list.exclusive, + } + end + end + + before do + Fabricate(:list) + end + + it_behaves_like 'forbidden for wrong scope', 'write write:lists' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the expected lists' do + subject + + expect(body_as_json).to match_array(expected_response) + end + end + + describe 'GET /api/v1/lists/:id' do + subject do + get "/api/v1/lists/#{list.id}", headers: headers + end + + let(:list) { Fabricate(:list, account: user.account) } + + it_behaves_like 'forbidden for wrong scope', 'write write:lists' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the requested list correctly' do + subject + + expect(body_as_json).to eq({ + id: list.id.to_s, + title: list.title, + replies_policy: list.replies_policy, + exclusive: list.exclusive, + }) + end + + context 'when the list belongs to a different user' do + let(:list) { Fabricate(:list) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'when the list does not exist' do + it 'returns http not found' do + get '/api/v1/lists/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/lists' do + subject do + post '/api/v1/lists', headers: headers, params: params + end + + let(:params) { { title: 'my list', replies_policy: 'none', exclusive: 'true' } } + + it_behaves_like 'forbidden for wrong scope', 'read read:lists' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the new list' do + subject + + expect(body_as_json).to match(a_hash_including(title: 'my list', replies_policy: 'none', exclusive: true)) + end + + it 'creates a list' do + subject + + expect(List.where(account: user.account).count).to eq(1) + end + + context 'when a title is not given' do + let(:params) { { title: '' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the given replies_policy is invalid' do + let(:params) { { title: 'a list', replies_policy: 'whatever' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + + describe 'PUT /api/v1/lists/:id' do + subject do + put "/api/v1/lists/#{list.id}", headers: headers, params: params + end + + let(:list) { Fabricate(:list, account: user.account, title: 'my list') } + let(:params) { { title: 'list', replies_policy: 'followed', exclusive: 'true' } } + + it_behaves_like 'forbidden for wrong scope', 'read read:lists' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the updated list' do + subject + + list.reload + + expect(body_as_json).to eq({ + id: list.id.to_s, + title: list.title, + replies_policy: list.replies_policy, + exclusive: list.exclusive, + }) + end + + it 'updates the list title' do + expect { subject }.to change { list.reload.title }.from('my list').to('list') + end + + it 'updates the list replies_policy' do + expect { subject }.to change { list.reload.replies_policy }.from('list').to('followed') + end + + it 'updates the list exclusive' do + expect { subject }.to change { list.reload.exclusive }.from(false).to(true) + end + + context 'when the list does not exist' do + it 'returns http not found' do + put '/api/v1/lists/-1', headers: headers, params: params + + expect(response).to have_http_status(404) + end + end + + context 'when the list belongs to another user' do + let(:list) { Fabricate(:list) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end + + describe 'DELETE /api/v1/lists/:id' do + subject do + delete "/api/v1/lists/#{list.id}", headers: headers + end + + let(:list) { Fabricate(:list, account: user.account) } + + it_behaves_like 'forbidden for wrong scope', 'read read:lists' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'deletes the list' do + subject + + expect(List.where(id: list.id)).to_not exist + end + + context 'when the list does not exist' do + it 'returns http not found' do + delete '/api/v1/lists/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + + context 'when the list belongs to another user' do + let(:list) { Fabricate(:list) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/api/v1/mutes_spec.rb b/spec/requests/api/v1/mutes_spec.rb new file mode 100644 index 000000000..9a1d16200 --- /dev/null +++ b/spec/requests/api/v1/mutes_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Mutes' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'read:mutes' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/mutes' do + subject do + get '/api/v1/mutes', headers: headers, params: params + end + + let!(:mutes) { Fabricate.times(3, :mute, account: user.account) } + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'write write:mutes' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the muted accounts' do + subject + + muted_accounts = mutes.map(&:target_account) + + expect(body_as_json.pluck(:id)).to match_array(muted_accounts.map { |account| account.id.to_s }) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of muted accounts' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + + it 'sets the correct pagination headers', :aggregate_failures do + subject + + headers = response.headers['Link'] + + expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_mutes_url(limit: params[:limit], since_id: mutes[2].id.to_s)) + expect(headers.find_link(%w(rel next)).href).to eq(api_v1_mutes_url(limit: params[:limit], max_id: mutes[1].id.to_s)) + end + end + + context 'with max_id param' do + let(:params) { { max_id: mutes[1].id } } + + it 'queries mutes in range according to max_id', :aggregate_failures do + subject + + body = body_as_json + + expect(body.size).to eq 1 + expect(body[0][:id]).to eq mutes[0].target_account_id.to_s + end + end + + context 'with since_id param' do + let(:params) { { since_id: mutes[0].id } } + + it 'queries mutes in range according to since_id', :aggregate_failures do + subject + + body = body_as_json + + expect(body.size).to eq 2 + expect(body[0][:id]).to eq mutes[2].target_account_id.to_s + end + end + + context 'without an authentication header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end +end diff --git a/spec/requests/api/v1/polls_spec.rb b/spec/requests/api/v1/polls_spec.rb new file mode 100644 index 000000000..1c8a818d5 --- /dev/null +++ b/spec/requests/api/v1/polls_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Polls' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'read:statuses' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/polls/:id' do + subject do + get "/api/v1/polls/#{poll.id}", headers: headers + end + + let(:poll) { Fabricate(:poll, status: Fabricate(:status, visibility: visibility)) } + let(:visibility) { 'public' } + + it_behaves_like 'forbidden for wrong scope', 'write write:statuses' + + context 'when parent status is public' do + it 'returns the poll data successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to match( + a_hash_including( + id: poll.id.to_s, + voted: false, + voters_count: poll.voters_count, + votes_count: poll.votes_count + ) + ) + end + end + + context 'when parent status is private' do + let(:visibility) { 'private' } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/api/v1/profiles_spec.rb b/spec/requests/api/v1/profiles_spec.rb new file mode 100644 index 000000000..26a9b848e --- /dev/null +++ b/spec/requests/api/v1/profiles_spec.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Deleting profile images' do + let(:account) do + Fabricate( + :account, + avatar: fixture_file_upload('avatar.gif', 'image/gif'), + header: fixture_file_upload('attachment.jpg', 'image/jpeg') + ) + end + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: account.user.id, scopes: scopes) } + let(:scopes) { 'write:accounts' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'DELETE /api/v1/profile' do + before do + allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_async) + end + + context 'when deleting an avatar' do + context 'with wrong scope' do + before do + delete '/api/v1/profile/avatar', headers: headers + end + + it_behaves_like 'forbidden for wrong scope', 'read' + end + + it 'returns http success' do + delete '/api/v1/profile/avatar', headers: headers + + expect(response).to have_http_status(200) + end + + it 'deletes the avatar' do + delete '/api/v1/profile/avatar', headers: headers + + account.reload + + expect(account.avatar).to_not exist + end + + it 'does not delete the header' do + delete '/api/v1/profile/avatar', headers: headers + + account.reload + + expect(account.header).to exist + end + + it 'queues up an account update distribution' do + delete '/api/v1/profile/avatar', headers: headers + + expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id) + end + end + + context 'when deleting a header' do + context 'with wrong scope' do + before do + delete '/api/v1/profile/header', headers: headers + end + + it_behaves_like 'forbidden for wrong scope', 'read' + end + + it 'returns http success' do + delete '/api/v1/profile/header', headers: headers + + expect(response).to have_http_status(200) + end + + it 'does not delete the avatar' do + delete '/api/v1/profile/header', headers: headers + + account.reload + + expect(account.avatar).to exist + end + + it 'deletes the header' do + delete '/api/v1/profile/header', headers: headers + + account.reload + + expect(account.header).to_not exist + end + + it 'queues up an account update distribution' do + delete '/api/v1/profile/header', headers: headers + + expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(account.id) + end + end + end +end diff --git a/spec/requests/api/v1/statuses/bookmarks_spec.rb b/spec/requests/api/v1/statuses/bookmarks_spec.rb new file mode 100644 index 000000000..d3007740a --- /dev/null +++ b/spec/requests/api/v1/statuses/bookmarks_spec.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Bookmarks' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'write:bookmarks' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'POST /api/v1/statuses/:status_id/bookmark' do + subject do + post "/api/v1/statuses/#{status.id}/bookmark", headers: headers + end + + let(:status) { Fabricate(:status) } + + it_behaves_like 'forbidden for wrong scope', 'read' + + context 'with public status' do + it 'bookmarks the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.bookmarked?(status)).to be true + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, bookmarked: true) + ) + end + end + + context 'with private status of not-followed account' do + let(:status) { Fabricate(:status, visibility: :private) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'with private status of followed account' do + let(:status) { Fabricate(:status, visibility: :private) } + + before do + user.account.follow!(status.account) + end + + it 'bookmarks the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.bookmarked?(status)).to be true + end + end + + context 'when the status does not exist' do + it 'returns http not found' do + post '/api/v1/statuses/-1/bookmark', headers: headers + + expect(response).to have_http_status(404) + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end + + describe 'POST /api/v1/statuses/:status_id/unbookmark' do + subject do + post "/api/v1/statuses/#{status.id}/unbookmark", headers: headers + end + + let(:status) { Fabricate(:status) } + + it_behaves_like 'forbidden for wrong scope', 'read' + + context 'with public status' do + context 'when the status was previously bookmarked' do + before do + Bookmark.find_or_create_by!(account: user.account, status: status) + end + + it 'unbookmarks the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.bookmarked?(status)).to be false + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, bookmarked: false) + ) + end + end + + context 'when the requesting user was blocked by the status author' do + let(:status) { Fabricate(:status) } + + before do + Bookmark.find_or_create_by!(account: user.account, status: status) + status.account.block!(user.account) + end + + it 'unbookmarks the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.bookmarked?(status)).to be false + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, bookmarked: false) + ) + end + end + + context 'when the status is not bookmarked' do + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end + end + + context 'with private status that was not bookmarked' do + let(:status) { Fabricate(:status, visibility: :private) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/api/v1/statuses/favourites_spec.rb b/spec/requests/api/v1/statuses/favourites_spec.rb new file mode 100644 index 000000000..ac5e86f29 --- /dev/null +++ b/spec/requests/api/v1/statuses/favourites_spec.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Favourites' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'write:favourites' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'POST /api/v1/statuses/:status_id/favourite' do + subject do + post "/api/v1/statuses/#{status.id}/favourite", headers: headers + end + + let(:status) { Fabricate(:status) } + + it_behaves_like 'forbidden for wrong scope', 'read read:favourites' + + context 'with public status' do + it 'favourites the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.favourited?(status)).to be true + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, favourites_count: 1, favourited: true) + ) + end + end + + context 'with private status of not-followed account' do + let(:status) { Fabricate(:status, visibility: :private) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'with private status of followed account' do + let(:status) { Fabricate(:status, visibility: :private) } + + before do + user.account.follow!(status.account) + end + + it 'favourites the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.favourited?(status)).to be true + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end + + describe 'POST /api/v1/statuses/:status_id/unfavourite' do + subject do + post "/api/v1/statuses/#{status.id}/unfavourite", headers: headers + end + + let(:status) { Fabricate(:status) } + + around do |example| + Sidekiq::Testing.fake! do + example.run + end + end + + it_behaves_like 'forbidden for wrong scope', 'read read:favourites' + + context 'with public status' do + before do + FavouriteService.new.call(user.account, status) + end + + it 'unfavourites the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.favourited?(status)).to be true + + UnfavouriteWorker.drain + expect(user.account.favourited?(status)).to be false + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, favourites_count: 0, favourited: false) + ) + end + end + + context 'when the requesting user was blocked by the status author' do + before do + FavouriteService.new.call(user.account, status) + status.account.block!(user.account) + end + + it 'unfavourites the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.favourited?(status)).to be true + + UnfavouriteWorker.drain + expect(user.account.favourited?(status)).to be false + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, favourites_count: 0, favourited: false) + ) + end + end + + context 'when status is not favourited' do + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end + + context 'with private status that was not favourited' do + let(:status) { Fabricate(:status, visibility: :private) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/api/v1/statuses/pins_spec.rb b/spec/requests/api/v1/statuses/pins_spec.rb new file mode 100644 index 000000000..db07fa424 --- /dev/null +++ b/spec/requests/api/v1/statuses/pins_spec.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Pins' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'write:accounts' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'POST /api/v1/statuses/:status_id/pin' do + subject do + post "/api/v1/statuses/#{status.id}/pin", headers: headers + end + + let(:status) { Fabricate(:status, account: user.account) } + + it_behaves_like 'forbidden for wrong scope', 'read read:accounts' + + context 'when the status is public' do + it 'pins the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.pinned?(status)).to be true + end + + it 'return json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, pinned: true) + ) + end + end + + context 'when the status is private' do + let(:status) { Fabricate(:status, account: user.account, visibility: :private) } + + it 'pins the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.pinned?(status)).to be true + end + end + + context 'when the status belongs to somebody else' do + let(:status) { Fabricate(:status) } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the status does not exist' do + it 'returns http not found' do + post '/api/v1/statuses/-1/pin', headers: headers + + expect(response).to have_http_status(404) + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end + + describe 'POST /api/v1/statuses/:status_id/unpin' do + subject do + post "/api/v1/statuses/#{status.id}/unpin", headers: headers + end + + let(:status) { Fabricate(:status, account: user.account) } + + context 'when the status is pinned' do + before do + Fabricate(:status_pin, status: status, account: user.account) + end + + it 'unpins the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.pinned?(status)).to be false + end + + it 'return json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, pinned: false) + ) + end + end + + context 'when the status is not pinned' do + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end + + context 'when the status does not exist' do + it 'returns http not found' do + post '/api/v1/statuses/-1/unpin', headers: headers + + expect(response).to have_http_status(404) + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end +end diff --git a/spec/requests/api/v1/suggestions_spec.rb b/spec/requests/api/v1/suggestions_spec.rb new file mode 100644 index 000000000..42b7f8662 --- /dev/null +++ b/spec/requests/api/v1/suggestions_spec.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Suggestions' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/suggestions' do + subject do + get '/api/v1/suggestions', headers: headers, params: params + end + + let(:bob) { Fabricate(:account) } + let(:jeff) { Fabricate(:account) } + let(:params) { {} } + + before do + PotentialFriendshipTracker.record(user.account_id, bob.id, :reblog) + PotentialFriendshipTracker.record(user.account_id, jeff.id, :favourite) + end + + it_behaves_like 'forbidden for wrong scope', 'write' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns accounts' do + subject + + body = body_as_json + + expect(body.size).to eq 2 + expect(body.pluck(:id)).to match_array([bob, jeff].map { |i| i.id.to_s }) + end + + context 'with limit param' do + let(:params) { { limit: 1 } } + + it 'returns only the requested number of accounts' do + subject + + expect(body_as_json.size).to eq 1 + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end + + describe 'DELETE /api/v1/suggestions/:id' do + subject do + delete "/api/v1/suggestions/#{jeff.id}", headers: headers + end + + let(:suggestions_source) { instance_double(AccountSuggestions::PastInteractionsSource, remove: nil) } + let(:bob) { Fabricate(:account) } + let(:jeff) { Fabricate(:account) } + + before do + PotentialFriendshipTracker.record(user.account_id, bob.id, :reblog) + PotentialFriendshipTracker.record(user.account_id, jeff.id, :favourite) + allow(AccountSuggestions::PastInteractionsSource).to receive(:new).and_return(suggestions_source) + end + + it_behaves_like 'forbidden for wrong scope', 'write' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'removes the specified suggestion' do + subject + + expect(suggestions_source).to have_received(:remove).with(user.account, jeff.id.to_s).once + expect(suggestions_source).to_not have_received(:remove).with(user.account, bob.id.to_s) + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end +end diff --git a/spec/requests/api/v1/tags_spec.rb b/spec/requests/api/v1/tags_spec.rb new file mode 100644 index 000000000..300ddf805 --- /dev/null +++ b/spec/requests/api/v1/tags_spec.rb @@ -0,0 +1,169 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Tags' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'write:follows' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/tags/:id' do + subject do + get "/api/v1/tags/#{name}" + end + + context 'when the tag exists' do + let!(:tag) { Fabricate(:tag) } + let(:name) { tag.name } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the tag' do + subject + + expect(body_as_json[:name]).to eq(name) + end + end + + context 'when the tag does not exist' do + let(:name) { 'hoge' } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end + + context 'when the tag name is invalid' do + let(:name) { 'tag-name' } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/tags/:id/follow' do + subject do + post "/api/v1/tags/#{name}/follow", headers: headers + end + + let!(:tag) { Fabricate(:tag) } + let(:name) { tag.name } + + it_behaves_like 'forbidden for wrong scope', 'read read:follows' + + context 'when the tag exists' do + it 'returns http success' do + subject + + expect(response).to have_http_status(:success) + end + + it 'creates follow' do + subject + + expect(TagFollow.where(tag: tag, account: user.account)).to exist + end + end + + context 'when the tag does not exist' do + let(:name) { 'hoge' } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'creates a new tag with the specified name' do + subject + + expect(Tag.where(name: name)).to exist + end + + it 'creates follow' do + subject + + expect(TagFollow.where(tag: Tag.find_by(name: name), account: user.account)).to exist + end + end + + context 'when the tag name is invalid' do + let(:name) { 'tag-name' } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'when the Authorization header is missing' do + let(:headers) { {} } + let(:name) { 'unauthorized' } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end + + describe 'POST #unfollow' do + subject do + post "/api/v1/tags/#{name}/unfollow", headers: headers + end + + let(:name) { tag.name } + let!(:tag) { Fabricate(:tag, name: 'foo') } + + before do + Fabricate(:tag_follow, account: user.account, tag: tag) + end + + it_behaves_like 'forbidden for wrong scope', 'read read:follows' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'removes the follow' do + subject + + expect(TagFollow.where(tag: tag, account: user.account)).to_not exist + end + + context 'when the tag name is invalid' do + let(:name) { 'tag-name' } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'when the Authorization header is missing' do + let(:headers) { {} } + let(:name) { 'unauthorized' } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end +end diff --git a/spec/requests/api/v1/timelines/home_spec.rb b/spec/requests/api/v1/timelines/home_spec.rb new file mode 100644 index 000000000..5834b9095 --- /dev/null +++ b/spec/requests/api/v1/timelines/home_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Home' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'read:statuses' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/timelines/home' do + subject do + get '/api/v1/timelines/home', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'write write:statuses' + + context 'when the timeline is available' do + let(:home_statuses) { bob.statuses + ana.statuses } + let!(:bob) { Fabricate(:account) } + let!(:tim) { Fabricate(:account) } + let!(:ana) { Fabricate(:account) } + + before do + user.account.follow!(bob) + user.account.follow!(ana) + PostStatusService.new.call(bob, text: 'New toot from bob.') + PostStatusService.new.call(tim, text: 'New toot from tim.') + PostStatusService.new.call(ana, text: 'New toot from ana.') + end + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the statuses of followed users' do + subject + + expect(body_as_json.pluck(:id)).to match_array(home_statuses.map { |status| status.id.to_s }) + end + + context 'with limit param' do + let(:params) { { limit: 1 } } + + it 'returns only the requested number of statuses' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + + it 'sets the correct pagination headers', :aggregate_failures do + subject + + headers = response.headers['Link'] + + expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_timelines_home_url(limit: 1, min_id: ana.statuses.first.id.to_s)) + expect(headers.find_link(%w(rel next)).href).to eq(api_v1_timelines_home_url(limit: 1, max_id: ana.statuses.first.id.to_s)) + end + end + end + + context 'when the timeline is regenerating' do + let(:timeline) { instance_double(HomeFeed, regenerating?: true, get: []) } + + before do + allow(HomeFeed).to receive(:new).and_return(timeline) + end + + it 'returns http partial content' do + subject + + expect(response).to have_http_status(206) + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + + context 'without a user context' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: scopes) } + + it 'returns http unprocessable entity', :aggregate_failures do + subject + + expect(response).to have_http_status(422) + expect(response.headers['Link']).to be_nil + end + end + end +end diff --git a/spec/requests/api/v1/timelines/public_spec.rb b/spec/requests/api/v1/timelines/public_spec.rb new file mode 100644 index 000000000..c43626240 --- /dev/null +++ b/spec/requests/api/v1/timelines/public_spec.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Public' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'read:statuses' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + shared_examples 'a successful request to the public timeline' do + it 'returns the expected statuses successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json.pluck(:id)).to match_array(expected_statuses.map { |status| status.id.to_s }) + end + end + + describe 'GET /api/v1/timelines/public' do + subject do + get '/api/v1/timelines/public', headers: headers, params: params + end + + let!(:private_status) { Fabricate(:status, visibility: :private) } # rubocop:disable RSpec/LetSetup + let!(:local_status) { Fabricate(:status, account: Fabricate.build(:account, domain: nil)) } + let!(:remote_status) { Fabricate(:status, account: Fabricate.build(:account, domain: 'example.com')) } + let!(:media_status) { Fabricate(:status, media_attachments: [Fabricate.build(:media_attachment)]) } + + let(:params) { {} } + + context 'when the instance allows public preview' do + let(:expected_statuses) { [local_status, remote_status, media_status] } + + context 'with an authorized user' do + it_behaves_like 'a successful request to the public timeline' + end + + context 'with an anonymous user' do + let(:headers) { {} } + + it_behaves_like 'a successful request to the public timeline' + end + + context 'with local param' do + let(:params) { { local: true } } + let(:expected_statuses) { [local_status, media_status] } + + it_behaves_like 'a successful request to the public timeline' + end + + context 'with remote param' do + let(:params) { { remote: true } } + let(:expected_statuses) { [remote_status] } + + it_behaves_like 'a successful request to the public timeline' + end + + context 'with local and remote params' do + let(:params) { { local: true, remote: true } } + let(:expected_statuses) { [local_status, remote_status, media_status] } + + it_behaves_like 'a successful request to the public timeline' + end + + context 'with only_media param' do + let(:params) { { only_media: true } } + let(:expected_statuses) { [media_status] } + + it_behaves_like 'a successful request to the public timeline' + end + + context 'with limit param' do + let(:params) { { limit: 1 } } + + it 'returns only the requested number of statuses', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json.size).to eq(params[:limit]) + end + + it 'sets the correct pagination headers', :aggregate_failures do + subject + + headers = response.headers['Link'] + + expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_timelines_public_url(limit: 1, min_id: media_status.id.to_s)) + expect(headers.find_link(%w(rel next)).href).to eq(api_v1_timelines_public_url(limit: 1, max_id: media_status.id.to_s)) + end + end + end + + context 'when the instance does not allow public preview' do + before do + Form::AdminSettings.new(timeline_preview: false).save + end + + context 'with an authenticated user' do + let(:expected_statuses) { [local_status, remote_status, media_status] } + + it_behaves_like 'a successful request to the public timeline' + end + + context 'with an unauthenticated user' do + let(:headers) { {} } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + end +end diff --git a/spec/requests/api/v2/filters/filters_spec.rb b/spec/requests/api/v2/filters/filters_spec.rb new file mode 100644 index 000000000..2ee24d809 --- /dev/null +++ b/spec/requests/api/v2/filters/filters_spec.rb @@ -0,0 +1,248 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Filters' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'read:filters write:filters' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + shared_examples 'unauthorized for invalid token' do + let(:headers) { { 'Authorization' => '' } } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + + describe 'GET /api/v2/filters' do + subject do + get '/api/v2/filters', headers: headers + end + + let!(:filters) { Fabricate.times(3, :custom_filter, account: user.account) } + + it_behaves_like 'forbidden for wrong scope', 'write write:filters' + it_behaves_like 'unauthorized for invalid token' + + it 'returns the existing filters successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json.pluck(:id)).to match_array(filters.map { |filter| filter.id.to_s }) + end + end + + describe 'POST /api/v2/filters' do + subject do + post '/api/v2/filters', params: params, headers: headers + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'read read:filters' + it_behaves_like 'unauthorized for invalid token' + + context 'with valid params' do + let(:params) { { title: 'magic', context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } } + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns a filter with keywords', :aggregate_failures do + subject + + json = body_as_json + + expect(json[:title]).to eq 'magic' + expect(json[:filter_action]).to eq 'hide' + expect(json[:context]).to eq ['home'] + expect(json[:keywords].map { |keyword| keyword.slice(:keyword, :whole_word) }).to eq [{ keyword: 'magic', whole_word: true }] + end + + it 'creates a filter', :aggregate_failures do + subject + + filter = user.account.custom_filters.first + + expect(filter).to be_present + expect(filter.keywords.pluck(:keyword)).to eq ['magic'] + expect(filter.context).to eq %w(home) + expect(filter.irreversible?).to be true + expect(filter.expires_at).to be_nil + end + end + + context 'when the required title param is missing' do + let(:params) { { context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the required context param is missing' do + let(:params) { { title: 'magic', filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the given context value is invalid' do + let(:params) { { title: 'magic', context: %w(shaolin), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + + describe 'GET /api/v2/filters/:id' do + subject do + get "/api/v2/filters/#{filter.id}", headers: headers + end + + let(:filter) { Fabricate(:custom_filter, account: user.account) } + + it_behaves_like 'forbidden for wrong scope', 'write write:filters' + it_behaves_like 'unauthorized for invalid token' + + it 'returns the filter successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json[:id]).to eq(filter.id.to_s) + end + + context 'when the filter belongs to someone else' do + let(:filter) { Fabricate(:custom_filter) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end + + describe 'PUT /api/v2/filters/:id' do + subject do + put "/api/v2/filters/#{filter.id}", params: params, headers: headers + end + + let!(:filter) { Fabricate(:custom_filter, account: user.account) } + let!(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) } + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'read read:filters' + it_behaves_like 'unauthorized for invalid token' + + context 'when updating filter parameters' do + context 'with valid params' do + let(:params) { { title: 'updated', context: %w(home public) } } + + it 'updates the filter successfully', :aggregate_failures do + subject + + filter.reload + + expect(response).to have_http_status(200) + expect(filter.title).to eq 'updated' + expect(filter.reload.context).to eq %w(home public) + end + end + + context 'with invalid params' do + let(:params) { { title: 'updated', context: %w(word) } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + + context 'when updating keywords in bulk' do + let(:params) { { keywords_attributes: [{ id: keyword.id, keyword: 'updated' }] } } + + before do + allow(redis).to receive_messages(publish: nil) + end + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'updates the keyword' do + subject + + expect(keyword.reload.keyword).to eq 'updated' + end + + it 'sends exactly one filters_changed event' do + subject + + expect(redis).to have_received(:publish).with("timeline:#{user.account.id}", Oj.dump(event: :filters_changed)).once + end + end + + context 'when the filter belongs to someone else' do + let(:filter) { Fabricate(:custom_filter) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end + + describe 'DELETE /api/v2/filters/:id' do + subject do + delete "/api/v2/filters/#{filter.id}", headers: headers + end + + let(:filter) { Fabricate(:custom_filter, account: user.account) } + + it_behaves_like 'forbidden for wrong scope', 'read read:filters' + it_behaves_like 'unauthorized for invalid token' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'removes the filter' do + subject + + expect { filter.reload }.to raise_error ActiveRecord::RecordNotFound + end + + context 'when the filter belongs to someone else' do + let(:filter) { Fabricate(:custom_filter) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/api/web/embeds_spec.rb b/spec/requests/api/web/embeds_spec.rb new file mode 100644 index 000000000..6314f43aa --- /dev/null +++ b/spec/requests/api/web/embeds_spec.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe '/api/web/embed' do + subject { get "/api/web/embeds/#{id}", headers: headers } + + context 'when accessed anonymously' do + let(:headers) { {} } + + context 'when the requested status is local' do + let(:id) { status.id } + + context 'when the requested status is public' do + let(:status) { Fabricate(:status, visibility: :public) } + + it 'returns JSON with an html attribute' do + subject + + expect(response).to have_http_status(200) + expect(body_as_json[:html]).to be_present + end + end + + context 'when the requested status is private' do + let(:status) { Fabricate(:status, visibility: :private) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end + + context 'when the requested status is remote' do + let(:remote_account) { Fabricate(:account, domain: 'example.com') } + let(:status) { Fabricate(:status, visibility: :public, account: remote_account, url: 'https://example.com/statuses/1') } + let(:id) { status.id } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'when the requested status does not exist' do + let(:id) { -1 } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end + + context 'with an API token' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + context 'when the requested status is local' do + let(:id) { status.id } + + context 'when the requested status is public' do + let(:status) { Fabricate(:status, visibility: :public) } + + it 'returns JSON with an html attribute' do + subject + + expect(response).to have_http_status(200) + expect(body_as_json[:html]).to be_present + end + + context 'when the requesting user is blocked' do + before do + status.account.block!(user.account) + end + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end + + context 'when the requested status is private' do + let(:status) { Fabricate(:status, visibility: :private) } + + before do + user.account.follow!(status.account) + end + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end + + context 'when the requested status is remote' do + let(:remote_account) { Fabricate(:account, domain: 'example.com') } + let(:status) { Fabricate(:status, visibility: :public, account: remote_account, url: 'https://example.com/statuses/1') } + let(:id) { status.id } + + let(:service_instance) { instance_double(FetchOEmbedService) } + + before do + allow(FetchOEmbedService).to receive(:new) { service_instance } + allow(service_instance).to receive(:call) { call_result } + end + + context 'when the requesting user is blocked' do + before do + status.account.block!(user.account) + end + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'when successfully fetching OEmbed' do + let(:call_result) { { html: 'ok' } } + + it 'returns JSON with an html attribute' do + subject + + expect(response).to have_http_status(200) + expect(body_as_json[:html]).to be_present + end + end + + context 'when failing to fetch OEmbed' do + let(:call_result) { nil } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end + + context 'when the requested status does not exist' do + let(:id) { -1 } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/backups_spec.rb b/spec/requests/backups_spec.rb new file mode 100644 index 000000000..a6c2efe0d --- /dev/null +++ b/spec/requests/backups_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Backups' do + include RoutingHelper + + describe 'GET backups#download' do + let(:user) { Fabricate(:user) } + let(:backup) { Fabricate(:backup, user: user) } + + before do + sign_in user + end + + it 'Downloads a user backup' do + get download_backup_path(backup) + + expect(response).to redirect_to(backup_dump_url) + end + + def backup_dump_url + full_asset_url(backup.dump.url) + end + end +end diff --git a/spec/requests/cache_spec.rb b/spec/requests/cache_spec.rb new file mode 100644 index 000000000..d40895fc3 --- /dev/null +++ b/spec/requests/cache_spec.rb @@ -0,0 +1,692 @@ +# frozen_string_literal: true + +require 'rails_helper' + +module TestEndpoints + # Endpoints that do not include authorization-dependent results + # and should be cacheable no matter what. + ALWAYS_CACHED = %w( + /.well-known/host-meta + /.well-known/nodeinfo + /nodeinfo/2.0 + /manifest + /custom.css + /actor + /api/v1/instance/extended_description + /api/v1/instance/rules + /api/v1/instance/peers + /api/v1/instance + /api/v2/instance + ).freeze + + # Endpoints that should be cachable when accessed anonymously but have a Vary + # on Cookie to prevent logged-in users from getting values from logged-out cache. + COOKIE_DEPENDENT_CACHABLE = %w( + / + /explore + /public + /about + /privacy-policy + /directory + /@alice + /@alice/110224538612341312 + /deck/home + ).freeze + + # Endpoints that should be cachable when accessed anonymously but have a Vary + # on Authorization to prevent logged-in users from getting values from logged-out cache. + AUTHORIZATION_DEPENDENT_CACHABLE = %w( + /api/v1/accounts/lookup?acct=alice + /api/v1/statuses/110224538612341312 + /api/v1/statuses/110224538612341312/context + /api/v1/polls/12345 + /api/v1/trends/statuses + /api/v1/directory + ).freeze + + # Private status that should only be returned with to a valid signature from + # a specific user. + # Should never be cached. + REQUIRE_SIGNATURE = %w( + /users/alice/statuses/110224538643211312 + ).freeze + + # Pages only available to logged-in users. + # Should never be cached. + REQUIRE_LOGIN = %w( + /settings/preferences/appearance + /settings/profile + /settings/featured_tags + /settings/export + /relationships + /filters + /statuses_cleanup + /auth/edit + /oauth/authorized_applications + /admin/dashboard + ).freeze + + # API endpoints only available to logged-in users. + # Should never be cached. + REQUIRE_TOKEN = %w( + /api/v1/announcements + /api/v1/timelines/home + /api/v1/notifications + /api/v1/bookmarks + /api/v1/favourites + /api/v1/follow_requests + /api/v1/conversations + /api/v1/statuses/110224538643211312 + /api/v1/statuses/110224538643211312/context + /api/v1/lists + /api/v2/filters + ).freeze + + # Pages that are only shown to logged-out users, and should never get cached + # because of CSRF protection. + REQUIRE_LOGGED_OUT = %w( + /invite/abcdef + /auth/sign_in + /auth/sign_up + /auth/password/new + /auth/confirmation/new + ).freeze + + # Non-exhaustive list of endpoints that feature language-dependent results + # and thus need to have a Vary on Accept-Language + LANGUAGE_DEPENDENT = %w( + / + /explore + /about + /api/v1/trends/statuses + ).freeze + + module AuthorizedFetch + # Endpoints that require a signature with AUTHORIZED_FETCH and LIMITED_FEDERATION_MODE + # and thus should not be cached in those modes. + REQUIRE_SIGNATURE = %w( + /users/alice + ).freeze + end + + module DisabledAnonymousAPI + # Endpoints that require a signature with DISALLOW_UNAUTHENTICATED_API_ACCESS + # and thus should not be cached in this mode. + REQUIRE_TOKEN = %w( + /api/v1/custom_emojis + ).freeze + end +end + +describe 'Caching behavior' do + shared_examples 'cachable response' do + it 'does not set cookies' do + expect(response.cookies).to be_empty + end + + it 'sets public cache control', :aggregate_failures do + # expect(response.cache_control[:max_age]&.to_i).to be_positive + expect(response.cache_control[:public]).to be_truthy + expect(response.cache_control[:private]).to be_falsy + expect(response.cache_control[:no_store]).to be_falsy + expect(response.cache_control[:no_cache]).to be_falsy + end + end + + shared_examples 'non-cacheable response' do + it 'sets private cache control' do + expect(response.cache_control[:private]).to be_truthy + expect(response.cache_control[:no_store]).to be_truthy + end + end + + shared_examples 'non-cacheable error' do + it 'does not return HTTP success and does not have cache headers', :aggregate_failures do + expect(response).to_not have_http_status(200) + expect(response.cache_control[:public]).to be_falsy + end + end + + shared_examples 'language-dependent' do + it 'has a Vary on Accept-Language' do + expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('accept-language') + end + end + + # Enable CSRF protection like it is in production, as it can cause cookies + # to be set and thus mess with cache. + around do |example| + old = ActionController::Base.allow_forgery_protection + ActionController::Base.allow_forgery_protection = true + + example.run + + ActionController::Base.allow_forgery_protection = old + end + + let(:alice) { Fabricate(:account, username: 'alice') } + let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Moderator')) } + + before do + # rubocop:disable Style/NumericLiterals + status = Fabricate(:status, account: alice, id: 110224538612341312) + Fabricate(:status, account: alice, id: 110224538643211312, visibility: :private) + Fabricate(:invite, code: 'abcdef') + Fabricate(:poll, status: status, account: alice, id: 12345) + # rubocop:enable Style/NumericLiterals + + user.account.follow!(alice) + end + + context 'when anonymously accessed' do + describe '/users/alice' do + it 'redirects with proper cache header', :aggregate_failures do + get '/users/alice' + + expect(response).to redirect_to('/@alice') + expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('accept') + end + end + + TestEndpoints::ALWAYS_CACHED.each do |endpoint| + describe endpoint do + before { get endpoint } + + it_behaves_like 'cachable response' + it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint) + end + end + + TestEndpoints::COOKIE_DEPENDENT_CACHABLE.each do |endpoint| + describe endpoint do + before { get endpoint } + + it_behaves_like 'cachable response' + + it 'has a Vary on Cookie' do + expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('cookie') + end + + it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint) + end + end + + TestEndpoints::AUTHORIZATION_DEPENDENT_CACHABLE.each do |endpoint| + describe endpoint do + before { get endpoint } + + it_behaves_like 'cachable response' + + it 'has a Vary on Authorization' do + expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization') + end + + it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint) + end + end + + TestEndpoints::REQUIRE_LOGGED_OUT.each do |endpoint| + describe endpoint do + before { get endpoint } + + it_behaves_like 'non-cacheable response' + end + end + + (TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::REQUIRE_LOGIN + TestEndpoints::REQUIRE_TOKEN).each do |endpoint| + describe endpoint do + before { get endpoint } + + it_behaves_like 'non-cacheable error' + end + end + + describe '/api/v1/instance/domain_blocks' do + around do |example| + old_setting = Setting.show_domain_blocks + Setting.show_domain_blocks = show_domain_blocks + + example.run + + Setting.show_domain_blocks = old_setting + end + + before { get '/api/v1/instance/domain_blocks' } + + context 'when set to be publicly-available' do + let(:show_domain_blocks) { 'all' } + + it_behaves_like 'cachable response' + end + + context 'when allowed for local users only' do + let(:show_domain_blocks) { 'users' } + + it_behaves_like 'non-cacheable error' + end + + context 'when disabled' do + let(:show_domain_blocks) { 'disabled' } + + it_behaves_like 'non-cacheable error' + end + end + end + + context 'when logged in' do + before do + sign_in user, scope: :user + + # Unfortunately, devise's `sign_in` helper causes the `session` to be + # loaded in the next request regardless of whether it's actually accessed + # by the client code. + # + # So, we make an extra query to clear issue a session cookie instead. + # + # A less resource-intensive way to deal with that would be to generate the + # session cookie manually, but this seems pretty involved. + get '/' + end + + TestEndpoints::ALWAYS_CACHED.each do |endpoint| + describe endpoint do + before { get endpoint } + + it_behaves_like 'cachable response' + it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint) + end + end + + TestEndpoints::COOKIE_DEPENDENT_CACHABLE.each do |endpoint| + describe endpoint do + before { get endpoint } + + it_behaves_like 'non-cacheable response' + + it 'has a Vary on Cookie' do + expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('cookie') + end + end + end + + TestEndpoints::REQUIRE_LOGIN.each do |endpoint| + describe endpoint do + before { get endpoint } + + it_behaves_like 'non-cacheable response' + + it 'returns HTTP success' do + expect(response).to have_http_status(200) + end + end + end + + TestEndpoints::REQUIRE_LOGGED_OUT.each do |endpoint| + describe endpoint do + before { get endpoint } + + it_behaves_like 'non-cacheable error' + end + end + end + + context 'with an auth token' do + let!(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') } + + TestEndpoints::ALWAYS_CACHED.each do |endpoint| + describe endpoint do + before do + get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" } + end + + it_behaves_like 'cachable response' + it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint) + end + end + + TestEndpoints::AUTHORIZATION_DEPENDENT_CACHABLE.each do |endpoint| + describe endpoint do + before do + get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" } + end + + it_behaves_like 'non-cacheable response' + + it 'has a Vary on Authorization' do + expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization') + end + end + end + + (TestEndpoints::REQUIRE_LOGGED_OUT + TestEndpoints::REQUIRE_TOKEN).each do |endpoint| + describe endpoint do + before do + get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" } + end + + it_behaves_like 'non-cacheable response' + + it 'returns HTTP success' do + expect(response).to have_http_status(200) + end + end + end + + describe '/api/v1/instance/domain_blocks' do + around do |example| + old_setting = Setting.show_domain_blocks + Setting.show_domain_blocks = show_domain_blocks + + example.run + + Setting.show_domain_blocks = old_setting + end + + before do + get '/api/v1/instance/domain_blocks', headers: { 'Authorization' => "Bearer #{token.token}" } + end + + context 'when set to be publicly-available' do + let(:show_domain_blocks) { 'all' } + + it_behaves_like 'cachable response' + end + + context 'when allowed for local users only' do + let(:show_domain_blocks) { 'users' } + + it_behaves_like 'non-cacheable response' + + it 'returns HTTP success' do + expect(response).to have_http_status(200) + end + end + + context 'when disabled' do + let(:show_domain_blocks) { 'disabled' } + + it_behaves_like 'non-cacheable error' + end + end + end + + context 'with a Signature header' do + let(:remote_actor) { Fabricate(:account, domain: 'example.org', uri: 'https://example.org/remote', protocol: :activitypub) } + let(:dummy_signature) { 'dummy-signature' } + + before do + remote_actor.follow!(alice) + end + + describe '/actor' do + before do + get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' } + end + + it_behaves_like 'cachable response' + + it 'returns HTTP success' do + expect(response).to have_http_status(200) + end + end + + TestEndpoints::REQUIRE_SIGNATURE.each do |endpoint| + describe endpoint do + before do + get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' } + end + + it_behaves_like 'non-cacheable response' + + it 'returns HTTP success' do + expect(response).to have_http_status(200) + end + end + end + end + + context 'when enabling AUTHORIZED_FETCH mode' do + around do |example| + ClimateControl.modify AUTHORIZED_FETCH: 'true' do + example.run + end + end + + context 'when not providing a Signature' do + describe '/actor' do + before do + get '/actor', headers: { 'Accept' => 'application/activity+json' } + end + + it_behaves_like 'cachable response' + + it 'returns HTTP success' do + expect(response).to have_http_status(200) + end + end + + (TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint| + describe endpoint do + before do + get endpoint, headers: { 'Accept' => 'application/activity+json' } + end + + it_behaves_like 'non-cacheable error' + end + end + end + + context 'when providing a Signature' do + let(:remote_actor) { Fabricate(:account, domain: 'example.org', uri: 'https://example.org/remote', protocol: :activitypub) } + let(:dummy_signature) { 'dummy-signature' } + + before do + remote_actor.follow!(alice) + end + + describe '/actor' do + before do + get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' } + end + + it_behaves_like 'cachable response' + + it 'returns HTTP success' do + expect(response).to have_http_status(200) + end + end + + (TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint| + describe endpoint do + before do + get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' } + end + + it_behaves_like 'non-cacheable response' + + it 'returns HTTP success' do + expect(response).to have_http_status(200) + end + end + end + end + end + + context 'when enabling LIMITED_FEDERATION_MODE mode' do + around do |example| + ClimateControl.modify LIMITED_FEDERATION_MODE: 'true' do + old_limited_federation_mode = Rails.configuration.x.limited_federation_mode + Rails.configuration.x.limited_federation_mode = true + + example.run + + Rails.configuration.x.limited_federation_mode = old_limited_federation_mode + end + end + + context 'when not providing a Signature' do + describe '/actor' do + before do + get '/actor', headers: { 'Accept' => 'application/activity+json' } + end + + it_behaves_like 'cachable response' + + it 'returns HTTP success' do + expect(response).to have_http_status(200) + end + end + + (TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint| + describe endpoint do + before do + get endpoint, headers: { 'Accept' => 'application/activity+json' } + end + + it_behaves_like 'non-cacheable error' + end + end + end + + context 'when providing a Signature from an allowed domain' do + let(:remote_actor) { Fabricate(:account, domain: 'example.org', uri: 'https://example.org/remote', protocol: :activitypub) } + let(:dummy_signature) { 'dummy-signature' } + + before do + DomainAllow.create!(domain: remote_actor.domain) + remote_actor.follow!(alice) + end + + describe '/actor' do + before do + get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' } + end + + it_behaves_like 'cachable response' + + it 'returns HTTP success' do + expect(response).to have_http_status(200) + end + end + + (TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint| + describe endpoint do + before do + get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' } + end + + it_behaves_like 'non-cacheable response' + + it 'returns HTTP success' do + expect(response).to have_http_status(200) + end + end + end + end + + context 'when providing a Signature from a non-allowed domain' do + let(:remote_actor) { Fabricate(:account, domain: 'example.org', uri: 'https://example.org/remote', protocol: :activitypub) } + let(:dummy_signature) { 'dummy-signature' } + + describe '/actor' do + before do + get '/actor', sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' } + end + + it_behaves_like 'cachable response' + + it 'returns HTTP success' do + expect(response).to have_http_status(200) + end + end + + (TestEndpoints::REQUIRE_SIGNATURE + TestEndpoints::AuthorizedFetch::REQUIRE_SIGNATURE).each do |endpoint| + describe endpoint do + before do + get endpoint, sign_with: remote_actor, headers: { 'Accept' => 'application/activity+json' } + end + + it_behaves_like 'non-cacheable error' + end + end + end + end + + context 'when enabling DISALLOW_UNAUTHENTICATED_API_ACCESS' do + around do |example| + ClimateControl.modify DISALLOW_UNAUTHENTICATED_API_ACCESS: 'true' do + example.run + end + end + + context 'when anonymously accessed' do + TestEndpoints::ALWAYS_CACHED.each do |endpoint| + describe endpoint do + before { get endpoint } + + it_behaves_like 'cachable response' + it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint) + end + end + + TestEndpoints::REQUIRE_LOGGED_OUT.each do |endpoint| + describe endpoint do + before { get endpoint } + + it_behaves_like 'non-cacheable response' + end + end + + (TestEndpoints::REQUIRE_TOKEN + TestEndpoints::AUTHORIZATION_DEPENDENT_CACHABLE + TestEndpoints::DisabledAnonymousAPI::REQUIRE_TOKEN).each do |endpoint| + describe endpoint do + before { get endpoint } + + it_behaves_like 'non-cacheable error' + end + end + end + + context 'with an auth token' do + let!(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') } + + TestEndpoints::ALWAYS_CACHED.each do |endpoint| + describe endpoint do + before do + get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" } + end + + it_behaves_like 'cachable response' + it_behaves_like 'language-dependent' if TestEndpoints::LANGUAGE_DEPENDENT.include?(endpoint) + end + end + + TestEndpoints::AUTHORIZATION_DEPENDENT_CACHABLE.each do |endpoint| + describe endpoint do + before do + get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" } + end + + it_behaves_like 'non-cacheable response' + + it 'has a Vary on Authorization' do + expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('authorization') + end + end + end + + (TestEndpoints::REQUIRE_LOGGED_OUT + TestEndpoints::REQUIRE_TOKEN + TestEndpoints::DisabledAnonymousAPI::REQUIRE_TOKEN).each do |endpoint| + describe endpoint do + before do + get endpoint, headers: { 'Authorization' => "Bearer #{token.token}" } + end + + it_behaves_like 'non-cacheable response' + + it 'returns HTTP success' do + expect(response).to have_http_status(200) + end + end + end + end + end +end diff --git a/spec/requests/catch_all_route_request_spec.rb b/spec/requests/catch_all_route_request_spec.rb index f965f5522..e600bedfe 100644 --- a/spec/requests/catch_all_route_request_spec.rb +++ b/spec/requests/catch_all_route_request_spec.rb @@ -1,21 +1,23 @@ -require "rails_helper" +# frozen_string_literal: true -describe "The catch all route" do - describe "with a simple value" do - it "returns a 404 page as html" do - get "/test" +require 'rails_helper' - expect(response.status).to eq 404 - expect(response.media_type).to eq "text/html" +describe 'The catch all route' do + describe 'with a simple value' do + it 'returns a 404 page as html' do + get '/test' + + expect(response).to have_http_status 404 + expect(response.media_type).to eq 'text/html' end end - describe "with an implied format" do - it "returns a 404 page as html" do - get "/test.test" + describe 'with an implied format' do + it 'returns a 404 page as html' do + get '/test.test' - expect(response.status).to eq 404 - expect(response.media_type).to eq "text/html" + expect(response).to have_http_status 404 + expect(response.media_type).to eq 'text/html' end end end diff --git a/spec/requests/content_security_policy_spec.rb b/spec/requests/content_security_policy_spec.rb index 91158fe59..7eb27d61d 100644 --- a/spec/requests/content_security_policy_spec.rb +++ b/spec/requests/content_security_policy_spec.rb @@ -17,6 +17,7 @@ describe 'Content-Security-Policy' do "media-src 'self' https: data: https://cb6e6126.ngrok.io", "frame-src 'self' https:", "manifest-src 'self' https://cb6e6126.ngrok.io", + "form-action 'self'", "child-src 'self' blob: https://cb6e6126.ngrok.io", "worker-src 'self' blob: https://cb6e6126.ngrok.io", "connect-src 'self' data: blob: https://cb6e6126.ngrok.io https://cb6e6126.ngrok.io ws://localhost:4000", diff --git a/spec/requests/follower_accounts_spec.rb b/spec/requests/follower_accounts_spec.rb new file mode 100644 index 000000000..52e86e13f --- /dev/null +++ b/spec/requests/follower_accounts_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'FollowerAccountsController' do + describe 'The follower_accounts route' do + it "returns a http 'moved_permanently' code" do + get '/users/:username/followers' + + expect(response).to have_http_status(301) + end + end +end diff --git a/spec/requests/following_accounts_spec.rb b/spec/requests/following_accounts_spec.rb new file mode 100644 index 000000000..f0955ceb3 --- /dev/null +++ b/spec/requests/following_accounts_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'FollowingAccountsController' do + describe 'The following_accounts route' do + it "returns a http 'moved_permanently' code" do + get '/users/:username/following' + + expect(response).to have_http_status(301) + end + end +end diff --git a/spec/requests/host_meta_request_spec.rb b/spec/requests/host_meta_request_spec.rb index 0ca641461..ec26ecba7 100644 --- a/spec/requests/host_meta_request_spec.rb +++ b/spec/requests/host_meta_request_spec.rb @@ -1,12 +1,14 @@ -require "rails_helper" +# frozen_string_literal: true -describe "The host_meta route" do - describe "requested without accepts headers" do - it "returns an xml response" do +require 'rails_helper' + +describe 'The host_meta route' do + describe 'requested without accepts headers' do + it 'returns an xml response' do get host_meta_url expect(response).to have_http_status(200) - expect(response.media_type).to eq "application/xrd+xml" + expect(response.media_type).to eq 'application/xrd+xml' end end end diff --git a/spec/requests/link_headers_spec.rb b/spec/requests/link_headers_spec.rb index c32e0f79a..b822adbfb 100644 --- a/spec/requests/link_headers_spec.rb +++ b/spec/requests/link_headers_spec.rb @@ -13,7 +13,7 @@ describe 'Link headers' do it 'contains webfinger url in link header' do link_header = link_header_with_type('application/jrd+json') - expect(link_header.href).to match 'http://www.example.com/.well-known/webfinger?resource=acct%3Atest%40cb6e6126.ngrok.io' + expect(link_header.href).to eq 'http://www.example.com/.well-known/webfinger?resource=acct%3Atest%40cb6e6126.ngrok.io' expect(link_header.attr_pairs.first).to eq %w(rel lrdd) end @@ -26,7 +26,7 @@ describe 'Link headers' do def link_header_with_type(type) LinkHeader.parse(response.headers['Link'].to_s).links.find do |link| - link.attr_pairs.any? { |pair| pair == ['type', type] } + link.attr_pairs.any?(['type', type]) end end end diff --git a/spec/requests/localization_spec.rb b/spec/requests/localization_spec.rb index 0bc2786ac..b7fb53ed8 100644 --- a/spec/requests/localization_spec.rb +++ b/spec/requests/localization_spec.rb @@ -3,14 +3,16 @@ require 'rails_helper' describe 'Localization' do - after(:all) do - I18n.locale = I18n.default_locale + around do |example| + I18n.with_locale(I18n.locale) do + example.run + end end it 'uses a specific region when provided' do headers = { 'Accept-Language' => 'zh-HK' } - get "/auth/sign_in", headers: headers + get '/auth/sign_in', headers: headers expect(response.body).to include( I18n.t('auth.login', locale: 'zh-HK') @@ -20,7 +22,7 @@ describe 'Localization' do it 'falls back to a locale when region missing' do headers = { 'Accept-Language' => 'es-FAKE' } - get "/auth/sign_in", headers: headers + get '/auth/sign_in', headers: headers expect(response.body).to include( I18n.t('auth.login', locale: 'es') @@ -30,7 +32,7 @@ describe 'Localization' do it 'falls back to english when locale is missing' do headers = { 'Accept-Language' => '12-FAKE' } - get "/auth/sign_in", headers: headers + get '/auth/sign_in', headers: headers expect(response.body).to include( I18n.t('auth.login', locale: 'en') diff --git a/spec/requests/mail_subscriptions_spec.rb b/spec/requests/mail_subscriptions_spec.rb new file mode 100644 index 000000000..cc6557cab --- /dev/null +++ b/spec/requests/mail_subscriptions_spec.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'MailSubscriptionsController' do + let(:user) { Fabricate(:user) } + let(:token) { user.to_sgid(for: 'unsubscribe').to_s } + let(:type) { 'follow' } + + shared_examples 'not found with invalid token' do + context 'with invalid token' do + let(:token) { 'invalid-token' } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + + shared_examples 'not found with invalid type' do + context 'with invalid type' do + let(:type) { 'invalid_type' } + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + end + end + + describe 'on the unsubscribe confirmation page' do + before do + get unsubscribe_url(token: token, type: type) + end + + it_behaves_like 'not found with invalid token' + it_behaves_like 'not found with invalid type' + + it 'shows unsubscribe form' do + expect(response).to have_http_status(200) + + expect(response.body).to include( + I18n.t('mail_subscriptions.unsubscribe.action') + ) + expect(response.body).to include(user.email) + end + end + + describe 'submitting the unsubscribe confirmation page' do + before do + user.settings.update('notification_emails.follow': true) + user.save! + + post unsubscribe_url, params: { token: token, type: type } + end + + it_behaves_like 'not found with invalid token' + it_behaves_like 'not found with invalid type' + + it 'shows confirmation page' do + expect(response).to have_http_status(200) + + expect(response.body).to include( + I18n.t('mail_subscriptions.unsubscribe.complete') + ) + expect(response.body).to include(user.email) + end + + it 'updates notification settings' do + user.reload + expect(user.settings['notification_emails.follow']).to be false + end + end + + describe 'unsubscribing with List-Unsubscribe-Post' do + around do |example| + old = ActionController::Base.allow_forgery_protection + ActionController::Base.allow_forgery_protection = true + + example.run + + ActionController::Base.allow_forgery_protection = old + end + + before do + user.settings.update('notification_emails.follow': true) + user.save! + + post unsubscribe_url(token: token, type: type), params: { 'List-Unsubscribe' => 'One-Click' } + end + + it_behaves_like 'not found with invalid token' + it_behaves_like 'not found with invalid type' + + it 'return http success' do + expect(response).to have_http_status(200) + end + + it 'updates notification settings' do + user.reload + expect(user.settings['notification_emails.follow']).to be false + end + end +end diff --git a/spec/requests/omniauth_callbacks_spec.rb b/spec/requests/omniauth_callbacks_spec.rb index 095535e48..6381bf066 100644 --- a/spec/requests/omniauth_callbacks_spec.rb +++ b/spec/requests/omniauth_callbacks_spec.rb @@ -4,7 +4,7 @@ require 'rails_helper' describe 'OmniAuth callbacks' do shared_examples 'omniauth provider callbacks' do |provider| - subject { post send :"user_#{provider}_omniauth_callback_path" } + subject { post send "user_#{provider}_omniauth_callback_path" } context 'with full information in response' do before do diff --git a/spec/requests/signature_verification_spec.rb b/spec/requests/signature_verification_spec.rb new file mode 100644 index 000000000..401828c4a --- /dev/null +++ b/spec/requests/signature_verification_spec.rb @@ -0,0 +1,398 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'signature verification concern' do + before do + stub_tests_controller + + # Signature checking is time-dependent, so travel to a fixed date + travel_to '2023-12-20T10:00:00Z' + end + + after { Rails.application.reload_routes! } + + # Include the private key so the tests can be easily adjusted and reviewed + let(:actor_keypair) do + OpenSSL::PKey.read(<<~PEM_TEXT) + -----BEGIN RSA PRIVATE KEY----- + MIIEowIBAAKCAQEAqIAYvNFGbZ5g4iiK6feSdXD4bDStFM58A7tHycYXaYtzZQpI + eHXAmaXuZzXIwtrP4N0gIk8JNwZvXj2UPS+S07t0V9wNK94he01LV5EMz/GN4eNn + FmDL64HIEuKLvV8TvgjbUPRD6Y5X0UpKi2ZIFLSb96Q5w0Z/k7ntpVKV52y8kz5F + jr/O/0JuHryZe0yItzJh8kzFfeMf0EXzfSnaKvT7P9jhgC6uTre+jXyvVZjiHDrn + qvvucdI3I7DRfXo1OqARBrLjy+TdseUAjNYJ+OuPRI1URIWQI01DCHqcohVu9+Ar + +BiCjFp3ua+XMuJvrvbD61d1Fvig/9nbBRR+8QIDAQABAoIBAAgySHnFWI6gItR3 + fkfiqIm80cHCN3Xk1C6iiVu+3oBOZbHpW9R7vl9e/WOA/9O+LPjiSsQOegtWnVvd + RRjrl7Hj20VDlZKv5Mssm6zOGAxksrcVbqwdj+fUJaNJCL0AyyseH0x/IE9T8rDC + I1GH+3tB3JkhkIN/qjipdX5ab8MswEPu8IC4ViTpdBgWYY/xBcAHPw4xuL0tcwzh + FBlf4DqoEVQo8GdK5GAJ2Ny0S4xbXHUURzx/R4y4CCts7niAiLGqd9jmLU1kUTMk + QcXfQYK6l+unLc7wDYAz7sFEHh04M48VjWwiIZJnlCqmQbLda7uhhu8zkF1DqZTu + ulWDGQECgYEA0TIAc8BQBVab979DHEEmMdgqBwxLY3OIAk0b+r50h7VBGWCDPRsC + STD73fQY3lNet/7/jgSGwwAlAJ5PpMXxXiZAE3bUwPmHzgF7pvIOOLhA8O07tHSO + L2mvQe6NPzjZ+6iAO2U9PkClxcvGvPx2OBvisfHqZLmxC9PIVxzruQECgYEAzjM6 + BTUXa6T/qHvLFbN699BXsUOGmHBGaLRapFDBfVvgZrwqYQcZpBBhesLdGTGSqwE7 + gWsITPIJ+Ldo+38oGYyVys+w/V67q6ud7hgSDTW3hSvm+GboCjk6gzxlt9hQ0t9X + 8vfDOYhEXvVUJNv3mYO60ENqQhILO4bQ0zi+VfECgYBb/nUccfG+pzunU0Cb6Dp3 + qOuydcGhVmj1OhuXxLFSDG84Tazo7juvHA9mp7VX76mzmDuhpHPuxN2AzB2SBEoE + cSW0aYld413JRfWukLuYTc6hJHIhBTCRwRQFFnae2s1hUdQySm8INT2xIc+fxBXo + zrp+Ljg5Wz90SAnN5TX0AQKBgDaatDOq0o/r+tPYLHiLtfWoE4Dau+rkWJDjqdk3 + lXWn/e3WyHY3Vh/vQpEqxzgju45TXjmwaVtPATr+/usSykCxzP0PMPR3wMT+Rm1F + rIoY/odij+CaB7qlWwxj0x/zRbwB7x1lZSp4HnrzBpxYL+JUUwVRxPLIKndSBTza + GvVRAoGBAIVBcNcRQYF4fvZjDKAb4fdBsEuHmycqtRCsnkGOz6ebbEQznSaZ0tZE + +JuouZaGjyp8uPjNGD5D7mIGbyoZ3KyG4mTXNxDAGBso1hrNDKGBOrGaPhZx8LgO + 4VXJ+ybXrATf4jr8ccZYsZdFpOphPzz+j55Mqg5vac5P1XjmsGTb + -----END RSA PRIVATE KEY----- + PEM_TEXT + end + + context 'without a Signature header' do + it 'does not treat the request as signed' do + get '/activitypub/success' + + expect(response).to have_http_status(200) + expect(body_as_json).to match( + signed_request: false, + signature_actor_id: nil, + error: 'Request not signed' + ) + end + + context 'when a signature is required' do + it 'returns http unauthorized with appropriate error' do + get '/activitypub/signature_required' + + expect(response).to have_http_status(401) + expect(body_as_json).to match( + error: 'Request not signed' + ) + end + end + end + + context 'with an HTTP Signature from a known account' do + let!(:actor) { Fabricate(:account, domain: 'remote.domain', uri: 'https://remote.domain/users/bob', private_key: nil, public_key: actor_keypair.public_key.to_pem) } + + context 'with a valid signature on a GET request' do + let(:signature_header) do + 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="Z8ilar3J7bOwqZkMp7sL8sRs4B1FT+UorbmvWoE+A5UeoOJ3KBcUmbsh+k3wQwbP5gMNUrra9rEWabpasZGphLsbDxfbsWL3Cf0PllAc7c1c7AFEwnewtExI83/qqgEkfWc2z7UDutXc2NfgAx89Ox8DXU/fA2GG0jILjB6UpFyNugkY9rg6oI31UnvfVi3R7sr3/x8Ea3I9thPvqI2byF6cojknSpDAwYzeKdngX3TAQEGzFHz3SDWwyp3jeMWfwvVVbM38FxhvAnSumw7YwWW4L7M7h4M68isLimoT3yfCn2ucBVL5Dz8koBpYf/40w7QidClAwCafZQFC29yDOg=="' # rubocop:disable Layout/LineLength + end + + it 'successfuly verifies signature', :aggregate_failures do + expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'get /activitypub/success', { 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', 'Host' => 'www.example.com' }) + + get '/activitypub/success', headers: { + 'Host' => 'www.example.com', + 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', + 'Signature' => signature_header, + } + + expect(response).to have_http_status(200) + expect(body_as_json).to match( + signed_request: true, + signature_actor_id: actor.id.to_s + ) + end + end + + context 'with a valid signature on a GET request that has a query string' do + let(:signature_header) do + 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="SDMa4r/DQYMXYxVgYO2yEqGWWUXugKjVuz0I8dniQAk+aunzBaF2aPu+4grBfawAshlx1Xytl8lhb0H2MllEz16/tKY7rUrb70MK0w8ohXgpb0qs3YvQgdj4X24L1x2MnkFfKHR/J+7TBlnivq0HZqXm8EIkPWLv+eQxu8fbowLwHIVvRd/3t6FzvcfsE0UZKkoMEX02542MhwSif6cu7Ec/clsY9qgKahb9JVGOGS1op9Lvg/9y1mc8KCgD83U5IxVygYeYXaVQ6gixA9NgZiTCwEWzHM5ELm7w5hpdLFYxYOHg/3G3fiqJzpzNQAcCD4S4JxfE7hMI0IzVlNLT6A=="' # rubocop:disable Layout/LineLength + end + + it 'successfuly verifies signature', :aggregate_failures do + expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'get /activitypub/success?foo=42', { 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', 'Host' => 'www.example.com' }) + + get '/activitypub/success?foo=42', headers: { + 'Host' => 'www.example.com', + 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', + 'Signature' => signature_header, + } + + expect(response).to have_http_status(200) + expect(body_as_json).to match( + signed_request: true, + signature_actor_id: actor.id.to_s + ) + end + end + + context 'when the query string is missing from the signature verification (compatibility quirk)' do + let(:signature_header) do + 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="Z8ilar3J7bOwqZkMp7sL8sRs4B1FT+UorbmvWoE+A5UeoOJ3KBcUmbsh+k3wQwbP5gMNUrra9rEWabpasZGphLsbDxfbsWL3Cf0PllAc7c1c7AFEwnewtExI83/qqgEkfWc2z7UDutXc2NfgAx89Ox8DXU/fA2GG0jILjB6UpFyNugkY9rg6oI31UnvfVi3R7sr3/x8Ea3I9thPvqI2byF6cojknSpDAwYzeKdngX3TAQEGzFHz3SDWwyp3jeMWfwvVVbM38FxhvAnSumw7YwWW4L7M7h4M68isLimoT3yfCn2ucBVL5Dz8koBpYf/40w7QidClAwCafZQFC29yDOg=="' # rubocop:disable Layout/LineLength + end + + it 'successfuly verifies signature', :aggregate_failures do + expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'get /activitypub/success', { 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', 'Host' => 'www.example.com' }) + + get '/activitypub/success?foo=42', headers: { + 'Host' => 'www.example.com', + 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', + 'Signature' => signature_header, + } + + expect(response).to have_http_status(200) + expect(body_as_json).to match( + signed_request: true, + signature_actor_id: actor.id.to_s + ) + end + end + + context 'with mismatching query string' do + let(:signature_header) do + 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="SDMa4r/DQYMXYxVgYO2yEqGWWUXugKjVuz0I8dniQAk+aunzBaF2aPu+4grBfawAshlx1Xytl8lhb0H2MllEz16/tKY7rUrb70MK0w8ohXgpb0qs3YvQgdj4X24L1x2MnkFfKHR/J+7TBlnivq0HZqXm8EIkPWLv+eQxu8fbowLwHIVvRd/3t6FzvcfsE0UZKkoMEX02542MhwSif6cu7Ec/clsY9qgKahb9JVGOGS1op9Lvg/9y1mc8KCgD83U5IxVygYeYXaVQ6gixA9NgZiTCwEWzHM5ELm7w5hpdLFYxYOHg/3G3fiqJzpzNQAcCD4S4JxfE7hMI0IzVlNLT6A=="' # rubocop:disable Layout/LineLength + end + + it 'fails to verify signature', :aggregate_failures do + expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'get /activitypub/success?foo=42', { 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', 'Host' => 'www.example.com' }) + + get '/activitypub/success?foo=43', headers: { + 'Host' => 'www.example.com', + 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', + 'Signature' => signature_header, + } + + expect(body_as_json).to match( + signed_request: true, + signature_actor_id: nil, + error: anything + ) + end + end + + context 'with a mismatching path' do + it 'fails to verify signature', :aggregate_failures do + get '/activitypub/alternative-path', headers: { + 'Host' => 'www.example.com', + 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', + 'Signature' => 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="Z8ilar3J7bOwqZkMp7sL8sRs4B1FT+UorbmvWoE+A5UeoOJ3KBcUmbsh+k3wQwbP5gMNUrra9rEWabpasZGphLsbDxfbsWL3Cf0PllAc7c1c7AFEwnewtExI83/qqgEkfWc2z7UDutXc2NfgAx89Ox8DXU/fA2GG0jILjB6UpFyNugkY9rg6oI31UnvfVi3R7sr3/x8Ea3I9thPvqI2byF6cojknSpDAwYzeKdngX3TAQEGzFHz3SDWwyp3jeMWfwvVVbM38FxhvAnSumw7YwWW4L7M7h4M68isLimoT3yfCn2ucBVL5Dz8koBpYf/40w7QidClAwCafZQFC29yDOg=="', # rubocop:disable Layout/LineLength + } + + expect(body_as_json).to match( + signed_request: true, + signature_actor_id: nil, + error: anything + ) + end + end + + context 'with a mismatching method' do + it 'fails to verify signature', :aggregate_failures do + post '/activitypub/success', headers: { + 'Host' => 'www.example.com', + 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', + 'Signature' => 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="Z8ilar3J7bOwqZkMp7sL8sRs4B1FT+UorbmvWoE+A5UeoOJ3KBcUmbsh+k3wQwbP5gMNUrra9rEWabpasZGphLsbDxfbsWL3Cf0PllAc7c1c7AFEwnewtExI83/qqgEkfWc2z7UDutXc2NfgAx89Ox8DXU/fA2GG0jILjB6UpFyNugkY9rg6oI31UnvfVi3R7sr3/x8Ea3I9thPvqI2byF6cojknSpDAwYzeKdngX3TAQEGzFHz3SDWwyp3jeMWfwvVVbM38FxhvAnSumw7YwWW4L7M7h4M68isLimoT3yfCn2ucBVL5Dz8koBpYf/40w7QidClAwCafZQFC29yDOg=="', # rubocop:disable Layout/LineLength + } + + expect(body_as_json).to match( + signed_request: true, + signature_actor_id: nil, + error: anything + ) + end + end + + context 'with an unparsable date' do + let(:signature_header) do + 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="d4B7nfx8RJcfdJDu1J//5WzPzK/hgtPkdzZx49lu5QhnE7qdV3lgyVimmhCFrO16bwvzIp9iRMyRLkNFxLiEeVaa1gqeKbldGSnU0B0OMjx7rFBa65vLuzWQOATDitVGiBEYqoK4v0DMuFCz2DtFaA/DIUZ3sty8bZ/Ea3U1nByLOO6MacARA3zhMSI0GNxGqsSmZmG0hPLavB3jIXoE3IDoQabMnC39jrlcO/a8h1iaxBm2WD8TejrImJullgqlJIFpKhIHI3ipQkvTGPlm9dx0y+beM06qBvWaWQcmT09eRIUefVsOAzIhUtS/7FVb/URhZvircIJDa7vtiFcmZQ=="' # rubocop:disable Layout/LineLength + end + + it 'fails to verify signature', :aggregate_failures do + expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'get /activitypub/success', { 'Date' => 'wrong date', 'Host' => 'www.example.com' }) + + get '/activitypub/success', headers: { + 'Host' => 'www.example.com', + 'Date' => 'wrong date', + 'Signature' => signature_header, + } + + expect(body_as_json).to match( + signed_request: true, + signature_actor_id: nil, + error: 'Invalid Date header: not RFC 2616 compliant date: "wrong date"' + ) + end + end + + context 'with a request older than a day' do + let(:signature_header) do + 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="G1NuJv4zgoZ3B/ZIjzDWZHK4RC+5pYee74q8/LJEMCWXhcnAomcb9YHaqk1QYfQvcBUIXw3UZ3Q9xO8F9y0i8G5mzJHfQ+OgHqCoJk8EmGwsUXJMh5s1S5YFCRt8TT12TmJZz0VMqLq85ubueSYBM7QtUE/FzFIVLvz4RysgXxaXQKzdnM6+gbUEEKdCURpXdQt2NXQhp4MAmZH3+0lQoR6VxdsK0hx0Ji2PNp1nuqFTlYqNWZazVdLBN+9rETLRmvGXknvg9jOxTTppBVWnkAIl26HtLS3wwFVvz4pJzi9OQDOvLziehVyLNbU61hky+oJ215e2HuKSe2hxHNl1MA=="' # rubocop:disable Layout/LineLength + end + + it 'fails to verify signature', :aggregate_failures do + expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'get /activitypub/success', { 'Date' => 'Wed, 18 Dec 2023 10:00:00 GMT', 'Host' => 'www.example.com' }) + + get '/activitypub/success', headers: { + 'Host' => 'www.example.com', + 'Date' => 'Wed, 18 Dec 2023 10:00:00 GMT', + 'Signature' => signature_header, + } + + expect(body_as_json).to match( + signed_request: true, + signature_actor_id: nil, + error: 'Signed request date outside acceptable time window' + ) + end + end + + context 'with a valid signature on a POST request' do + let(:digest_header) { 'SHA-256=ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=' } + let(:signature_header) do + 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="host date digest (request-target)",signature="gmhMjgMROGElJU3fpehV2acD5kMHeELi8EFP2UPHOdQ54H0r55AxIpji+J3lPe+N2qSb/4H1KXIh6f0lRu8TGSsu12OQmg5hiO8VA9flcA/mh9Lpk+qwlQZIPRqKP9xUEfqD+Z7ti5wPzDKrWAUK/7FIqWgcT/mlqB1R1MGkpMFc/q4CIs2OSNiWgA4K+Kp21oQxzC2kUuYob04gAZ7cyE/FTia5t08uv6lVYFdRsn4XNPn1MsHgFBwBMRG79ng3SyhoG4PrqBEi5q2IdLq3zfre/M6He3wlCpyO2VJNdGVoTIzeZ0Zz8jUscPV3XtWUchpGclLGSaKaq/JyNZeiYQ=="' # rubocop:disable Layout/LineLength + end + + it 'successfuly verifies signature', :aggregate_failures do + expect(digest_header).to eq digest_value('Hello world') + expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'post /activitypub/success', { 'Host' => 'www.example.com', 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', 'Digest' => digest_header }) + + post '/activitypub/success', params: 'Hello world', headers: { + 'Host' => 'www.example.com', + 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', + 'Digest' => digest_header, + 'Signature' => signature_header, + } + + expect(response).to have_http_status(200) + expect(body_as_json).to match( + signed_request: true, + signature_actor_id: actor.id.to_s + ) + end + end + + context 'when the Digest of a POST request is not signed' do + let(:digest_header) { 'SHA-256=ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=' } + let(:signature_header) do + 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="host date (request-target)",signature="CPD704CG8aCm8X8qIP8kkkiGp1qwFLk/wMVQHOGP0Txxan8c2DZtg/KK7eN8RG8tHx8br/yS2hJs51x4kXImYukGzNJd7ihE3T8lp+9RI1tCcdobTzr/VcVJHDFySdQkg266GCMijRQRZfNvqlJLiisr817PI+gNVBI5qV+vnVd1XhWCEZ+YSmMe8UqYARXAYNqMykTheojqGpTeTFGPUpTQA2Fmt2BipwIjcFDm2Hpihl2kB0MUS0x3zPmHDuadvzoBbN6m3usPDLgYrpALlh+wDs1dYMntcwdwawRKY1oE1XNtgOSum12wntDq3uYL4gya2iPdcw3c929b4koUzw=="' # rubocop:disable Layout/LineLength + end + + it 'fails to verify signature', :aggregate_failures do + expect(digest_header).to eq digest_value('Hello world') + expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'post /activitypub/success', { 'Host' => 'www.example.com', 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT' }) + + post '/activitypub/success', params: 'Hello world', headers: { + 'Host' => 'www.example.com', + 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', + 'Digest' => digest_header, + 'Signature' => signature_header, + } + + expect(body_as_json).to match( + signed_request: true, + signature_actor_id: nil, + error: 'Mastodon requires the Digest header to be signed when doing a POST request' + ) + end + end + + context 'with a tampered body on a POST request' do + let(:digest_header) { 'SHA-256=ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=' } + let(:signature_header) do + 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="host date digest (request-target)",signature="gmhMjgMROGElJU3fpehV2acD5kMHeELi8EFP2UPHOdQ54H0r55AxIpji+J3lPe+N2qSb/4H1KXIh6f0lRu8TGSsu12OQmg5hiO8VA9flcA/mh9Lpk+qwlQZIPRqKP9xUEfqD+Z7ti5wPzDKrWAUK/7FIqWgcT/mlqB1R1MGkpMFc/q4CIs2OSNiWgA4K+Kp21oQxzC2kUuYob04gAZ7cyE/FTia5t08uv6lVYFdRsn4XNPn1MsHgFBwBMRG79ng3SyhoG4PrqBEi5q2IdLq3zfre/M6He3wlCpyO2VJNdGVoTIzeZ0Zz8jUscPV3XtWUchpGclLGSaKaq/JyNZeiYQ=="' # rubocop:disable Layout/LineLength + end + + it 'fails to verify signature', :aggregate_failures do + expect(digest_header).to_not eq digest_value('Hello world!') + expect(signature_header).to eq build_signature_string(actor_keypair, 'https://remote.domain/users/bob#main-key', 'post /activitypub/success', { 'Host' => 'www.example.com', 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', 'Digest' => digest_header }) + + post '/activitypub/success', params: 'Hello world!', headers: { + 'Host' => 'www.example.com', + 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', + 'Digest' => 'SHA-256=ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=', + 'Signature' => signature_header, + } + + expect(body_as_json).to match( + signed_request: true, + signature_actor_id: nil, + error: 'Invalid Digest value. Computed SHA-256 digest: wFNeS+K3n/2TKRMFQ2v4iTFOSj+uwF7P/Lt98xrZ5Ro=; given: ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=' + ) + end + end + + context 'with a tampered path in a POST request' do + it 'fails to verify signature', :aggregate_failures do + post '/activitypub/alternative-path', params: 'Hello world', headers: { + 'Host' => 'www.example.com', + 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', + 'Digest' => 'SHA-256=ZOyIygCyaOW6GjVnihtTFtIS9PNmskdyMlNKiuyjfzw=', + 'Signature' => 'keyId="https://remote.domain/users/bob#main-key",algorithm="rsa-sha256",headers="host date digest (request-target)",signature="gmhMjgMROGElJU3fpehV2acD5kMHeELi8EFP2UPHOdQ54H0r55AxIpji+J3lPe+N2qSb/4H1KXIh6f0lRu8TGSsu12OQmg5hiO8VA9flcA/mh9Lpk+qwlQZIPRqKP9xUEfqD+Z7ti5wPzDKrWAUK/7FIqWgcT/mlqB1R1MGkpMFc/q4CIs2OSNiWgA4K+Kp21oQxzC2kUuYob04gAZ7cyE/FTia5t08uv6lVYFdRsn4XNPn1MsHgFBwBMRG79ng3SyhoG4PrqBEi5q2IdLq3zfre/M6He3wlCpyO2VJNdGVoTIzeZ0Zz8jUscPV3XtWUchpGclLGSaKaq/JyNZeiYQ=="', # rubocop:disable Layout/LineLength + } + + expect(response).to have_http_status(200) + expect(body_as_json).to match( + signed_request: true, + signature_actor_id: nil, + error: anything + ) + end + end + end + + context 'with an inaccessible key' do + before do + stub_request(:get, 'https://remote.domain/users/alice#main-key').to_return(status: 404) + end + + it 'fails to verify signature', :aggregate_failures do + get '/activitypub/success', headers: { + 'Host' => 'www.example.com', + 'Date' => 'Wed, 20 Dec 2023 10:00:00 GMT', + 'Signature' => 'keyId="https://remote.domain/users/alice#main-key",algorithm="rsa-sha256",headers="date host (request-target)",signature="Z8ilar3J7bOwqZkMp7sL8sRs4B1FT+UorbmvWoE+A5UeoOJ3KBcUmbsh+k3wQwbP5gMNUrra9rEWabpasZGphLsbDxfbsWL3Cf0PllAc7c1c7AFEwnewtExI83/qqgEkfWc2z7UDutXc2NfgAx89Ox8DXU/fA2GG0jILjB6UpFyNugkY9rg6oI31UnvfVi3R7sr3/x8Ea3I9thPvqI2byF6cojknSpDAwYzeKdngX3TAQEGzFHz3SDWwyp3jeMWfwvVVbM38FxhvAnSumw7YwWW4L7M7h4M68isLimoT3yfCn2ucBVL5Dz8koBpYf/40w7QidClAwCafZQFC29yDOg=="', # rubocop:disable Layout/LineLength + } + + expect(body_as_json).to match( + signed_request: true, + signature_actor_id: nil, + error: 'Unable to fetch key JSON at https://remote.domain/users/alice#main-key' + ) + end + end + + private + + def stub_tests_controller + stub_const('ActivityPub::TestsController', activitypub_tests_controller) + + Rails.application.routes.draw do + # NOTE: RouteSet#draw removes all routes, so we need to re-insert one + resource :instance_actor, path: 'actor', only: [:show] + + match :via => [:get, :post], '/activitypub/success' => 'activitypub/tests#success' + match :via => [:get, :post], '/activitypub/alternative-path' => 'activitypub/tests#alternative_success' + match :via => [:get, :post], '/activitypub/signature_required' => 'activitypub/tests#signature_required' + end + end + + def activitypub_tests_controller + Class.new(ApplicationController) do + include SignatureVerification + + before_action :require_actor_signature!, only: [:signature_required] + + def success + render json: { + signed_request: signed_request?, + signature_actor_id: signed_request_actor&.id&.to_s, + }.merge(signature_verification_failure_reason || {}) + end + + alias_method :alternative_success, :success + alias_method :signature_required, :success + end + end + + def digest_value(body) + "SHA-256=#{Digest::SHA256.base64digest(body)}" + end + + def build_signature_string(keypair, key_id, request_target, headers) + algorithm = 'rsa-sha256' + signed_headers = headers.merge({ '(request-target)' => request_target }) + signed_string = signed_headers.map { |key, value| "#{key.downcase}: #{value}" }.join("\n") + signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string)) + + "keyId=\"#{key_id}\",algorithm=\"#{algorithm}\",headers=\"#{signed_headers.keys.join(' ').downcase}\",signature=\"#{signature}\"" + end +end diff --git a/spec/requests/webfinger_request_spec.rb b/spec/requests/webfinger_request_spec.rb index 209fda72a..68a1478be 100644 --- a/spec/requests/webfinger_request_spec.rb +++ b/spec/requests/webfinger_request_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe 'The webfinger route' do diff --git a/spec/routing/accounts_routing_spec.rb b/spec/routing/accounts_routing_spec.rb index 3f0e9b3e9..8b2c124fd 100644 --- a/spec/routing/accounts_routing_spec.rb +++ b/spec/routing/accounts_routing_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe 'Routes under accounts/' do diff --git a/spec/routing/api_routing_spec.rb b/spec/routing/api_routing_spec.rb index 2683ccb8d..a822fba4c 100644 --- a/spec/routing/api_routing_spec.rb +++ b/spec/routing/api_routing_spec.rb @@ -5,99 +5,99 @@ require 'rails_helper' describe 'API routes' do describe 'Credentials routes' do it 'routes to verify credentials' do - expect(get('/api/v1/accounts/verify_credentials')). - to route_to('api/v1/accounts/credentials#show') + expect(get('/api/v1/accounts/verify_credentials')) + .to route_to('api/v1/accounts/credentials#show') end it 'routes to update credentials' do - expect(patch('/api/v1/accounts/update_credentials')). - to route_to('api/v1/accounts/credentials#update') + expect(patch('/api/v1/accounts/update_credentials')) + .to route_to('api/v1/accounts/credentials#update') end end describe 'Account routes' do it 'routes to statuses' do - expect(get('/api/v1/accounts/user/statuses')). - to route_to('api/v1/accounts/statuses#index', account_id: 'user') + expect(get('/api/v1/accounts/user/statuses')) + .to route_to('api/v1/accounts/statuses#index', account_id: 'user') end it 'routes to followers' do - expect(get('/api/v1/accounts/user/followers')). - to route_to('api/v1/accounts/follower_accounts#index', account_id: 'user') + expect(get('/api/v1/accounts/user/followers')) + .to route_to('api/v1/accounts/follower_accounts#index', account_id: 'user') end it 'routes to following' do - expect(get('/api/v1/accounts/user/following')). - to route_to('api/v1/accounts/following_accounts#index', account_id: 'user') + expect(get('/api/v1/accounts/user/following')) + .to route_to('api/v1/accounts/following_accounts#index', account_id: 'user') end it 'routes to search' do - expect(get('/api/v1/accounts/search')). - to route_to('api/v1/accounts/search#show') + expect(get('/api/v1/accounts/search')) + .to route_to('api/v1/accounts/search#show') end it 'routes to relationships' do - expect(get('/api/v1/accounts/relationships')). - to route_to('api/v1/accounts/relationships#index') + expect(get('/api/v1/accounts/relationships')) + .to route_to('api/v1/accounts/relationships#index') end end describe 'Statuses routes' do it 'routes reblogged_by' do - expect(get('/api/v1/statuses/123/reblogged_by')). - to route_to('api/v1/statuses/reblogged_by_accounts#index', status_id: '123') + expect(get('/api/v1/statuses/123/reblogged_by')) + .to route_to('api/v1/statuses/reblogged_by_accounts#index', status_id: '123') end it 'routes favourited_by' do - expect(get('/api/v1/statuses/123/favourited_by')). - to route_to('api/v1/statuses/favourited_by_accounts#index', status_id: '123') + expect(get('/api/v1/statuses/123/favourited_by')) + .to route_to('api/v1/statuses/favourited_by_accounts#index', status_id: '123') end it 'routes reblog' do - expect(post('/api/v1/statuses/123/reblog')). - to route_to('api/v1/statuses/reblogs#create', status_id: '123') + expect(post('/api/v1/statuses/123/reblog')) + .to route_to('api/v1/statuses/reblogs#create', status_id: '123') end it 'routes unreblog' do - expect(post('/api/v1/statuses/123/unreblog')). - to route_to('api/v1/statuses/reblogs#destroy', status_id: '123') + expect(post('/api/v1/statuses/123/unreblog')) + .to route_to('api/v1/statuses/reblogs#destroy', status_id: '123') end it 'routes favourite' do - expect(post('/api/v1/statuses/123/favourite')). - to route_to('api/v1/statuses/favourites#create', status_id: '123') + expect(post('/api/v1/statuses/123/favourite')) + .to route_to('api/v1/statuses/favourites#create', status_id: '123') end it 'routes unfavourite' do - expect(post('/api/v1/statuses/123/unfavourite')). - to route_to('api/v1/statuses/favourites#destroy', status_id: '123') + expect(post('/api/v1/statuses/123/unfavourite')) + .to route_to('api/v1/statuses/favourites#destroy', status_id: '123') end it 'routes mute' do - expect(post('/api/v1/statuses/123/mute')). - to route_to('api/v1/statuses/mutes#create', status_id: '123') + expect(post('/api/v1/statuses/123/mute')) + .to route_to('api/v1/statuses/mutes#create', status_id: '123') end it 'routes unmute' do - expect(post('/api/v1/statuses/123/unmute')). - to route_to('api/v1/statuses/mutes#destroy', status_id: '123') + expect(post('/api/v1/statuses/123/unmute')) + .to route_to('api/v1/statuses/mutes#destroy', status_id: '123') end end describe 'Timeline routes' do it 'routes to home timeline' do - expect(get('/api/v1/timelines/home')). - to route_to('api/v1/timelines/home#show') + expect(get('/api/v1/timelines/home')) + .to route_to('api/v1/timelines/home#show') end it 'routes to public timeline' do - expect(get('/api/v1/timelines/public')). - to route_to('api/v1/timelines/public#show') + expect(get('/api/v1/timelines/public')) + .to route_to('api/v1/timelines/public#show') end it 'routes to tag timeline' do - expect(get('/api/v1/timelines/tag/test')). - to route_to('api/v1/timelines/tag#show', id: 'test') + expect(get('/api/v1/timelines/tag/test')) + .to route_to('api/v1/timelines/tag#show', id: 'test') end end end diff --git a/spec/routing/well_known_routes_spec.rb b/spec/routing/well_known_routes_spec.rb index 2e25605c2..8cf08c13c 100644 --- a/spec/routing/well_known_routes_spec.rb +++ b/spec/routing/well_known_routes_spec.rb @@ -1,15 +1,19 @@ +# frozen_string_literal: true + require 'rails_helper' -describe 'the host-meta route' do - it 'routes to correct place with xml format' do - expect(get('/.well-known/host-meta')). - to route_to('well_known/host_meta#show', format: 'xml') +describe 'Well Known routes' do + describe 'the host-meta route' do + it 'routes to correct place with xml format' do + expect(get('/.well-known/host-meta')) + .to route_to('well_known/host_meta#show', format: 'xml') + end end -end -describe 'the webfinger route' do - it 'routes to correct place with json format' do - expect(get('/.well-known/webfinger')). - to route_to('well_known/webfinger#show') + describe 'the webfinger route' do + it 'routes to correct place with json format' do + expect(get('/.well-known/webfinger')) + .to route_to('well_known/webfinger#show') + end end end diff --git a/spec/search/models/concerns/account_search_spec.rb b/spec/search/models/concerns/account_search_spec.rb new file mode 100644 index 000000000..65e1e4de1 --- /dev/null +++ b/spec/search/models/concerns/account_search_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe AccountSearch do + describe 'a non-discoverable account becoming discoverable' do + let(:account) { Account.find_by(username: 'search_test_account_1') } + + context 'when picking a non-discoverable account' do + it 'its bio is not in the AccountsIndex' do + results = AccountsIndex.filter(term: { username: account.username }) + expect(results.count).to eq(1) + expect(results.first.text).to be_nil + end + end + + context 'when the non-discoverable account becomes discoverable' do + it 'its bio is added to the AccountsIndex' do + account.discoverable = true + account.save! + + results = AccountsIndex.filter(term: { username: account.username }) + expect(results.count).to eq(1) + expect(results.first.text).to eq(account.note) + end + end + end + + describe 'a discoverable account becoming non-discoverable' do + let(:account) { Account.find_by(username: 'search_test_account_0') } + + context 'when picking an discoverable account' do + it 'has its bio in the AccountsIndex' do + results = AccountsIndex.filter(term: { username: account.username }) + expect(results.count).to eq(1) + expect(results.first.text).to eq(account.note) + end + end + + context 'when the discoverable account becomes non-discoverable' do + it 'its bio is removed from the AccountsIndex' do + account.discoverable = false + account.save! + + results = AccountsIndex.filter(term: { username: account.username }) + expect(results.count).to eq(1) + expect(results.first.text).to be_nil + end + end + end +end diff --git a/spec/search/models/concerns/account_statuses_search_spec.rb b/spec/search/models/concerns/account_statuses_search_spec.rb new file mode 100644 index 000000000..d35cfa563 --- /dev/null +++ b/spec/search/models/concerns/account_statuses_search_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe AccountStatusesSearch do + describe 'a non-indexable account becoming indexable' do + let(:account) { Account.find_by(username: 'search_test_account_1') } + + context 'when picking a non-indexable account' do + it 'has no statuses in the PublicStatusesIndex' do + expect(PublicStatusesIndex.filter(term: { account_id: account.id }).count).to eq(0) + end + + it 'has statuses in the StatusesIndex' do + expect(StatusesIndex.filter(term: { account_id: account.id }).count).to eq(account.statuses.count) + end + end + + context 'when the non-indexable account becomes indexable' do + it 'adds the public statuses to the PublicStatusesIndex' do + account.indexable = true + account.save! + + expect(PublicStatusesIndex.filter(term: { account_id: account.id }).count).to eq(account.statuses.where(visibility: :public).count) + expect(StatusesIndex.filter(term: { account_id: account.id }).count).to eq(account.statuses.count) + end + end + end + + describe 'an indexable account becoming non-indexable' do + let(:account) { Account.find_by(username: 'search_test_account_0') } + + context 'when picking an indexable account' do + it 'has statuses in the PublicStatusesIndex' do + expect(PublicStatusesIndex.filter(term: { account_id: account.id }).count).to eq(account.statuses.where(visibility: :public).count) + end + + it 'has statuses in the StatusesIndex' do + expect(StatusesIndex.filter(term: { account_id: account.id }).count).to eq(account.statuses.count) + end + end + + context 'when the indexable account becomes non-indexable' do + it 'removes the statuses from the PublicStatusesIndex' do + account.indexable = false + account.save! + + expect(PublicStatusesIndex.filter(term: { account_id: account.id }).count).to eq(0) + expect(StatusesIndex.filter(term: { account_id: account.id }).count).to eq(account.statuses.count) + end + end + end +end diff --git a/spec/serializers/activitypub/device_serializer_spec.rb b/spec/serializers/activitypub/device_serializer_spec.rb new file mode 100644 index 000000000..2a3be8212 --- /dev/null +++ b/spec/serializers/activitypub/device_serializer_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ActivityPub::DeviceSerializer do + let(:serialization) do + JSON.parse( + ActiveModelSerializers::SerializableResource.new( + record, serializer: described_class + ).to_json + ) + end + let(:record) { Fabricate(:device) } + + describe 'type' do + it 'returns correct serialized type' do + expect(serialization['type']).to eq('Device') + end + end +end diff --git a/spec/serializers/activitypub/note_serializer_spec.rb b/spec/serializers/activitypub/note_serializer_spec.rb new file mode 100644 index 000000000..31ee31f13 --- /dev/null +++ b/spec/serializers/activitypub/note_serializer_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ActivityPub::NoteSerializer do + subject { JSON.parse(@serialization.to_json) } + + let!(:account) { Fabricate(:account) } + let!(:other) { Fabricate(:account) } + let!(:parent) { Fabricate(:status, account: account, visibility: :public, language: 'zh-TW') } + let!(:reply_by_account_first) { Fabricate(:status, account: account, thread: parent, visibility: :public) } + let!(:reply_by_account_next) { Fabricate(:status, account: account, thread: parent, visibility: :public) } + let!(:reply_by_other_first) { Fabricate(:status, account: other, thread: parent, visibility: :public) } + let!(:reply_by_account_third) { Fabricate(:status, account: account, thread: parent, visibility: :public) } + let!(:reply_by_account_visibility_direct) { Fabricate(:status, account: account, thread: parent, visibility: :direct) } + + before(:each) do + @serialization = ActiveModelSerializers::SerializableResource.new(parent, serializer: described_class, adapter: ActivityPub::Adapter) + end + + it 'has the expected shape' do + expect(subject).to include({ + '@context' => include('https://www.w3.org/ns/activitystreams'), + 'type' => 'Note', + 'attributedTo' => ActivityPub::TagManager.instance.uri_for(account), + 'contentMap' => include({ + 'zh-TW' => a_kind_of(String), + }), + }) + end + + it 'has a replies collection' do + expect(subject['replies']['type']).to eql('Collection') + end + + it 'has a replies collection with a first Page' do + expect(subject['replies']['first']['type']).to eql('CollectionPage') + end + + it 'includes public self-replies in its replies collection' do + expect(subject['replies']['first']['items']).to include(reply_by_account_first.uri, reply_by_account_next.uri, reply_by_account_third.uri) + end + + it 'does not include replies from others in its replies collection' do + expect(subject['replies']['first']['items']).to_not include(reply_by_other_first.uri) + end + + it 'does not include replies with direct visibility in its replies collection' do + expect(subject['replies']['first']['items']).to_not include(reply_by_account_visibility_direct.uri) + end +end diff --git a/spec/serializers/activitypub/note_spec.rb b/spec/serializers/activitypub/note_spec.rb deleted file mode 100644 index 55bfbc16b..000000000 --- a/spec/serializers/activitypub/note_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe ActivityPub::NoteSerializer do - let!(:account) { Fabricate(:account) } - let!(:other) { Fabricate(:account) } - let!(:parent) { Fabricate(:status, account: account, visibility: :public) } - let!(:reply1) { Fabricate(:status, account: account, thread: parent, visibility: :public) } - let!(:reply2) { Fabricate(:status, account: account, thread: parent, visibility: :public) } - let!(:reply3) { Fabricate(:status, account: other, thread: parent, visibility: :public) } - let!(:reply4) { Fabricate(:status, account: account, thread: parent, visibility: :public) } - let!(:reply5) { Fabricate(:status, account: account, thread: parent, visibility: :direct) } - - before(:each) do - @serialization = ActiveModelSerializers::SerializableResource.new(parent, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter) - end - - subject { JSON.parse(@serialization.to_json) } - - it 'has a Note type' do - expect(subject['type']).to eql('Note') - end - - it 'has a replies collection' do - expect(subject['replies']['type']).to eql('Collection') - end - - it 'has a replies collection with a first Page' do - expect(subject['replies']['first']['type']).to eql('CollectionPage') - end - - it 'includes public self-replies in its replies collection' do - expect(subject['replies']['first']['items']).to include(reply1.uri, reply2.uri, reply4.uri) - end - - it 'does not include replies from others in its replies collection' do - expect(subject['replies']['first']['items']).to_not include(reply3.uri) - end - - it 'does not include replies with direct visibility in its replies collection' do - expect(subject['replies']['first']['items']).to_not include(reply5.uri) - end -end diff --git a/spec/serializers/activitypub/one_time_key_serializer_spec.rb b/spec/serializers/activitypub/one_time_key_serializer_spec.rb new file mode 100644 index 000000000..6fe1f0618 --- /dev/null +++ b/spec/serializers/activitypub/one_time_key_serializer_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ActivityPub::OneTimeKeySerializer do + let(:serialization) do + JSON.parse( + ActiveModelSerializers::SerializableResource.new( + record, serializer: described_class + ).to_json + ) + end + let(:record) { Fabricate(:one_time_key) } + + describe 'type' do + it 'returns correct serialized type' do + expect(serialization['type']).to eq('Curve25519Key') + end + end +end diff --git a/spec/serializers/activitypub/undo_like_serializer_spec.rb b/spec/serializers/activitypub/undo_like_serializer_spec.rb new file mode 100644 index 000000000..43cf7192e --- /dev/null +++ b/spec/serializers/activitypub/undo_like_serializer_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ActivityPub::UndoLikeSerializer do + let(:serialization) do + JSON.parse( + ActiveModelSerializers::SerializableResource.new( + record, serializer: described_class + ).to_json + ) + end + let(:record) { Fabricate(:favourite) } + + describe 'type' do + it 'returns correct serialized type' do + expect(serialization['type']).to eq('Undo') + end + end +end diff --git a/spec/serializers/activitypub/update_poll_spec.rb b/spec/serializers/activitypub/update_poll_serializer_spec.rb similarity index 88% rename from spec/serializers/activitypub/update_poll_spec.rb rename to spec/serializers/activitypub/update_poll_serializer_spec.rb index f9e035eab..14c24c70c 100644 --- a/spec/serializers/activitypub/update_poll_spec.rb +++ b/spec/serializers/activitypub/update_poll_serializer_spec.rb @@ -3,16 +3,16 @@ require 'rails_helper' describe ActivityPub::UpdatePollSerializer do + subject { JSON.parse(@serialization.to_json) } + let(:account) { Fabricate(:account) } let(:poll) { Fabricate(:poll, account: account) } let!(:status) { Fabricate(:status, account: account, poll: poll) } before(:each) do - @serialization = ActiveModelSerializers::SerializableResource.new(status, serializer: ActivityPub::UpdatePollSerializer, adapter: ActivityPub::Adapter) + @serialization = ActiveModelSerializers::SerializableResource.new(status, serializer: described_class, adapter: ActivityPub::Adapter) end - subject { JSON.parse(@serialization.to_json) } - it 'has a Update type' do expect(subject['type']).to eql('Update') end diff --git a/spec/serializers/activitypub/vote_serializer_spec.rb b/spec/serializers/activitypub/vote_serializer_spec.rb new file mode 100644 index 000000000..c329542d7 --- /dev/null +++ b/spec/serializers/activitypub/vote_serializer_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ActivityPub::VoteSerializer do + let(:serialization) do + JSON.parse( + ActiveModelSerializers::SerializableResource.new( + record, serializer: described_class + ).to_json + ) + end + let(:record) { Fabricate(:poll_vote) } + + describe 'type' do + it 'returns correct serialized type' do + expect(serialization['type']).to eq('Create') + end + end +end diff --git a/spec/serializers/rest/account_serializer_spec.rb b/spec/serializers/rest/account_serializer_spec.rb new file mode 100644 index 000000000..e399e88f3 --- /dev/null +++ b/spec/serializers/rest/account_serializer_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe REST::AccountSerializer do + subject { JSON.parse(ActiveModelSerializers::SerializableResource.new(account, serializer: described_class).to_json) } + + let(:role) { Fabricate(:user_role, name: 'Role', highlighted: true) } + let(:user) { Fabricate(:user, role: role) } + let(:account) { user.account } + + context 'when the account is suspended' do + before do + account.suspend! + end + + it 'returns empty roles' do + expect(subject['roles']).to eq [] + end + end + + context 'when the account has a highlighted role' do + let(:role) { Fabricate(:user_role, name: 'Role', highlighted: true) } + + it 'returns the expected role' do + expect(subject['roles'].first).to include({ 'name' => 'Role' }) + end + end + + context 'when the account has a non-highlighted role' do + let(:role) { Fabricate(:user_role, name: 'Role', highlighted: false) } + + it 'returns empty roles' do + expect(subject['roles']).to eq [] + end + end + + context 'when the account is memorialized' do + before do + account.memorialize! + end + + it 'marks it as such' do + expect(subject['memorial']).to be true + end + end +end diff --git a/spec/serializers/rest/encrypted_message_serializer_spec.rb b/spec/serializers/rest/encrypted_message_serializer_spec.rb new file mode 100644 index 000000000..e0e70a3b8 --- /dev/null +++ b/spec/serializers/rest/encrypted_message_serializer_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe REST::EncryptedMessageSerializer do + let(:serialization) do + JSON.parse( + ActiveModelSerializers::SerializableResource.new( + record, serializer: described_class + ).to_json + ) + end + let(:record) { Fabricate(:encrypted_message) } + + describe 'account' do + it 'returns the associated account' do + expect(serialization['account_id']).to eq(record.from_account.id.to_s) + end + end +end diff --git a/spec/serializers/rest/instance_serializer_spec.rb b/spec/serializers/rest/instance_serializer_spec.rb new file mode 100644 index 000000000..15a5de18d --- /dev/null +++ b/spec/serializers/rest/instance_serializer_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe REST::InstanceSerializer do + let(:serialization) do + JSON.parse( + ActiveModelSerializers::SerializableResource.new( + record, serializer: described_class + ).to_json + ) + end + let(:record) { InstancePresenter.new } + + describe 'usage' do + it 'returns recent usage data' do + expect(serialization['usage']).to eq({ 'users' => { 'active_month' => 0 } }) + end + end +end diff --git a/spec/serializers/rest/keys/claim_result_serializer_spec.rb b/spec/serializers/rest/keys/claim_result_serializer_spec.rb new file mode 100644 index 000000000..cf9416f03 --- /dev/null +++ b/spec/serializers/rest/keys/claim_result_serializer_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe REST::Keys::ClaimResultSerializer do + let(:serialization) do + JSON.parse( + ActiveModelSerializers::SerializableResource.new( + record, serializer: described_class + ).to_json + ) + end + let(:record) { Keys::ClaimService::Result.new(Account.new(id: 123), 456) } + + describe 'account' do + it 'returns the associated account' do + expect(serialization['account_id']).to eq('123') + end + end +end diff --git a/spec/serializers/rest/keys/device_serializer_spec.rb b/spec/serializers/rest/keys/device_serializer_spec.rb new file mode 100644 index 000000000..c15e197cb --- /dev/null +++ b/spec/serializers/rest/keys/device_serializer_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe REST::Keys::DeviceSerializer do + let(:serialization) do + JSON.parse( + ActiveModelSerializers::SerializableResource.new( + record, serializer: described_class + ).to_json + ) + end + let(:record) { Device.new(name: 'Device name') } + + describe 'name' do + it 'returns the name' do + expect(serialization['name']).to eq('Device name') + end + end +end diff --git a/spec/serializers/rest/keys/query_result_serializer_spec.rb b/spec/serializers/rest/keys/query_result_serializer_spec.rb new file mode 100644 index 000000000..983780ae9 --- /dev/null +++ b/spec/serializers/rest/keys/query_result_serializer_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe REST::Keys::QueryResultSerializer do + let(:serialization) do + JSON.parse( + ActiveModelSerializers::SerializableResource.new( + record, serializer: described_class + ).to_json + ) + end + let(:record) { Keys::QueryService::Result.new(Account.new(id: 123), []) } + + describe 'account' do + it 'returns the associated account id' do + expect(serialization['account_id']).to eq('123') + end + end +end diff --git a/spec/serializers/rest/suggestion_serializer_spec.rb b/spec/serializers/rest/suggestion_serializer_spec.rb new file mode 100644 index 000000000..b3c086208 --- /dev/null +++ b/spec/serializers/rest/suggestion_serializer_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe REST::SuggestionSerializer do + let(:serialization) do + JSON.parse( + ActiveModelSerializers::SerializableResource.new( + record, serializer: described_class + ).to_json + ) + end + let(:record) do + AccountSuggestions::Suggestion.new( + account: account, + source: 'SuggestionSource' + ) + end + let(:account) { Fabricate(:account) } + + describe 'account' do + it 'returns the associated account' do + expect(serialization['account']['id']).to eq(account.id.to_s) + end + end +end diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb index 81cbc175e..1cd036f48 100644 --- a/spec/services/account_search_service_spec.rb +++ b/spec/services/account_search_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe AccountSearchService, type: :service do @@ -18,7 +20,7 @@ describe AccountSearchService, type: :service do end end - context 'searching for a simple term that is not an exact match' do + context 'when searching for a simple term that is not an exact match' do it 'does not return a nil entry in the array for the exact match' do account = Fabricate(:account, username: 'matchingusername') results = subject.call('match', nil, limit: 5) @@ -51,7 +53,7 @@ describe AccountSearchService, type: :service do context 'when there is a domain but no exact match' do it 'follows the remote account when resolve is true' do - service = double(call: nil) + service = instance_double(ResolveAccountService, call: nil) allow(ResolveAccountService).to receive(:new).and_return(service) results = subject.call('newuser@remote.com', nil, limit: 10, resolve: true) @@ -59,11 +61,11 @@ describe AccountSearchService, type: :service do end it 'does not follow the remote account when resolve is false' do - service = double(call: nil) + service = instance_double(ResolveAccountService, call: nil) allow(ResolveAccountService).to receive(:new).and_return(service) results = subject.call('newuser@remote.com', nil, limit: 10, resolve: false) - expect(service).not_to have_received(:call) + expect(service).to_not have_received(:call) end end @@ -76,7 +78,7 @@ describe AccountSearchService, type: :service do expect(results).to eq [partial] end - it "does not return suspended remote accounts" do + it 'does not return suspended remote accounts' do remote = Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e', suspended: true) results = subject.call('a@example.com', nil, limit: 2) diff --git a/spec/services/account_statuses_cleanup_service_spec.rb b/spec/services/account_statuses_cleanup_service_spec.rb index 257655c41..f7a88a917 100644 --- a/spec/services/account_statuses_cleanup_service_spec.rb +++ b/spec/services/account_statuses_cleanup_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe AccountStatusesCleanupService, type: :service do @@ -18,13 +20,13 @@ describe AccountStatusesCleanupService, type: :service do let!(:another_old_status) { Fabricate(:status, created_at: 1.year.ago, account: account) } let!(:recent_status) { Fabricate(:status, created_at: 1.day.ago, account: account) } - context 'given a budget of 1' do + context 'when given a budget of 1' do it 'reports 1 deleted toot' do expect(subject.call(account_policy, 1)).to eq 1 end end - context 'given a normal budget of 10' do + context 'when given a normal budget of 10' do it 'reports 3 deleted statuses' do expect(subject.call(account_policy, 10)).to eq 3 end @@ -42,8 +44,8 @@ describe AccountStatusesCleanupService, type: :service do context 'when called repeatedly with a budget of 2' do it 'reports 2 then 1 deleted statuses' do - expect(subject.call(account_policy, 2)).to eq 2 - expect(subject.call(account_policy, 2)).to eq 1 + expect(subject.call(account_policy, 2)).to eq 2 + expect(subject.call(account_policy, 2)).to eq 1 end it 'actually deletes the statuses in the expected order' do diff --git a/spec/services/activitypub/fetch_featured_collection_service_spec.rb b/spec/services/activitypub/fetch_featured_collection_service_spec.rb index 82b12954d..583212c37 100644 --- a/spec/services/activitypub/fetch_featured_collection_service_spec.rb +++ b/spec/services/activitypub/fetch_featured_collection_service_spec.rb @@ -1,37 +1,41 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do + subject { described_class.new } + let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/account', featured_collection_url: 'https://example.com/account/pinned') } let!(:known_status) { Fabricate(:status, account: actor, uri: 'https://example.com/account/pinned/1') } - let(:status_json_1) do + let(:status_json_pinned_known) do { '@context': 'https://www.w3.org/ns/activitystreams', type: 'Note', - id: 'https://example.com/account/pinned/1', + id: 'https://example.com/account/pinned/known', content: 'foo', attributedTo: actor.uri, to: 'https://www.w3.org/ns/activitystreams#Public', } end - let(:status_json_2) do + let(:status_json_pinned_unknown_inlined) do { '@context': 'https://www.w3.org/ns/activitystreams', type: 'Note', - id: 'https://example.com/account/pinned/2', + id: 'https://example.com/account/pinned/unknown-inlined', content: 'foo', attributedTo: actor.uri, to: 'https://www.w3.org/ns/activitystreams#Public', } end - let(:status_json_4) do + let(:status_json_pinned_unknown_reachable) do { '@context': 'https://www.w3.org/ns/activitystreams', type: 'Note', - id: 'https://example.com/account/pinned/4', + id: 'https://example.com/account/pinned/unknown-reachable', content: 'foo', attributedTo: actor.uri, to: 'https://www.w3.org/ns/activitystreams#Public', @@ -40,10 +44,10 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do let(:items) do [ - 'https://example.com/account/pinned/1', # known - status_json_2, # unknown inlined - 'https://example.com/account/pinned/3', # unknown unreachable - 'https://example.com/account/pinned/4', # unknown reachable + 'https://example.com/account/pinned/known', # known + status_json_pinned_unknown_inlined, # unknown inlined + 'https://example.com/account/pinned/unknown-unreachable', # unknown unreachable + 'https://example.com/account/pinned/unknown-reachable', # unknown reachable ] end @@ -56,20 +60,22 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do }.with_indifferent_access end - subject { described_class.new } - shared_examples 'sets pinned posts' do before do - stub_request(:get, 'https://example.com/account/pinned/1').to_return(status: 200, body: Oj.dump(status_json_1), headers: { 'Content-Type': 'application/activity+json' }) - stub_request(:get, 'https://example.com/account/pinned/2').to_return(status: 200, body: Oj.dump(status_json_2), headers: { 'Content-Type': 'application/activity+json' }) - stub_request(:get, 'https://example.com/account/pinned/3').to_return(status: 404) - stub_request(:get, 'https://example.com/account/pinned/4').to_return(status: 200, body: Oj.dump(status_json_4), headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, 'https://example.com/account/pinned/known').to_return(status: 200, body: Oj.dump(status_json_pinned_known), headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, 'https://example.com/account/pinned/unknown-inlined').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_inlined), headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, 'https://example.com/account/pinned/unknown-unreachable').to_return(status: 404) + stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable), headers: { 'Content-Type': 'application/activity+json' }) subject.call(actor, note: true, hashtag: false) end it 'sets expected posts as pinned posts' do - expect(actor.pinned_statuses.pluck(:uri)).to match_array ['https://example.com/account/pinned/1', 'https://example.com/account/pinned/2', 'https://example.com/account/pinned/4'] + expect(actor.pinned_statuses.pluck(:uri)).to contain_exactly( + 'https://example.com/account/pinned/known', + 'https://example.com/account/pinned/unknown-inlined', + 'https://example.com/account/pinned/unknown-reachable' + ) end end @@ -97,6 +103,21 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do end it_behaves_like 'sets pinned posts' + + context 'when there is a single item, with the array compacted away' do + let(:items) { 'https://example.com/account/pinned/unknown-reachable' } + + before do + stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable), headers: { 'Content-Type': 'application/activity+json' }) + subject.call(actor, note: true, hashtag: false) + end + + it 'sets expected posts as pinned posts' do + expect(actor.pinned_statuses.pluck(:uri)).to contain_exactly( + 'https://example.com/account/pinned/unknown-reachable' + ) + end + end end context 'when the endpoint is a paginated Collection' do @@ -109,7 +130,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do type: 'CollectionPage', partOf: actor.featured_collection_url, items: items, - } + }, }.with_indifferent_access end @@ -118,6 +139,21 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do end it_behaves_like 'sets pinned posts' + + context 'when there is a single item, with the array compacted away' do + let(:items) { 'https://example.com/account/pinned/unknown-reachable' } + + before do + stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable), headers: { 'Content-Type': 'application/activity+json' }) + subject.call(actor, note: true, hashtag: false) + end + + it 'sets expected posts as pinned posts' do + expect(actor.pinned_statuses.pluck(:uri)).to contain_exactly( + 'https://example.com/account/pinned/unknown-reachable' + ) + end + end end end end diff --git a/spec/services/activitypub/fetch_featured_tags_collection_service_spec.rb b/spec/services/activitypub/fetch_featured_tags_collection_service_spec.rb index ba02f9259..638278a10 100644 --- a/spec/services/activitypub/fetch_featured_tags_collection_service_spec.rb +++ b/spec/services/activitypub/fetch_featured_tags_collection_service_spec.rb @@ -1,6 +1,10 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::FetchFeaturedTagsCollectionService, type: :service do + subject { described_class.new } + let(:collection_url) { 'https://example.com/account/tags' } let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/account') } @@ -21,15 +25,13 @@ RSpec.describe ActivityPub::FetchFeaturedTagsCollectionService, type: :service d }.with_indifferent_access end - subject { described_class.new } - shared_examples 'sets featured tags' do before do subject.call(actor, collection_url) end it 'sets expected tags as pinned tags' do - expect(actor.featured_tags.map(&:display_name)).to match_array ['Foo', 'bar', 'baZ'] + expect(actor.featured_tags.map(&:display_name)).to match_array %w(Foo bar baZ) end end @@ -81,7 +83,7 @@ RSpec.describe ActivityPub::FetchFeaturedTagsCollectionService, type: :service d type: 'CollectionPage', partOf: collection_url, items: items, - } + }, }.with_indifferent_access end diff --git a/spec/services/activitypub/fetch_remote_account_service_spec.rb b/spec/services/activitypub/fetch_remote_account_service_spec.rb index 2b8024cca..42badde05 100644 --- a/spec/services/activitypub/fetch_remote_account_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_account_service_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do - subject { ActivityPub::FetchRemoteAccountService.new } + subject { described_class.new } let!(:actor) do { diff --git a/spec/services/activitypub/fetch_remote_actor_service_spec.rb b/spec/services/activitypub/fetch_remote_actor_service_spec.rb index ad7bf0d1b..6d264b7b8 100644 --- a/spec/services/activitypub/fetch_remote_actor_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_actor_service_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do - subject { ActivityPub::FetchRemoteActorService.new } + subject { described_class.new } let!(:actor) do { diff --git a/spec/services/activitypub/fetch_remote_key_service_spec.rb b/spec/services/activitypub/fetch_remote_key_service_spec.rb index 535827899..478778cc9 100644 --- a/spec/services/activitypub/fetch_remote_key_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_key_service_spec.rb @@ -1,12 +1,24 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::FetchRemoteKeyService, type: :service do - subject { ActivityPub::FetchRemoteKeyService.new } + subject { described_class.new } let(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } } let(:public_key_pem) do - "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu3L4vnpNLzVH31MeWI39\n4F0wKeJFsLDAsNXGeOu0QF2x+h1zLWZw/agqD2R3JPU9/kaDJGPIV2Sn5zLyUA9S\n6swCCMOtn7BBR9g9sucgXJmUFB0tACH2QSgHywMAybGfmSb3LsEMNKsGJ9VsvYoh\n8lDET6X4Pyw+ZJU0/OLo/41q9w+OrGtlsTm/PuPIeXnxa6BLqnDaxC+4IcjG/FiP\nahNCTINl/1F/TgSSDZ4Taf4U9XFEIFw8wmgploELozzIzKq+t8nhQYkgAkt64euW\npva3qL5KD1mTIZQEP+LZvh3s2WHrLi3fhbdRuwQ2c0KkJA2oSTFPDpqqbPGZ3Qvu\nHQIDAQAB\n-----END PUBLIC KEY-----\n" + <<~TEXT + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu3L4vnpNLzVH31MeWI39 + 4F0wKeJFsLDAsNXGeOu0QF2x+h1zLWZw/agqD2R3JPU9/kaDJGPIV2Sn5zLyUA9S + 6swCCMOtn7BBR9g9sucgXJmUFB0tACH2QSgHywMAybGfmSb3LsEMNKsGJ9VsvYoh + 8lDET6X4Pyw+ZJU0/OLo/41q9w+OrGtlsTm/PuPIeXnxa6BLqnDaxC+4IcjG/FiP + ahNCTINl/1F/TgSSDZ4Taf4U9XFEIFw8wmgploELozzIzKq+t8nhQYkgAkt64euW + pva3qL5KD1mTIZQEP+LZvh3s2WHrLi3fhbdRuwQ2c0KkJA2oSTFPDpqqbPGZ3Qvu + HQIDAQAB + -----END PUBLIC KEY----- + TEXT end let(:public_key_id) { 'https://example.com/alice#main-key' } diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb index 7359ca0b4..826b67d88 100644 --- a/spec/services/activitypub/fetch_remote_status_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_status_service_spec.rb @@ -1,8 +1,12 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do include ActionView::Helpers::TextHelper + subject { described_class.new } + let!(:sender) { Fabricate(:account, domain: 'foo.bar', uri: 'https://foo.bar') } let!(:recipient) { Fabricate(:account) } @@ -11,15 +15,13 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do let(:note) do { '@context': 'https://www.w3.org/ns/activitystreams', - id: "https://foo.bar/@foo/1234", + id: 'https://foo.bar/@foo/1234', type: 'Note', content: 'Lorem ipsum', attributedTo: ActivityPub::TagManager.instance.uri_for(sender), } end - subject { described_class.new } - before do stub_request(:get, 'https://foo.bar/watch?v=12345').to_return(status: 404, body: '') stub_request(:get, object[:id]).to_return(body: Oj.dump(object)) @@ -46,7 +48,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do let(:object) do { '@context': 'https://www.w3.org/ns/activitystreams', - id: "https://foo.bar/@foo/1234", + id: 'https://foo.bar/@foo/1234', type: 'Video', name: 'Nyan Cat 10 hours remix', attributedTo: ActivityPub::TagManager.instance.uri_for(sender), @@ -54,13 +56,13 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do { type: 'Link', mimeType: 'application/x-bittorrent', - href: "https://foo.bar/12345.torrent", + href: 'https://foo.bar/12345.torrent', }, { type: 'Link', mimeType: 'text/html', - href: "https://foo.bar/watch?v=12345", + href: 'https://foo.bar/watch?v=12345', }, ], } @@ -70,8 +72,8 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do status = sender.statuses.first expect(status).to_not be_nil - expect(status.url).to eq "https://foo.bar/watch?v=12345" - expect(strip_tags(status.text)).to eq "Nyan Cat 10 hours remixhttps://foo.bar/watch?v=12345" + expect(status.url).to eq 'https://foo.bar/watch?v=12345' + expect(strip_tags(status.text)).to eq 'Nyan Cat 10 hours remixhttps://foo.bar/watch?v=12345' end end @@ -79,7 +81,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do let(:object) do { '@context': 'https://www.w3.org/ns/activitystreams', - id: "https://foo.bar/@foo/1234", + id: 'https://foo.bar/@foo/1234', type: 'Audio', name: 'Nyan Cat 10 hours remix', attributedTo: ActivityPub::TagManager.instance.uri_for(sender), @@ -87,13 +89,13 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do { type: 'Link', mimeType: 'application/x-bittorrent', - href: "https://foo.bar/12345.torrent", + href: 'https://foo.bar/12345.torrent', }, { type: 'Link', mimeType: 'text/html', - href: "https://foo.bar/watch?v=12345", + href: 'https://foo.bar/watch?v=12345', }, ], } @@ -103,8 +105,8 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do status = sender.statuses.first expect(status).to_not be_nil - expect(status.url).to eq "https://foo.bar/watch?v=12345" - expect(strip_tags(status.text)).to eq "Nyan Cat 10 hours remixhttps://foo.bar/watch?v=12345" + expect(status.url).to eq 'https://foo.bar/watch?v=12345' + expect(strip_tags(status.text)).to eq 'Nyan Cat 10 hours remixhttps://foo.bar/watch?v=12345' end end @@ -112,10 +114,10 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do let(:object) do { '@context': 'https://www.w3.org/ns/activitystreams', - id: "https://foo.bar/@foo/1234", + id: 'https://foo.bar/@foo/1234', type: 'Event', name: "Let's change the world", - attributedTo: ActivityPub::TagManager.instance.uri_for(sender) + attributedTo: ActivityPub::TagManager.instance.uri_for(sender), } end @@ -123,7 +125,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do status = sender.statuses.first expect(status).to_not be_nil - expect(status.url).to eq "https://foo.bar/@foo/1234" + expect(status.url).to eq 'https://foo.bar/@foo/1234' expect(strip_tags(status.text)).to eq "Let's change the worldhttps://foo.bar/@foo/1234" end end @@ -132,7 +134,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do let(:note) do { '@context': 'https://www.w3.org/ns/activitystreams', - id: "https://real.address/@foo/1234", + id: 'https://real.address/@foo/1234', type: 'Note', content: 'Lorem ipsum', attributedTo: ActivityPub::TagManager.instance.uri_for(sender), @@ -154,7 +156,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do let(:object) do { '@context': 'https://www.w3.org/ns/activitystreams', - id: "https://foo.bar/@foo/1234/create", + id: 'https://foo.bar/@foo/1234/create', type: 'Create', actor: ActivityPub::TagManager.instance.uri_for(sender), object: note, @@ -174,11 +176,11 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do let(:object) do { '@context': 'https://www.w3.org/ns/activitystreams', - id: "https://foo.bar/@foo/1234/create", + id: 'https://foo.bar/@foo/1234/create', type: 'Create', actor: ActivityPub::TagManager.instance.uri_for(sender), object: { - id: "https://real.address/@foo/1234", + id: 'https://real.address/@foo/1234', type: 'Note', content: 'Lorem ipsum', attributedTo: ActivityPub::TagManager.instance.uri_for(sender), @@ -208,7 +210,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do let(:object) do { '@context': 'https://www.w3.org/ns/activitystreams', - id: "https://foo.bar/@foo/1234/create", + id: 'https://foo.bar/@foo/1234/create', type: 'Create', actor: ActivityPub::TagManager.instance.uri_for(sender), object: note.merge(updated: '2021-09-08T22:39:25Z'), @@ -223,4 +225,98 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do end end end + + context 'with statuses referencing other statuses' do + before do + stub_const 'ActivityPub::FetchRemoteStatusService::DISCOVERIES_PER_REQUEST', 5 + end + + context 'when using inReplyTo' do + let(:object) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'https://foo.bar/@foo/1', + type: 'Note', + content: 'Lorem ipsum', + inReplyTo: 'https://foo.bar/@foo/2', + attributedTo: ActivityPub::TagManager.instance.uri_for(sender), + } + end + + before do + 8.times do |i| + status_json = { + '@context': 'https://www.w3.org/ns/activitystreams', + id: "https://foo.bar/@foo/#{i}", + type: 'Note', + content: 'Lorem ipsum', + inReplyTo: "https://foo.bar/@foo/#{i + 1}", + attributedTo: ActivityPub::TagManager.instance.uri_for(sender), + to: 'as:Public', + }.with_indifferent_access + stub_request(:get, "https://foo.bar/@foo/#{i}").to_return(status: 200, body: status_json.to_json, headers: { 'Content-Type': 'application/activity+json' }) + end + end + + it 'creates at least some statuses' do + expect { subject.call(object[:id], prefetched_body: Oj.dump(object)) }.to change { sender.statuses.count }.by_at_least(2) + end + + it 'creates no more account than the limit allows' do + expect { subject.call(object[:id], prefetched_body: Oj.dump(object)) }.to change { sender.statuses.count }.by_at_most(5) + end + end + + context 'when using replies' do + let(:object) do + { + '@context': 'https://www.w3.org/ns/activitystreams', + id: 'https://foo.bar/@foo/1', + type: 'Note', + content: 'Lorem ipsum', + replies: { + type: 'Collection', + id: 'https://foo.bar/@foo/1/replies', + first: { + type: 'CollectionPage', + partOf: 'https://foo.bar/@foo/1/replies', + items: ['https://foo.bar/@foo/2'], + }, + }, + attributedTo: ActivityPub::TagManager.instance.uri_for(sender), + } + end + + before do + 8.times do |i| + status_json = { + '@context': 'https://www.w3.org/ns/activitystreams', + id: "https://foo.bar/@foo/#{i}", + type: 'Note', + content: 'Lorem ipsum', + replies: { + type: 'Collection', + id: "https://foo.bar/@foo/#{i}/replies", + first: { + type: 'CollectionPage', + partOf: "https://foo.bar/@foo/#{i}/replies", + items: ["https://foo.bar/@foo/#{i + 1}"], + }, + }, + attributedTo: ActivityPub::TagManager.instance.uri_for(sender), + to: 'as:Public', + }.with_indifferent_access + stub_request(:get, "https://foo.bar/@foo/#{i}").to_return(status: 200, body: status_json.to_json, headers: { 'Content-Type': 'application/activity+json' }) + end + end + + it 'creates at least some statuses' do + expect { subject.call(object[:id], prefetched_body: Oj.dump(object)) }.to change { sender.statuses.count }.by_at_least(2) + end + + it 'creates no more account than the limit allows' do + expect { subject.call(object[:id], prefetched_body: Oj.dump(object)) }.to change { sender.statuses.count }.by_at_most(5) + end + end + end end diff --git a/spec/services/activitypub/fetch_replies_service_spec.rb b/spec/services/activitypub/fetch_replies_service_spec.rb index ec40abd5b..73c2b4506 100644 --- a/spec/services/activitypub/fetch_replies_service_spec.rb +++ b/spec/services/activitypub/fetch_replies_service_spec.rb @@ -1,6 +1,10 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::FetchRepliesService, type: :service do + subject { described_class.new } + let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') } let(:status) { Fabricate(:status, account: actor) } let(:collection_uri) { 'http://example.com/replies/1' } @@ -28,10 +32,20 @@ RSpec.describe ActivityPub::FetchRepliesService, type: :service do }.with_indifferent_access end - subject { described_class.new } - describe '#call' do context 'when the payload is a Collection with inlined replies' do + context 'when there is a single reply, with the array compacted away' do + let(:items) { 'http://example.com/self-reply-1' } + + it 'queues the expected worker' do + allow(FetchReplyWorker).to receive(:push_bulk) + + subject.call(status, payload) + + expect(FetchReplyWorker).to have_received(:push_bulk).with(['http://example.com/self-reply-1']) + end + end + context 'when passing the collection itself' do it 'spawns workers for up to 5 replies on the same server' do expect(FetchReplyWorker).to receive(:push_bulk).with(['http://example.com/self-reply-1', 'http://example.com/self-reply-2', 'http://example.com/self-reply-3', 'http://example.com/self-reply-4', 'http://example.com/self-reply-5']) @@ -90,7 +104,7 @@ RSpec.describe ActivityPub::FetchRepliesService, type: :service do type: 'CollectionPage', partOf: collection_uri, items: items, - } + }, }.with_indifferent_access end diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb index 7728b9ba8..60214d2ed 100644 --- a/spec/services/activitypub/process_account_service_spec.rb +++ b/spec/services/activitypub/process_account_service_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::ProcessAccountService, type: :service do subject { described_class.new } - context 'property values' do + context 'with property values, an avatar, and a profile header' do let(:payload) do { id: 'https://foo.test', @@ -12,25 +14,58 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do attachment: [ { type: 'PropertyValue', name: 'Pronouns', value: 'They/them' }, { type: 'PropertyValue', name: 'Occupation', value: 'Unit test' }, - { type: 'PropertyValue', name: 'non-string', value: ['foo', 'bar'] }, + { type: 'PropertyValue', name: 'non-string', value: %w(foo bar) }, ], + image: { + type: 'Image', + mediaType: 'image/png', + url: 'https://foo.test/image.png', + }, + icon: { + type: 'Image', + url: [ + { + mediaType: 'image/png', + href: 'https://foo.test/icon.png', + }, + ], + }, }.with_indifferent_access end - it 'parses out of attachment' do + before do + stub_request(:get, 'https://foo.test/image.png').to_return(request_fixture('avatar.txt')) + stub_request(:get, 'https://foo.test/icon.png').to_return(request_fixture('avatar.txt')) + end + + it 'parses property values, avatar and profile header as expected' do account = subject.call('alice', 'example.com', payload) - expect(account.fields).to be_a Array - expect(account.fields.size).to eq 2 - expect(account.fields[0]).to be_a Account::Field - expect(account.fields[0].name).to eq 'Pronouns' - expect(account.fields[0].value).to eq 'They/them' - expect(account.fields[1]).to be_a Account::Field - expect(account.fields[1].name).to eq 'Occupation' - expect(account.fields[1].value).to eq 'Unit test' + + expect(account.fields) + .to be_an(Array) + .and have_attributes(size: 2) + expect(account.fields.first) + .to be_an(Account::Field) + .and have_attributes( + name: eq('Pronouns'), + value: eq('They/them') + ) + expect(account.fields.last) + .to be_an(Account::Field) + .and have_attributes( + name: eq('Occupation'), + value: eq('Unit test') + ) + expect(account).to have_attributes( + avatar_remote_url: 'https://foo.test/icon.png', + header_remote_url: 'https://foo.test/image.png' + ) end end context 'when account is not suspended' do + subject { described_class.new.call('alice', 'example.com', payload) } + let!(:account) { Fabricate(:account, username: 'alice', domain: 'example.com') } let(:payload) do @@ -46,8 +81,6 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do allow(Admin::SuspensionWorker).to receive(:perform_async) end - subject { described_class.new.call('alice', 'example.com', payload) } - it 'suspends account remotely' do expect(subject.suspended?).to be true expect(subject.suspension_origin_remote?).to be true @@ -60,6 +93,8 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do end context 'when account is suspended' do + subject { described_class.new.call('alice', 'example.com', payload) } + let!(:account) { Fabricate(:account, username: 'alice', domain: 'example.com', display_name: '') } let(:payload) do @@ -78,9 +113,7 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do account.suspend!(origin: suspension_origin) end - subject { described_class.new.call('alice', 'example.com', payload) } - - context 'locally' do + context 'when locally' do let(:suspension_origin) { :local } it 'does not unsuspend it' do @@ -92,7 +125,7 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do end end - context 'remotely' do + context 'when remotely' do let(:suspension_origin) { :remote } it 'unsuspends it' do @@ -109,4 +142,96 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do end end end + + context 'when discovering many subdomains in a short timeframe' do + subject do + 8.times do |i| + domain = "test#{i}.testdomain.com" + json = { + id: "https://#{domain}/users/1", + type: 'Actor', + inbox: "https://#{domain}/inbox", + }.with_indifferent_access + described_class.new.call('alice', domain, json) + end + end + + before do + stub_const 'ActivityPub::ProcessAccountService::SUBDOMAINS_RATELIMIT', 5 + end + + it 'creates at least some accounts' do + expect { subject }.to change { Account.remote.count }.by_at_least(2) + end + + it 'creates no more account than the limit allows' do + expect { subject }.to change { Account.remote.count }.by_at_most(5) + end + end + + context 'when Accounts referencing other accounts' do + let(:payload) do + { + '@context': ['https://www.w3.org/ns/activitystreams'], + id: 'https://foo.test/users/1', + type: 'Person', + inbox: 'https://foo.test/inbox', + featured: 'https://foo.test/users/1/featured', + preferredUsername: 'user1', + }.with_indifferent_access + end + + before do + stub_const 'ActivityPub::ProcessAccountService::DISCOVERIES_PER_REQUEST', 5 + + 8.times do |i| + actor_json = { + '@context': ['https://www.w3.org/ns/activitystreams'], + id: "https://foo.test/users/#{i}", + type: 'Person', + inbox: 'https://foo.test/inbox', + featured: "https://foo.test/users/#{i}/featured", + preferredUsername: "user#{i}", + }.with_indifferent_access + status_json = { + '@context': ['https://www.w3.org/ns/activitystreams'], + id: "https://foo.test/users/#{i}/status", + attributedTo: "https://foo.test/users/#{i}", + type: 'Note', + content: "@user#{i + 1} test", + tag: [ + { + type: 'Mention', + href: "https://foo.test/users/#{i + 1}", + name: "@user#{i + 1}", + }, + ], + to: ['as:Public', "https://foo.test/users/#{i + 1}"], + }.with_indifferent_access + featured_json = { + '@context': ['https://www.w3.org/ns/activitystreams'], + id: "https://foo.test/users/#{i}/featured", + type: 'OrderedCollection', + totalItems: 1, + orderedItems: [status_json], + }.with_indifferent_access + webfinger = { + subject: "acct:user#{i}@foo.test", + links: [{ rel: 'self', href: "https://foo.test/users/#{i}" }], + }.with_indifferent_access + stub_request(:get, "https://foo.test/users/#{i}").to_return(status: 200, body: actor_json.to_json, headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, "https://foo.test/users/#{i}/featured").to_return(status: 200, body: featured_json.to_json, headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, "https://foo.test/users/#{i}/status").to_return(status: 200, body: status_json.to_json, headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, "https://foo.test/.well-known/webfinger?resource=acct:user#{i}@foo.test").to_return(body: webfinger.to_json, headers: { 'Content-Type': 'application/jrd+json' }) + end + end + + it 'creates at least some accounts' do + expect { subject.call('user1', 'foo.test', payload) }.to change { Account.remote.count }.by_at_least(2) + end + + it 'creates no more account than the limit allows' do + expect { subject.call('user1', 'foo.test', payload) }.to change { Account.remote.count }.by_at_most(5) + end + end end diff --git a/spec/services/activitypub/process_collection_service_spec.rb b/spec/services/activitypub/process_collection_service_spec.rb index 093a188a2..02011afea 100644 --- a/spec/services/activitypub/process_collection_service_spec.rb +++ b/spec/services/activitypub/process_collection_service_spec.rb @@ -1,6 +1,10 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::ProcessCollectionService, type: :service do + subject { described_class.new } + let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') } let(:payload) do @@ -19,8 +23,6 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do let(:json) { Oj.dump(payload) } - subject { described_class.new } - describe '#call' do context 'when actor is suspended' do before do @@ -39,7 +41,7 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do end it 'does not process payload' do - expect(ActivityPub::Activity).not_to receive(:factory) + expect(ActivityPub::Activity).to_not receive(:factory) subject.call(json, actor) end end @@ -68,8 +70,8 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do let(:forwarder) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/other_account') } it 'does not process payload if no signature exists' do - expect_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(nil) - expect(ActivityPub::Activity).not_to receive(:factory) + allow_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(nil) + expect(ActivityPub::Activity).to_not receive(:factory) subject.call(json, forwarder) end @@ -77,7 +79,7 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do it 'processes payload with actor if valid signature exists' do payload['signature'] = { 'type' => 'RsaSignature2017' } - expect_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(actor) + allow_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(actor) expect(ActivityPub::Activity).to receive(:factory).with(instance_of(Hash), actor, instance_of(Hash)) subject.call(json, forwarder) @@ -86,8 +88,8 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do it 'does not process payload if invalid signature exists' do payload['signature'] = { 'type' => 'RsaSignature2017' } - expect_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(nil) - expect(ActivityPub::Activity).not_to receive(:factory) + allow_any_instance_of(ActivityPub::LinkedDataSignature).to receive(:verify_actor!).and_return(nil) + expect(ActivityPub::Activity).to_not receive(:factory) subject.call(json, forwarder) end @@ -95,11 +97,21 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do context 'when receiving a fabricated status' do let!(:actor) do Fabricate(:account, - username: 'bob', - domain: 'example.com', - uri: 'https://example.com/users/bob', - public_key: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuuYyoyfsRkYnXRotMsId\nW3euBDDfiv9oVqOxUVC7bhel8KednIMrMCRWFAkgJhbrlzbIkjVr68o1MP9qLcn7\nCmH/BXHp7yhuFTr4byjdJKpwB+/i2jNEsvDH5jR8WTAeTCe0x/QHg21V3F7dSI5m\nCCZ/1dSIyOXLRTWVlfDlm3rE4ntlCo+US3/7oSWbg/4/4qEnt1HC32kvklgScxua\n4LR5ATdoXa5bFoopPWhul7MJ6NyWCyQyScUuGdlj8EN4kmKQJvphKHrI9fvhgOuG\nTvhTR1S5InA4azSSchY0tXEEw/VNxraeX0KPjbgr6DPcwhPd/m0nhVDq0zVyVBBD\nMwIDAQAB\n-----END PUBLIC KEY-----\n", - private_key: nil) + username: 'bob', + domain: 'example.com', + uri: 'https://example.com/users/bob', + private_key: nil, + public_key: <<~TEXT) + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuuYyoyfsRkYnXRotMsId + W3euBDDfiv9oVqOxUVC7bhel8KednIMrMCRWFAkgJhbrlzbIkjVr68o1MP9qLcn7 + CmH/BXHp7yhuFTr4byjdJKpwB+/i2jNEsvDH5jR8WTAeTCe0x/QHg21V3F7dSI5m + CCZ/1dSIyOXLRTWVlfDlm3rE4ntlCo+US3/7oSWbg/4/4qEnt1HC32kvklgScxua + 4LR5ATdoXa5bFoopPWhul7MJ6NyWCyQyScUuGdlj8EN4kmKQJvphKHrI9fvhgOuG + TvhTR1S5InA4azSSchY0tXEEw/VNxraeX0KPjbgr6DPcwhPd/m0nhVDq0zVyVBBD + MwIDAQAB + -----END PUBLIC KEY----- + TEXT end let(:payload) do @@ -107,48 +119,55 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do '@context': [ 'https://www.w3.org/ns/activitystreams', nil, - {'object': 'https://www.w3.org/ns/activitystreams#object'} + { object: 'https://www.w3.org/ns/activitystreams#object' }, ], - 'id': 'https://example.com/users/bob/fake-status/activity', - 'type': 'Create', - 'actor': 'https://example.com/users/bob', - 'published': '2022-01-22T15:00:00Z', - 'to': [ - 'https://www.w3.org/ns/activitystreams#Public' + id: 'https://example.com/users/bob/fake-status/activity', + type: 'Create', + actor: 'https://example.com/users/bob', + published: '2022-01-22T15:00:00Z', + to: [ + 'https://www.w3.org/ns/activitystreams#Public', ], - 'cc': [ - 'https://example.com/users/bob/followers' + cc: [ + 'https://example.com/users/bob/followers', ], - 'signature': { - 'type': 'RsaSignature2017', - 'creator': 'https://example.com/users/bob#main-key', - 'created': '2022-03-09T21:57:25Z', - 'signatureValue': 'WculK0LelTQ0MvGwU9TPoq5pFzFfGYRDCJqjZ232/Udj4CHqDTGOSw5UTDLShqBOyycCkbZGrQwXG+dpyDpQLSe1UVPZ5TPQtc/9XtI57WlS2nMNpdvRuxGnnb2btPdesXZ7n3pCxo0zjaXrJMe0mqQh5QJO22mahb4bDwwmfTHgbD3nmkD+fBfGi+UV2qWwqr+jlV4L4JqNkh0gWljF5KTePLRRZCuWiQ/FAt7c67636cdIPf7fR+usjuZltTQyLZKEGuK8VUn2Gkfsx5qns7Vcjvlz1JqlAjyO8HPBbzTTHzUG2nUOIgC3PojCSWv6mNTmRGoLZzOscCAYQA6cKw==' + signature: { + type: 'RsaSignature2017', + creator: 'https://example.com/users/bob#main-key', + created: '2022-03-09T21:57:25Z', + signatureValue: 'WculK0LelTQ0MvGwU9TPoq5pFzFfGYRDCJqjZ232/Udj4' \ + 'CHqDTGOSw5UTDLShqBOyycCkbZGrQwXG+dpyDpQLSe1UV' \ + 'PZ5TPQtc/9XtI57WlS2nMNpdvRuxGnnb2btPdesXZ7n3p' \ + 'Cxo0zjaXrJMe0mqQh5QJO22mahb4bDwwmfTHgbD3nmkD+' \ + 'fBfGi+UV2qWwqr+jlV4L4JqNkh0gWljF5KTePLRRZCuWi' \ + 'Q/FAt7c67636cdIPf7fR+usjuZltTQyLZKEGuK8VUn2Gk' \ + 'fsx5qns7Vcjvlz1JqlAjyO8HPBbzTTHzUG2nUOIgC3Poj' \ + 'CSWv6mNTmRGoLZzOscCAYQA6cKw==', }, '@id': 'https://example.com/users/bob/statuses/107928807471117876/activity', '@type': 'https://www.w3.org/ns/activitystreams#Create', 'https://www.w3.org/ns/activitystreams#actor': { - '@id': 'https://example.com/users/bob' + '@id': 'https://example.com/users/bob', }, 'https://www.w3.org/ns/activitystreams#cc': { - '@id': 'https://example.com/users/bob/followers' + '@id': 'https://example.com/users/bob/followers', }, - 'object': { - 'id': 'https://example.com/users/bob/fake-status', - 'type': 'Note', - 'published': '2022-01-22T15:00:00Z', - 'url': 'https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=puck-was-here', - 'attributedTo': 'https://example.com/users/bob', - 'to': [ - 'https://www.w3.org/ns/activitystreams#Public' + object: { + id: 'https://example.com/users/bob/fake-status', + type: 'Note', + published: '2022-01-22T15:00:00Z', + url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=puck-was-here', + attributedTo: 'https://example.com/users/bob', + to: [ + 'https://www.w3.org/ns/activitystreams#Public', ], - 'cc': [ - 'https://example.com/users/bob/followers' + cc: [ + 'https://example.com/users/bob/followers', ], - 'sensitive': false, - 'atomUri': 'https://example.com/users/bob/fake-status', - 'conversation': 'tag:example.com,2022-03-09:objectId=15:objectType=Conversation', - 'content': '

puck was here

', + sensitive: false, + atomUri: 'https://example.com/users/bob/fake-status', + conversation: 'tag:example.com,2022-03-09:objectId=15:objectType=Conversation', + content: '

puck was here

', '@id': 'https://example.com/users/bob/statuses/107928807471117876', '@type': 'https://www.w3.org/ns/activitystreams#Note', @@ -156,21 +175,21 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do 'http://ostatus.org#conversation': 'tag:example.com,2022-03-09:objectId=15:objectType=Conversation', 'https://www.w3.org/ns/activitystreams#attachment': [], 'https://www.w3.org/ns/activitystreams#attributedTo': { - '@id': 'https://example.com/users/bob' + '@id': 'https://example.com/users/bob', }, 'https://www.w3.org/ns/activitystreams#cc': { - '@id': 'https://example.com/users/bob/followers' + '@id': 'https://example.com/users/bob/followers', }, 'https://www.w3.org/ns/activitystreams#content': [ '

hello world

', { '@value': '

hello world

', - '@language': 'en' - } + '@language': 'en', + }, ], 'https://www.w3.org/ns/activitystreams#published': { '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', - '@value': '2022-03-09T21:55:07Z' + '@value': '2022-03-09T21:55:07Z', }, 'https://www.w3.org/ns/activitystreams#replies': { '@id': 'https://example.com/users/bob/statuses/107928807471117876/replies', @@ -179,51 +198,51 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do '@type': 'https://www.w3.org/ns/activitystreams#CollectionPage', 'https://www.w3.org/ns/activitystreams#items': [], 'https://www.w3.org/ns/activitystreams#next': { - '@id': 'https://example.com/users/bob/statuses/107928807471117876/replies?only_other_accounts=true&page=true' + '@id': 'https://example.com/users/bob/statuses/107928807471117876/replies?only_other_accounts=true&page=true', }, 'https://www.w3.org/ns/activitystreams#partOf': { - '@id': 'https://example.com/users/bob/statuses/107928807471117876/replies' - } - } + '@id': 'https://example.com/users/bob/statuses/107928807471117876/replies', + }, + }, }, 'https://www.w3.org/ns/activitystreams#sensitive': false, 'https://www.w3.org/ns/activitystreams#tag': [], 'https://www.w3.org/ns/activitystreams#to': { - '@id': 'https://www.w3.org/ns/activitystreams#Public' + '@id': 'https://www.w3.org/ns/activitystreams#Public', }, 'https://www.w3.org/ns/activitystreams#url': { - '@id': 'https://example.com/@bob/107928807471117876' - } + '@id': 'https://example.com/@bob/107928807471117876', + }, }, 'https://www.w3.org/ns/activitystreams#published': { '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', - '@value': '2022-03-09T21:55:07Z' + '@value': '2022-03-09T21:55:07Z', }, 'https://www.w3.org/ns/activitystreams#to': { - '@id': 'https://www.w3.org/ns/activitystreams#Public' - } + '@id': 'https://www.w3.org/ns/activitystreams#Public', + }, } end it 'does not process forged payload' do - expect(ActivityPub::Activity).not_to receive(:factory).with( + expect(ActivityPub::Activity).to_not receive(:factory).with( hash_including( 'object' => hash_including( 'id' => 'https://example.com/users/bob/fake-status' ) ), - anything(), - anything() + anything, + anything ) - expect(ActivityPub::Activity).not_to receive(:factory).with( + expect(ActivityPub::Activity).to_not receive(:factory).with( hash_including( 'object' => hash_including( 'content' => '

puck was here

' ) ), - anything(), - anything() + anything, + anything ) subject.call(json, forwarder) diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb index 481572742..9d91f31cc 100644 --- a/spec/services/activitypub/process_status_update_service_spec.rb +++ b/spec/services/activitypub/process_status_update_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' def poll_option_json(name, votes) @@ -5,21 +7,9 @@ def poll_option_json(name, votes) end RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do + subject { described_class.new } + let!(:status) { Fabricate(:status, text: 'Hello world', account: Fabricate(:account, domain: 'example.com')) } - - let(:alice) { Fabricate(:account) } - let(:bob) { Fabricate(:account) } - - let(:mentions) { [] } - let(:tags) { [] } - let(:media_attachments) { [] } - - before do - mentions.each { |a| Fabricate(:mention, status: status, account: a) } - tags.each { |t| status.tags << t } - media_attachments.each { |m| status.media_attachments << m } - end - let(:payload) do { '@context': 'https://www.w3.org/ns/activitystreams', @@ -34,19 +24,29 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do ], } end - let(:json) { Oj.load(Oj.dump(payload)) } - subject { described_class.new } + let(:alice) { Fabricate(:account) } + let(:bob) { Fabricate(:account) } + + let(:mentions) { [] } + let(:tags) { [] } + let(:media_attachments) { [] } + + before do + mentions.each { |a| Fabricate(:mention, status: status, account: a) } + tags.each { |t| status.tags << t } + media_attachments.each { |m| status.media_attachments << m } + end describe '#call' do it 'updates text' do - subject.call(status, json) + subject.call(status, json, json) expect(status.reload.text).to eq 'Hello universe' end it 'updates content warning' do - subject.call(status, json) + subject.call(status, json, json) expect(status.reload.spoiler_text).to eq 'Show more' end @@ -64,7 +64,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end before do - subject.call(status, json) + subject.call(status, json, json) end it 'does not create any edits' do @@ -87,7 +87,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end before do - subject.call(status, json) + subject.call(status, json, json) end it 'does not create any edits' do @@ -104,20 +104,19 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end context 'when the status has not been explicitly edited and features a poll' do - let(:account) { Fabricate(:account, domain: 'example.com') } + let(:account) { Fabricate(:account, domain: 'example.com') } let!(:expiration) { 10.days.from_now.utc } let!(:status) do Fabricate(:status, - text: 'Hello world', - account: account, - poll_attributes: { - options: %w(Foo Bar), - account: account, - multiple: false, - hide_totals: false, - expires_at: expiration - } - ) + text: 'Hello world', + account: account, + poll_attributes: { + options: %w(Foo Bar), + account: account, + multiple: false, + hide_totals: false, + expires_at: expiration, + }) end let(:payload) do @@ -135,7 +134,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end before do - subject.call(status, json) + subject.call(status, json, json) end it 'does not create any edits' do @@ -156,20 +155,19 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end context 'when the status changes a poll despite being not explicitly marked as updated' do - let(:account) { Fabricate(:account, domain: 'example.com') } + let(:account) { Fabricate(:account, domain: 'example.com') } let!(:expiration) { 10.days.from_now.utc } let!(:status) do Fabricate(:status, - text: 'Hello world', - account: account, - poll_attributes: { - options: %w(Foo Bar), - account: account, - multiple: false, - hide_totals: false, - expires_at: expiration - } - ) + text: 'Hello world', + account: account, + poll_attributes: { + options: %w(Foo Bar), + account: account, + multiple: false, + hide_totals: false, + expires_at: expiration, + }) end let(:payload) do @@ -188,7 +186,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end before do - subject.call(status, json) + subject.call(status, json, json) end it 'does not create any edits' do @@ -216,11 +214,11 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end it 'does not create any edits' do - expect { subject.call(status, json) }.not_to change { status.reload.edits.pluck(&:id) } + expect { subject.call(status, json, json) }.to_not(change { status.reload.edits.pluck(&:id) }) end it 'does not update the text, spoiler_text or edited_at' do - expect { subject.call(status, json) }.not_to change { s = status.reload; [s.text, s.spoiler_text, s.edited_at] } + expect { subject.call(status, json, json) }.to_not(change { s = status.reload; [s.text, s.spoiler_text, s.edited_at] }) end end @@ -235,7 +233,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end before do - subject.call(status, json) + subject.call(status, json, json) end it 'does not create any edits' do @@ -259,7 +257,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do before do status.update(ordered_media_attachment_ids: nil) - subject.call(status, json) + subject.call(status, json, json) end it 'does not create any edits' do @@ -271,9 +269,9 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end end - context 'originally without tags' do + context 'when originally without tags' do before do - subject.call(status, json) + subject.call(status, json, json) end it 'updates tags' do @@ -281,7 +279,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end end - context 'originally with tags' do + context 'when originally with tags' do let(:tags) { [Fabricate(:tag, name: 'test'), Fabricate(:tag, name: 'foo')] } let(:payload) do @@ -299,7 +297,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end before do - subject.call(status, json) + subject.call(status, json, json) end it 'updates tags' do @@ -307,9 +305,9 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end end - context 'originally without mentions' do + context 'when originally without mentions' do before do - subject.call(status, json) + subject.call(status, json, json) end it 'updates mentions' do @@ -317,11 +315,11 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end end - context 'originally with mentions' do + context 'when originally with mentions' do let(:mentions) { [alice, bob] } before do - subject.call(status, json) + subject.call(status, json, json) end it 'updates mentions' do @@ -329,10 +327,10 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end end - context 'originally without media attachments' do + context 'when originally without media attachments' do before do - allow(RedownloadMediaWorker).to receive(:perform_async) - subject.call(status, json) + stub_request(:get, 'https://example.com/foo.png').to_return(body: attachment_fixture('emojo.png')) + subject.call(status, json, json) end let(:payload) do @@ -344,7 +342,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do updated: '2021-09-08T22:39:25Z', attachment: [ { type: 'Image', mediaType: 'image/png', url: 'https://example.com/foo.png' }, - ] + ], } end @@ -355,8 +353,8 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do expect(media_attachment.remote_url).to eq 'https://example.com/foo.png' end - it 'queues download of media attachments' do - expect(RedownloadMediaWorker).to have_received(:perform_async) + it 'fetches the attachment' do + expect(a_request(:get, 'https://example.com/foo.png')).to have_been_made end it 'records media change in edit' do @@ -364,7 +362,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end end - context 'originally with media attachments' do + context 'when originally with media attachments' do let(:media_attachments) { [Fabricate(:media_attachment, remote_url: 'https://example.com/foo.png'), Fabricate(:media_attachment, remote_url: 'https://example.com/unused.png')] } let(:payload) do @@ -376,13 +374,13 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do updated: '2021-09-08T22:39:25Z', attachment: [ { type: 'Image', mediaType: 'image/png', url: 'https://example.com/foo.png', name: 'A picture' }, - ] + ], } end before do allow(RedownloadMediaWorker).to receive(:perform_async) - subject.call(status, json) + subject.call(status, json, json) end it 'updates the existing media attachment in-place' do @@ -406,15 +404,15 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end end - context 'originally with a poll' do + context 'when originally with a poll' do before do poll = Fabricate(:poll, status: status) status.update(preloadable_poll: poll) - subject.call(status, json) + subject.call(status, json, json) end it 'removes poll' do - expect(status.reload.poll).to eq nil + expect(status.reload.poll).to be_nil end it 'records media change in edit' do @@ -422,7 +420,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end end - context 'originally without a poll' do + context 'when originally without a poll' do let(:payload) do { '@context': 'https://www.w3.org/ns/activitystreams', @@ -440,7 +438,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end before do - subject.call(status, json) + subject.call(status, json, json) end it 'creates a poll' do @@ -456,12 +454,12 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do end it 'creates edit history' do - subject.call(status, json) + subject.call(status, json, json) expect(status.edits.reload.map(&:text)).to eq ['Hello world', 'Hello universe'] end it 'sets edited timestamp' do - subject.call(status, json) + subject.call(status, json, json) expect(status.reload.edited_at.to_s).to eq '2021-09-08 22:39:25 UTC' end end diff --git a/spec/services/activitypub/synchronize_followers_service_spec.rb b/spec/services/activitypub/synchronize_followers_service_spec.rb index 7b4a5f8ff..f62376ab9 100644 --- a/spec/services/activitypub/synchronize_followers_service_spec.rb +++ b/spec/services/activitypub/synchronize_followers_service_spec.rb @@ -1,6 +1,10 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ActivityPub::SynchronizeFollowersService, type: :service do + subject { described_class.new } + let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account', inbox_url: 'http://example.com/inbox') } let(:alice) { Fabricate(:account, username: 'alice') } let(:bob) { Fabricate(:account, username: 'bob') } @@ -25,8 +29,6 @@ RSpec.describe ActivityPub::SynchronizeFollowersService, type: :service do }.with_indifferent_access end - subject { described_class.new } - shared_examples 'synchronizes followers' do before do alice.follow!(actor) @@ -91,7 +93,7 @@ RSpec.describe ActivityPub::SynchronizeFollowersService, type: :service do type: 'CollectionPage', partOf: collection_uri, items: items, - } + }, }.with_indifferent_access end diff --git a/spec/services/after_block_domain_from_account_service_spec.rb b/spec/services/after_block_domain_from_account_service_spec.rb index 006e3f4d2..9bfaa3580 100644 --- a/spec/services/after_block_domain_from_account_service_spec.rb +++ b/spec/services/after_block_domain_from_account_service_spec.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe AfterBlockDomainFromAccountService, type: :service do + subject { described_class.new } + let!(:wolf) { Fabricate(:account, username: 'wolf', domain: 'evil.org', inbox_url: 'https://evil.org/inbox', protocol: :activitypub) } let!(:alice) { Fabricate(:account, username: 'alice') } - subject { AfterBlockDomainFromAccountService.new } - before do stub_jsonld_contexts! allow(ActivityPub::DeliveryWorker).to receive(:perform_async) diff --git a/spec/services/after_block_service_spec.rb b/spec/services/after_block_service_spec.rb index 337766d06..d81bba1d8 100644 --- a/spec/services/after_block_service_spec.rb +++ b/spec/services/after_block_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe AfterBlockService, type: :service do diff --git a/spec/services/app_sign_up_service_spec.rb b/spec/services/app_sign_up_service_spec.rb index 8ec4d4a7a..253230496 100644 --- a/spec/services/app_sign_up_service_spec.rb +++ b/spec/services/app_sign_up_service_spec.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe AppSignUpService, type: :service do + subject { described_class.new } + let(:app) { Fabricate(:application, scopes: 'read write') } let(:good_params) { { username: 'alice', password: '12345678', email: 'good@email.com', agreement: true } } let(:remote_ip) { IPAddr.new('198.0.2.1') } - subject { described_class.new } - describe '#call' do it 'returns nil when registrations are closed' do tmp = Setting.registrations_mode diff --git a/spec/services/authorize_follow_service_spec.rb b/spec/services/authorize_follow_service_spec.rb index 888d694b6..d07645ab6 100644 --- a/spec/services/authorize_follow_service_spec.rb +++ b/spec/services/authorize_follow_service_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe AuthorizeFollowService, type: :service do - let(:sender) { Fabricate(:account, username: 'alice') } + subject { described_class.new } - subject { AuthorizeFollowService.new } + let(:sender) { Fabricate(:account, username: 'alice') } describe 'local' do let(:bob) { Fabricate(:account, username: 'bob') } diff --git a/spec/services/backup_service_spec.rb b/spec/services/backup_service_spec.rb new file mode 100644 index 000000000..1eb789d1f --- /dev/null +++ b/spec/services/backup_service_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe BackupService, type: :service do + subject(:service_call) { described_class.new.call(backup) } + + let!(:user) { Fabricate(:user) } + let!(:attachment) { Fabricate(:media_attachment, account: user.account) } + let!(:status) { Fabricate(:status, account: user.account, text: 'Hello', visibility: :public, media_attachments: [attachment]) } + let!(:private_status) { Fabricate(:status, account: user.account, text: 'secret', visibility: :private) } + let!(:favourite) { Fabricate(:favourite, account: user.account) } + let!(:bookmark) { Fabricate(:bookmark, account: user.account) } + let!(:backup) { Fabricate(:backup, user: user) } + + def read_zip_file(backup, filename) + file = Paperclip.io_adapters.for(backup.dump) + Zip::File.open(file) do |zipfile| + entry = zipfile.glob(filename).first + return entry.get_input_stream.read + end + end + + context 'when the user has an avatar and header' do + before do + user.account.update!(avatar: attachment_fixture('avatar.gif')) + user.account.update!(header: attachment_fixture('emojo.png')) + end + + it 'stores them as expected' do + service_call + + json = export_json(:actor) + avatar_path = json.dig('icon', 'url') + header_path = json.dig('image', 'url') + + expect(avatar_path).to_not be_nil + expect(header_path).to_not be_nil + + expect(read_zip_file(backup, avatar_path)).to be_present + expect(read_zip_file(backup, header_path)).to be_present + end + end + + it 'marks the backup as processed and exports files' do + expect { service_call }.to process_backup + + expect_outbox_export + expect_likes_export + expect_bookmarks_export + end + + def process_backup + change(backup, :processed).from(false).to(true) + end + + def expect_outbox_export + body = export_json_raw(:outbox) + json = Oj.load(body) + + aggregate_failures do + expect(body.scan('@context').count).to eq 1 + expect(json['@context']).to_not be_nil + expect(json['type']).to eq 'OrderedCollection' + expect(json['totalItems']).to eq 2 + expect(json['orderedItems'][0]['@context']).to be_nil + expect(json['orderedItems'][0]).to include_create_item(status) + expect(json['orderedItems'][1]).to include_create_item(private_status) + end + end + + def expect_likes_export + json = export_json(:likes) + + aggregate_failures do + expect(json['type']).to eq 'OrderedCollection' + expect(json['orderedItems']).to eq [ActivityPub::TagManager.instance.uri_for(favourite.status)] + end + end + + def expect_bookmarks_export + json = export_json(:bookmarks) + + aggregate_failures do + expect(json['type']).to eq 'OrderedCollection' + expect(json['orderedItems']).to eq [ActivityPub::TagManager.instance.uri_for(bookmark.status)] + end + end + + def export_json_raw(type) + read_zip_file(backup, "#{type}.json") + end + + def export_json(type) + Oj.load(export_json_raw(type)) + end + + def include_create_item(status) + include({ + 'type' => 'Create', + 'object' => include({ + 'id' => ActivityPub::TagManager.instance.uri_for(status), + 'content' => "

#{status.text}

", + }), + }) + end +end diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb index 920edeb13..8201c9d51 100644 --- a/spec/services/batched_remove_status_service_spec.rb +++ b/spec/services/batched_remove_status_service_spec.rb @@ -1,15 +1,17 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe BatchedRemoveStatusService, type: :service do - subject { BatchedRemoveStatusService.new } + subject { described_class.new } let!(:alice) { Fabricate(:account) } let!(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com') } let!(:jeff) { Fabricate(:account) } let!(:hank) { Fabricate(:account, username: 'hank', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } - let(:status1) { PostStatusService.new.call(alice, text: 'Hello @bob@example.com') } - let(:status2) { PostStatusService.new.call(alice, text: 'Another status') } + let(:status_alice_hello) { PostStatusService.new.call(alice, text: 'Hello @bob@example.com') } + let(:status_alice_other) { PostStatusService.new.call(alice, text: 'Another status') } before do allow(redis).to receive_messages(publish: nil) @@ -20,23 +22,23 @@ RSpec.describe BatchedRemoveStatusService, type: :service do jeff.follow!(alice) hank.follow!(alice) - status1 - status2 + status_alice_hello + status_alice_other - subject.call([status1, status2]) + subject.call([status_alice_hello, status_alice_other]) end it 'removes statuses' do - expect { Status.find(status1.id) }.to raise_error ActiveRecord::RecordNotFound - expect { Status.find(status2.id) }.to raise_error ActiveRecord::RecordNotFound + expect { Status.find(status_alice_hello.id) }.to raise_error ActiveRecord::RecordNotFound + expect { Status.find(status_alice_other.id) }.to raise_error ActiveRecord::RecordNotFound end it 'removes statuses from author\'s home feed' do - expect(HomeFeed.new(alice).get(10)).to_not include([status1.id, status2.id]) + expect(HomeFeed.new(alice).get(10).pluck(:id)).to_not include(status_alice_hello.id, status_alice_other.id) end it 'removes statuses from local follower\'s home feed' do - expect(HomeFeed.new(jeff).get(10)).to_not include([status1.id, status2.id]) + expect(HomeFeed.new(jeff).get(10).pluck(:id)).to_not include(status_alice_hello.id, status_alice_other.id) end it 'notifies streaming API of followers' do diff --git a/spec/services/block_domain_service_spec.rb b/spec/services/block_domain_service_spec.rb index 242b02fff..36dce9d19 100644 --- a/spec/services/block_domain_service_spec.rb +++ b/spec/services/block_domain_service_spec.rb @@ -1,13 +1,15 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe BlockDomainService, type: :service do - let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') } - let!(:bad_status1) { Fabricate(:status, account: bad_account, text: 'You suck') } - let!(:bad_status2) { Fabricate(:status, account: bad_account, text: 'Hahaha') } - let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status2, file: attachment_fixture('attachment.jpg')) } - let!(:already_banned_account) { Fabricate(:account, username: 'badguy', domain: 'evil.org', suspended: true, silenced: true) } + subject { described_class.new } - subject { BlockDomainService.new } + let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') } + let!(:bad_status_plain) { Fabricate(:status, account: bad_account, text: 'You suck') } + let!(:bad_status_with_attachment) { Fabricate(:status, account: bad_account, text: 'Hahaha') } + let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status_with_attachment, file: attachment_fixture('attachment.jpg')) } + let!(:already_banned_account) { Fabricate(:account, username: 'badguy', domain: 'evil.org', suspended: true, silenced: true) } describe 'for a suspension' do before do @@ -35,8 +37,8 @@ RSpec.describe BlockDomainService, type: :service do end it 'removes the remote accounts\'s statuses and media attachments' do - expect { bad_status1.reload }.to raise_exception ActiveRecord::RecordNotFound - expect { bad_status2.reload }.to raise_exception ActiveRecord::RecordNotFound + expect { bad_status_plain.reload }.to raise_exception ActiveRecord::RecordNotFound + expect { bad_status_with_attachment.reload }.to raise_exception ActiveRecord::RecordNotFound expect { bad_attachment.reload }.to raise_exception ActiveRecord::RecordNotFound end end @@ -67,9 +69,9 @@ RSpec.describe BlockDomainService, type: :service do end it 'leaves the domains status and attachments, but clears media' do - expect { bad_status1.reload }.not_to raise_error - expect { bad_status2.reload }.not_to raise_error - expect { bad_attachment.reload }.not_to raise_error + expect { bad_status_plain.reload }.to_not raise_error + expect { bad_status_with_attachment.reload }.to_not raise_error + expect { bad_attachment.reload }.to_not raise_error expect(bad_attachment.file.exists?).to be false end end diff --git a/spec/services/block_service_spec.rb b/spec/services/block_service_spec.rb index a53e1f928..5f7c2e8da 100644 --- a/spec/services/block_service_spec.rb +++ b/spec/services/block_service_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe BlockService, type: :service do - let(:sender) { Fabricate(:account, username: 'alice') } + subject { described_class.new } - subject { BlockService.new } + let(:sender) { Fabricate(:account, username: 'alice') } describe 'local' do let(:bob) { Fabricate(:account, username: 'bob') } diff --git a/spec/services/bootstrap_timeline_service_spec.rb b/spec/services/bootstrap_timeline_service_spec.rb index 16f3e9962..721a0337f 100644 --- a/spec/services/bootstrap_timeline_service_spec.rb +++ b/spec/services/bootstrap_timeline_service_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe BootstrapTimelineService, type: :service do - subject { BootstrapTimelineService.new } + subject { described_class.new } context 'when the new user has registered from an invite' do - let(:service) { double } + let(:service) { instance_double(FollowService) } let(:autofollow) { false } let(:inviter) { Fabricate(:user, confirmed_at: 2.days.ago) } let(:invite) { Fabricate(:invite, user: inviter, max_uses: nil, expires_at: 1.hour.from_now, autofollow: autofollow) } @@ -32,6 +34,5 @@ RSpec.describe BootstrapTimelineService, type: :service do expect(service).to_not have_received(:call) end end - end end diff --git a/spec/services/bulk_import_row_service_spec.rb b/spec/services/bulk_import_row_service_spec.rb new file mode 100644 index 000000000..a77acc073 --- /dev/null +++ b/spec/services/bulk_import_row_service_spec.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe BulkImportRowService do + subject { described_class.new } + + let(:account) { Fabricate(:account) } + let(:import) { Fabricate(:bulk_import, account: account, type: import_type) } + let(:import_row) { Fabricate(:bulk_import_row, bulk_import: import, data: data) } + + describe '#call' do + context 'when importing a follow' do + let(:import_type) { 'following' } + let(:target_account) { Fabricate(:account) } + let(:service_double) { instance_double(FollowService, call: nil) } + let(:data) do + { 'acct' => target_account.acct } + end + + before do + allow(FollowService).to receive(:new).and_return(service_double) + end + + it 'calls FollowService with the expected arguments and returns true' do + expect(subject.call(import_row)).to be true + + expect(service_double).to have_received(:call).with(account, target_account, { reblogs: nil, notify: nil, languages: nil }) + end + end + + context 'when importing a block' do + let(:import_type) { 'blocking' } + let(:target_account) { Fabricate(:account) } + let(:service_double) { instance_double(BlockService, call: nil) } + let(:data) do + { 'acct' => target_account.acct } + end + + before do + allow(BlockService).to receive(:new).and_return(service_double) + end + + it 'calls BlockService with the expected arguments and returns true' do + expect(subject.call(import_row)).to be true + + expect(service_double).to have_received(:call).with(account, target_account) + end + end + + context 'when importing a mute' do + let(:import_type) { 'muting' } + let(:target_account) { Fabricate(:account) } + let(:service_double) { instance_double(MuteService, call: nil) } + let(:data) do + { 'acct' => target_account.acct } + end + + before do + allow(MuteService).to receive(:new).and_return(service_double) + end + + it 'calls MuteService with the expected arguments and returns true' do + expect(subject.call(import_row)).to be true + + expect(service_double).to have_received(:call).with(account, target_account, { notifications: nil }) + end + end + + context 'when importing a bookmark' do + let(:import_type) { 'bookmarks' } + let(:data) do + { 'uri' => ActivityPub::TagManager.instance.uri_for(target_status) } + end + + context 'when the status is public' do + let(:target_status) { Fabricate(:status) } + + it 'bookmarks the status and returns true' do + expect(subject.call(import_row)).to be true + expect(account.bookmarked?(target_status)).to be true + end + end + + context 'when the status is not accessible to the user' do + let(:target_status) { Fabricate(:status, visibility: :direct) } + + it 'does not bookmark the status and returns false' do + expect(subject.call(import_row)).to be false + expect(account.bookmarked?(target_status)).to be false + end + end + end + + context 'when importing a list row' do + let(:import_type) { 'lists' } + let(:target_account) { Fabricate(:account) } + let(:data) do + { 'acct' => target_account.acct, 'list_name' => 'my list' } + end + + shared_examples 'common behavior' do + context 'when the target account is already followed' do + before do + account.follow!(target_account) + end + + it 'returns true' do + expect(subject.call(import_row)).to be true + end + + it 'adds the target account to the list' do + expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + end + end + + context 'when the user already requested to follow the target account' do + before do + account.request_follow!(target_account) + end + + it 'returns true' do + expect(subject.call(import_row)).to be true + end + + it 'adds the target account to the list' do + expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + end + end + + context 'when the target account is neither followed nor requested' do + it 'returns true' do + expect(subject.call(import_row)).to be true + end + + it 'adds the target account to the list' do + expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + end + end + + context 'when the target account is the user themself' do + let(:target_account) { account } + + it 'returns true' do + expect(subject.call(import_row)).to be true + end + + it 'adds the target account to the list' do + expect { subject.call(import_row) }.to change { ListAccount.joins(:list).exists?(account_id: target_account.id, list: { title: 'my list' }) }.from(false).to(true) + end + end + end + + context 'when the list does not exist yet' do + include_examples 'common behavior' + end + + context 'when the list exists' do + before do + Fabricate(:list, account: account, title: 'my list') + end + + include_examples 'common behavior' + + it 'does not create a new list' do + account.follow!(target_account) + + expect { subject.call(import_row) }.to_not(change { List.where(title: 'my list').count }) + end + end + end + end +end diff --git a/spec/services/bulk_import_service_spec.rb b/spec/services/bulk_import_service_spec.rb new file mode 100644 index 000000000..281b642ea --- /dev/null +++ b/spec/services/bulk_import_service_spec.rb @@ -0,0 +1,417 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe BulkImportService do + subject { described_class.new } + + let(:account) { Fabricate(:account) } + let(:import) { Fabricate(:bulk_import, account: account, type: import_type, overwrite: overwrite, state: :in_progress, imported_items: 0, processed_items: 0) } + + before do + import.update(total_items: import.rows.count) + end + + describe '#call' do + around do |example| + Sidekiq::Testing.fake! do + example.run + Sidekiq::Worker.clear_all + end + end + + context 'when importing follows' do + let(:import_type) { 'following' } + let(:overwrite) { false } + + let!(:rows) do + [ + { 'acct' => 'user@foo.bar' }, + { 'acct' => 'unknown@unknown.bar' }, + ].map { |data| import.rows.create!(data: data) } + end + + before do + account.follow!(Fabricate(:account)) + end + + it 'does not immediately change who the account follows' do + expect { subject.call(import) }.to_not(change { account.reload.active_relationships.to_a }) + end + + it 'enqueues workers for the expected rows' do + subject.call(import) + expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows.map(&:id)) + end + + it 'requests to follow all the listed users once the workers have run' do + subject.call(import) + + resolve_account_service_double = instance_double(ResolveAccountService) + allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double) + allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) } + allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) } + + Import::RowWorker.drain + + expect(FollowRequest.includes(:target_account).where(account: account).map(&:target_account).map(&:acct)).to contain_exactly('user@foo.bar', 'unknown@unknown.bar') + end + end + + context 'when importing follows with overwrite' do + let(:import_type) { 'following' } + let(:overwrite) { true } + + let!(:followed) { Fabricate(:account, username: 'followed', domain: 'foo.bar', protocol: :activitypub) } + let!(:to_be_unfollowed) { Fabricate(:account, username: 'to_be_unfollowed', domain: 'foo.bar', protocol: :activitypub) } + + let!(:rows) do + [ + { 'acct' => 'followed@foo.bar', 'show_reblogs' => false, 'notify' => true, 'languages' => ['en'] }, + { 'acct' => 'user@foo.bar' }, + { 'acct' => 'unknown@unknown.bar' }, + ].map { |data| import.rows.create!(data: data) } + end + + before do + account.follow!(followed, reblogs: true, notify: false) + account.follow!(to_be_unfollowed) + end + + it 'unfollows user not present on list' do + subject.call(import) + expect(account.following?(to_be_unfollowed)).to be false + end + + it 'updates the existing follow relationship as expected' do + expect { subject.call(import) }.to change { Follow.where(account: account, target_account: followed).pick(:show_reblogs, :notify, :languages) }.from([true, false, nil]).to([false, true, ['en']]) + end + + it 'enqueues workers for the expected rows' do + subject.call(import) + expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows[1..].map(&:id)) + end + + it 'requests to follow all the expected users once the workers have run' do + subject.call(import) + + resolve_account_service_double = instance_double(ResolveAccountService) + allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double) + allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) } + allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) } + + Import::RowWorker.drain + + expect(FollowRequest.includes(:target_account).where(account: account).map(&:target_account).map(&:acct)).to contain_exactly('user@foo.bar', 'unknown@unknown.bar') + end + end + + context 'when importing blocks' do + let(:import_type) { 'blocking' } + let(:overwrite) { false } + + let!(:rows) do + [ + { 'acct' => 'user@foo.bar' }, + { 'acct' => 'unknown@unknown.bar' }, + ].map { |data| import.rows.create!(data: data) } + end + + before do + account.block!(Fabricate(:account, username: 'already_blocked', domain: 'remote.org')) + end + + it 'does not immediately change who the account blocks' do + expect { subject.call(import) }.to_not(change { account.reload.blocking.to_a }) + end + + it 'enqueues workers for the expected rows' do + subject.call(import) + expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows.map(&:id)) + end + + it 'blocks all the listed users once the workers have run' do + subject.call(import) + + resolve_account_service_double = instance_double(ResolveAccountService) + allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double) + allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) } + allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) } + + Import::RowWorker.drain + + expect(account.blocking.map(&:acct)).to contain_exactly('already_blocked@remote.org', 'user@foo.bar', 'unknown@unknown.bar') + end + end + + context 'when importing blocks with overwrite' do + let(:import_type) { 'blocking' } + let(:overwrite) { true } + + let!(:blocked) { Fabricate(:account, username: 'blocked', domain: 'foo.bar', protocol: :activitypub) } + let!(:to_be_unblocked) { Fabricate(:account, username: 'to_be_unblocked', domain: 'foo.bar', protocol: :activitypub) } + + let!(:rows) do + [ + { 'acct' => 'blocked@foo.bar' }, + { 'acct' => 'user@foo.bar' }, + { 'acct' => 'unknown@unknown.bar' }, + ].map { |data| import.rows.create!(data: data) } + end + + before do + account.block!(blocked) + account.block!(to_be_unblocked) + end + + it 'unblocks user not present on list' do + subject.call(import) + expect(account.blocking?(to_be_unblocked)).to be false + end + + it 'enqueues workers for the expected rows' do + subject.call(import) + expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows[1..].map(&:id)) + end + + it 'requests to follow all the expected users once the workers have run' do + subject.call(import) + + resolve_account_service_double = instance_double(ResolveAccountService) + allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double) + allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) } + allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) } + + Import::RowWorker.drain + + expect(account.blocking.map(&:acct)).to contain_exactly('blocked@foo.bar', 'user@foo.bar', 'unknown@unknown.bar') + end + end + + context 'when importing mutes' do + let(:import_type) { 'muting' } + let(:overwrite) { false } + + let!(:rows) do + [ + { 'acct' => 'user@foo.bar' }, + { 'acct' => 'unknown@unknown.bar' }, + ].map { |data| import.rows.create!(data: data) } + end + + before do + account.mute!(Fabricate(:account, username: 'already_muted', domain: 'remote.org')) + end + + it 'does not immediately change who the account blocks' do + expect { subject.call(import) }.to_not(change { account.reload.muting.to_a }) + end + + it 'enqueues workers for the expected rows' do + subject.call(import) + expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows.map(&:id)) + end + + it 'mutes all the listed users once the workers have run' do + subject.call(import) + + resolve_account_service_double = instance_double(ResolveAccountService) + allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double) + allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) } + allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) } + + Import::RowWorker.drain + + expect(account.muting.map(&:acct)).to contain_exactly('already_muted@remote.org', 'user@foo.bar', 'unknown@unknown.bar') + end + end + + context 'when importing mutes with overwrite' do + let(:import_type) { 'muting' } + let(:overwrite) { true } + + let!(:muted) { Fabricate(:account, username: 'muted', domain: 'foo.bar', protocol: :activitypub) } + let!(:to_be_unmuted) { Fabricate(:account, username: 'to_be_unmuted', domain: 'foo.bar', protocol: :activitypub) } + + let!(:rows) do + [ + { 'acct' => 'muted@foo.bar', 'hide_notifications' => true }, + { 'acct' => 'user@foo.bar' }, + { 'acct' => 'unknown@unknown.bar' }, + ].map { |data| import.rows.create!(data: data) } + end + + before do + account.mute!(muted, notifications: false) + account.mute!(to_be_unmuted) + end + + it 'updates the existing mute as expected' do + expect { subject.call(import) }.to change { Mute.where(account: account, target_account: muted).pick(:hide_notifications) }.from(false).to(true) + end + + it 'unblocks user not present on list' do + subject.call(import) + expect(account.muting?(to_be_unmuted)).to be false + end + + it 'enqueues workers for the expected rows' do + subject.call(import) + expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows[1..].map(&:id)) + end + + it 'requests to follow all the expected users once the workers have run' do + subject.call(import) + + resolve_account_service_double = instance_double(ResolveAccountService) + allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double) + allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) } + allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) } + + Import::RowWorker.drain + + expect(account.muting.map(&:acct)).to contain_exactly('muted@foo.bar', 'user@foo.bar', 'unknown@unknown.bar') + end + end + + context 'when importing domain blocks' do + let(:import_type) { 'domain_blocking' } + let(:overwrite) { false } + + let!(:rows) do + [ + { 'domain' => 'blocked.com' }, + { 'domain' => 'to_block.com' }, + ].map { |data| import.rows.create!(data: data) } + end + + before do + account.block_domain!('alreadyblocked.com') + account.block_domain!('blocked.com') + end + + it 'blocks all the new domains' do + subject.call(import) + expect(account.domain_blocks.pluck(:domain)).to contain_exactly('alreadyblocked.com', 'blocked.com', 'to_block.com') + end + + it 'marks the import as finished' do + subject.call(import) + expect(import.reload.finished?).to be true + end + end + + context 'when importing domain blocks with overwrite' do + let(:import_type) { 'domain_blocking' } + let(:overwrite) { true } + + let!(:rows) do + [ + { 'domain' => 'blocked.com' }, + { 'domain' => 'to_block.com' }, + ].map { |data| import.rows.create!(data: data) } + end + + before do + account.block_domain!('alreadyblocked.com') + account.block_domain!('blocked.com') + end + + it 'blocks all the new domains' do + subject.call(import) + expect(account.domain_blocks.pluck(:domain)).to contain_exactly('blocked.com', 'to_block.com') + end + + it 'marks the import as finished' do + subject.call(import) + expect(import.reload.finished?).to be true + end + end + + context 'when importing bookmarks' do + let(:import_type) { 'bookmarks' } + let(:overwrite) { false } + + let!(:already_bookmarked) { Fabricate(:status, uri: 'https://already.bookmarked/1') } + let!(:status) { Fabricate(:status, uri: 'https://foo.bar/posts/1') } + let!(:inaccessible_status) { Fabricate(:status, uri: 'https://foo.bar/posts/inaccessible', visibility: :direct) } + let!(:bookmarked) { Fabricate(:status, uri: 'https://foo.bar/posts/already-bookmarked') } + + let!(:rows) do + [ + { 'uri' => status.uri }, + { 'uri' => inaccessible_status.uri }, + { 'uri' => bookmarked.uri }, + { 'uri' => 'https://domain.unknown/foo' }, + { 'uri' => 'https://domain.unknown/private' }, + ].map { |data| import.rows.create!(data: data) } + end + + before do + account.bookmarks.create!(status: already_bookmarked) + account.bookmarks.create!(status: bookmarked) + end + + it 'enqueues workers for the expected rows' do + subject.call(import) + expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows.map(&:id)) + end + + it 'updates the bookmarks as expected once the workers have run' do + subject.call(import) + + service_double = instance_double(ActivityPub::FetchRemoteStatusService) + allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service_double) + allow(service_double).to receive(:call).with('https://domain.unknown/foo') { Fabricate(:status, uri: 'https://domain.unknown/foo') } + allow(service_double).to receive(:call).with('https://domain.unknown/private') { Fabricate(:status, uri: 'https://domain.unknown/private', visibility: :direct) } + + Import::RowWorker.drain + + expect(account.bookmarks.map(&:status).map(&:uri)).to contain_exactly(already_bookmarked.uri, status.uri, bookmarked.uri, 'https://domain.unknown/foo') + end + end + + context 'when importing bookmarks with overwrite' do + let(:import_type) { 'bookmarks' } + let(:overwrite) { true } + + let!(:already_bookmarked) { Fabricate(:status, uri: 'https://already.bookmarked/1') } + let!(:status) { Fabricate(:status, uri: 'https://foo.bar/posts/1') } + let!(:inaccessible_status) { Fabricate(:status, uri: 'https://foo.bar/posts/inaccessible', visibility: :direct) } + let!(:bookmarked) { Fabricate(:status, uri: 'https://foo.bar/posts/already-bookmarked') } + + let!(:rows) do + [ + { 'uri' => status.uri }, + { 'uri' => inaccessible_status.uri }, + { 'uri' => bookmarked.uri }, + { 'uri' => 'https://domain.unknown/foo' }, + { 'uri' => 'https://domain.unknown/private' }, + ].map { |data| import.rows.create!(data: data) } + end + + before do + account.bookmarks.create!(status: already_bookmarked) + account.bookmarks.create!(status: bookmarked) + end + + it 'enqueues workers for the expected rows' do + subject.call(import) + expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows.map(&:id)) + end + + it 'updates the bookmarks as expected once the workers have run' do + subject.call(import) + + service_double = instance_double(ActivityPub::FetchRemoteStatusService) + allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service_double) + allow(service_double).to receive(:call).with('https://domain.unknown/foo') { Fabricate(:status, uri: 'https://domain.unknown/foo') } + allow(service_double).to receive(:call).with('https://domain.unknown/private') { Fabricate(:status, uri: 'https://domain.unknown/private', visibility: :direct) } + + Import::RowWorker.drain + + expect(account.bookmarks.map(&:status).map(&:uri)).to contain_exactly(status.uri, bookmarked.uri, 'https://domain.unknown/foo') + end + end + end +end diff --git a/spec/services/clear_domain_media_service_spec.rb b/spec/services/clear_domain_media_service_spec.rb index 45b92e2c9..9766e62de 100644 --- a/spec/services/clear_domain_media_service_spec.rb +++ b/spec/services/clear_domain_media_service_spec.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ClearDomainMediaService, type: :service do - let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') } - let!(:bad_status1) { Fabricate(:status, account: bad_account, text: 'You suck') } - let!(:bad_status2) { Fabricate(:status, account: bad_account, text: 'Hahaha') } - let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status2, file: attachment_fixture('attachment.jpg')) } + subject { described_class.new } - subject { ClearDomainMediaService.new } + let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') } + let!(:bad_status_plain) { Fabricate(:status, account: bad_account, text: 'You suck') } + let!(:bad_status_with_attachment) { Fabricate(:status, account: bad_account, text: 'Hahaha') } + let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status_with_attachment, file: attachment_fixture('attachment.jpg')) } describe 'for a silence with reject media' do before do @@ -14,9 +16,9 @@ RSpec.describe ClearDomainMediaService, type: :service do end it 'leaves the domains status and attachments, but clears media' do - expect { bad_status1.reload }.not_to raise_error - expect { bad_status2.reload }.not_to raise_error - expect { bad_attachment.reload }.not_to raise_error + expect { bad_status_plain.reload }.to_not raise_error + expect { bad_status_with_attachment.reload }.to_not raise_error + expect { bad_attachment.reload }.to_not raise_error expect(bad_attachment.file.exists?).to be false end end diff --git a/spec/services/delete_account_service_spec.rb b/spec/services/delete_account_service_spec.rb index 1fbe4d07c..68ab491e4 100644 --- a/spec/services/delete_account_service_spec.rb +++ b/spec/services/delete_account_service_spec.rb @@ -1,7 +1,11 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe DeleteAccountService, type: :service do shared_examples 'common behavior' do + subject { described_class.new.call(account) } + let!(:status) { Fabricate(:status, account: account) } let!(:mention) { Fabricate(:mention, account: local_follower) } let!(:status_with_mention) { Fabricate(:status, account: account, mentions: [mention]) } @@ -23,8 +27,6 @@ RSpec.describe DeleteAccountService, type: :service do let!(:account_note) { Fabricate(:account_note, account: account) } - subject { described_class.new.call(account) } - it 'deletes associated owned records' do expect { subject }.to change { [ @@ -50,21 +52,21 @@ RSpec.describe DeleteAccountService, type: :service do it 'deletes associated target notifications' do expect { subject }.to change { - [ - 'poll', 'favourite', 'status', 'mention', 'follow' - ].map { |type| Notification.where(type: type).count } + %w( + poll favourite status mention follow + ).map { |type| Notification.where(type: type).count } }.from([1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0]) end end describe '#call on local account' do before do - stub_request(:post, "https://alice.com/inbox").to_return(status: 201) - stub_request(:post, "https://bob.com/inbox").to_return(status: 201) + stub_request(:post, 'https://alice.com/inbox').to_return(status: 201) + stub_request(:post, 'https://bob.com/inbox').to_return(status: 201) end - let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', protocol: :activitypub) } - let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) } + let!(:remote_alice) { Fabricate(:account, inbox_url: 'https://alice.com/inbox', domain: 'alice.com', protocol: :activitypub) } + let!(:remote_bob) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', domain: 'bob.com', protocol: :activitypub) } include_examples 'common behavior' do let!(:account) { Fabricate(:account) } @@ -72,25 +74,47 @@ RSpec.describe DeleteAccountService, type: :service do it 'sends a delete actor activity to all known inboxes' do subject - expect(a_request(:post, "https://alice.com/inbox")).to have_been_made.once - expect(a_request(:post, "https://bob.com/inbox")).to have_been_made.once + expect(a_request(:post, 'https://alice.com/inbox')).to have_been_made.once + expect(a_request(:post, 'https://bob.com/inbox')).to have_been_made.once end end end describe '#call on remote account' do before do - stub_request(:post, "https://alice.com/inbox").to_return(status: 201) - stub_request(:post, "https://bob.com/inbox").to_return(status: 201) + stub_request(:post, 'https://alice.com/inbox').to_return(status: 201) + stub_request(:post, 'https://bob.com/inbox').to_return(status: 201) end include_examples 'common behavior' do - let!(:account) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub) } + let!(:account) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub, domain: 'bob.com') } let!(:local_follower) { Fabricate(:account) } - it 'sends a reject follow to follower inboxes' do + it 'sends expected activities to followed and follower inboxes' do subject - expect(a_request(:post, account.inbox_url)).to have_been_made.once + + expect(a_request(:post, account.inbox_url).with( + body: + hash_including({ + 'type' => 'Reject', + 'object' => hash_including({ + 'type' => 'Follow', + 'actor' => account.uri, + 'object' => ActivityPub::TagManager.instance.uri_for(local_follower), + }), + }) + )).to have_been_made.once + + expect(a_request(:post, account.inbox_url).with( + body: hash_including({ + 'type' => 'Undo', + 'object' => hash_including({ + 'type' => 'Follow', + 'actor' => ActivityPub::TagManager.instance.uri_for(local_follower), + 'object' => account.uri, + }), + }) + )).to have_been_made.once end end end diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb index 59e15d230..3b554f9ea 100644 --- a/spec/services/fan_out_on_write_service_spec.rb +++ b/spec/services/fan_out_on_write_service_spec.rb @@ -1,16 +1,17 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe FanOutOnWriteService, type: :service do + subject { described_class.new } + let(:last_active_at) { Time.now.utc } + let(:status) { Fabricate(:status, account: alice, visibility: visibility, text: 'Hello @bob #hoge') } let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at).account } let!(:bob) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { username: 'bob' }).account } let!(:tom) { Fabricate(:user, current_sign_in_at: last_active_at).account } - subject { described_class.new } - - let(:status) { Fabricate(:status, account: alice, visibility: visibility, text: 'Hello @bob #hoge') } - before do bob.follow!(alice) tom.follow!(alice) diff --git a/spec/services/favourite_service_spec.rb b/spec/services/favourite_service_spec.rb index 94a8111dd..782c235c4 100644 --- a/spec/services/favourite_service_spec.rb +++ b/spec/services/favourite_service_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe FavouriteService, type: :service do - let(:sender) { Fabricate(:account, username: 'alice') } + subject { described_class.new } - subject { FavouriteService.new } + let(:sender) { Fabricate(:account, username: 'alice') } describe 'local' do let(:bob) { Fabricate(:account) } @@ -23,7 +25,7 @@ RSpec.describe FavouriteService, type: :service do let(:status) { Fabricate(:status, account: bob) } before do - stub_request(:post, "http://example.com/inbox").to_return(:status => 200, :body => "", :headers => {}) + stub_request(:post, 'http://example.com/inbox').to_return(status: 200, body: '', headers: {}) subject.call(sender, status) end @@ -32,7 +34,7 @@ RSpec.describe FavouriteService, type: :service do end it 'sends a like activity' do - expect(a_request(:post, "http://example.com/inbox")).to have_been_made.once + expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once end end end diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb index 7a758f910..592dce24d 100644 --- a/spec/services/fetch_link_card_service_spec.rb +++ b/spec/services/fetch_link_card_service_spec.rb @@ -1,107 +1,275 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe FetchLinkCardService, type: :service do subject { described_class.new } + let(:html) { 'Hello world' } + let(:oembed_cache) { nil } + before do - stub_request(:get, 'http://example.xn--fiqs8s/').to_return(request_fixture('idn.txt')) + stub_request(:get, 'http://example.com/html').to_return(headers: { 'Content-Type' => 'text/html' }, body: html) + stub_request(:get, 'http://example.com/not-found').to_return(status: 404, headers: { 'Content-Type' => 'text/html' }, body: html) + stub_request(:get, 'http://example.com/text').to_return(status: 404, headers: { 'Content-Type' => 'text/plain' }, body: 'Hello') + stub_request(:get, 'http://example.com/redirect').to_return(status: 302, headers: { 'Location' => 'http://example.com/html' }) + stub_request(:get, 'http://example.com/redirect-to-404').to_return(status: 302, headers: { 'Location' => 'http://example.com/not-found' }) + stub_request(:get, 'http://example.com/oembed?url=http://example.com/html').to_return(headers: { 'Content-Type' => 'application/json' }, body: '{ "version": "1.0", "type": "link", "title": "oEmbed title" }') + stub_request(:get, 'http://example.com/oembed?format=json&url=http://example.com/html').to_return(headers: { 'Content-Type' => 'application/json' }, body: '{ "version": "1.0", "type": "link", "title": "oEmbed title" }') + + stub_request(:get, 'http://example.xn--fiqs8s') + stub_request(:get, 'http://example.com/日本語') + stub_request(:get, 'http://example.com/test?data=file.gpx%5E1') + stub_request(:get, 'http://example.com/test-') + stub_request(:get, 'http://example.com/sjis').to_return(request_fixture('sjis.txt')) stub_request(:get, 'http://example.com/sjis_with_wrong_charset').to_return(request_fixture('sjis_with_wrong_charset.txt')) stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt')) - stub_request(:get, 'http://example.com/日本語').to_return(request_fixture('sjis.txt')) - stub_request(:get, 'https://github.com/qbi/WannaCry').to_return(status: 404) - stub_request(:get, 'http://example.com/test?data=file.gpx%5E1').to_return(status: 200) - stub_request(:get, 'http://example.com/test-').to_return(request_fixture('idn.txt')) stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt')) + stub_request(:get, 'http://example.com/low_confidence_latin1').to_return(request_fixture('low_confidence_latin1.txt')) + + Rails.cache.write('oembed_endpoint:example.com', oembed_cache) if oembed_cache subject.call(status) end - context 'in a local status' do - context do + context 'with a local status' do + context 'with URL of a regular HTML page' do + let(:status) { Fabricate(:status, text: 'http://example.com/html') } + + it 'creates preview card' do + expect(status.preview_card).to_not be_nil + expect(status.preview_card.url).to eq 'http://example.com/html' + expect(status.preview_card.title).to eq 'Hello world' + end + end + + context 'with URL of a page with no title' do + let(:status) { Fabricate(:status, text: 'http://example.com/html') } + let(:html) { '' } + + it 'does not create a preview card' do + expect(status.preview_card).to be_nil + end + end + + context 'with a URL of a plain-text page' do + let(:status) { Fabricate(:status, text: 'http://example.com/text') } + + it 'does not create a preview card' do + expect(status.preview_card).to be_nil + end + end + + context 'with multiple URLs' do + let(:status) { Fabricate(:status, text: 'ftp://example.com http://example.com/html http://example.com/text') } + + it 'fetches the first valid URL' do + expect(a_request(:get, 'http://example.com/html')).to have_been_made + end + + it 'does not fetch the second valid URL' do + expect(a_request(:get, 'http://example.com/text/')).to_not have_been_made + end + end + + context 'with a redirect URL' do + let(:status) { Fabricate(:status, text: 'http://example.com/redirect') } + + it 'follows redirect' do + expect(a_request(:get, 'http://example.com/redirect')).to have_been_made.once + expect(a_request(:get, 'http://example.com/html')).to have_been_made.once + end + + it 'creates preview card' do + expect(status.preview_card).to_not be_nil + expect(status.preview_card.url).to eq 'http://example.com/html' + expect(status.preview_card.title).to eq 'Hello world' + end + end + + context 'with a broken redirect URL' do + let(:status) { Fabricate(:status, text: 'http://example.com/redirect-to-404') } + + it 'follows redirect' do + expect(a_request(:get, 'http://example.com/redirect-to-404')).to have_been_made.once + expect(a_request(:get, 'http://example.com/not-found')).to have_been_made.once + end + + it 'does not create a preview card' do + expect(status.preview_card).to be_nil + end + end + + context 'with a 404 URL' do + let(:status) { Fabricate(:status, text: 'http://example.com/not-found') } + + it 'does not create a preview card' do + expect(status.preview_card).to be_nil + end + end + + context 'with an IDN URL' do let(:status) { Fabricate(:status, text: 'Check out http://example.中国') } - it 'works with IDN URLs' do - expect(a_request(:get, 'http://example.xn--fiqs8s/')).to have_been_made.at_least_once + it 'fetches the URL' do + expect(a_request(:get, 'http://example.xn--fiqs8s/')).to have_been_made.once end end - context do + context 'with a URL of a page in Shift JIS encoding' do let(:status) { Fabricate(:status, text: 'Check out http://example.com/sjis') } - it 'works with SJIS' do - expect(a_request(:get, 'http://example.com/sjis')).to have_been_made.at_least_once - expect(status.preview_cards.first.title).to eq("SJISのページ") + it 'decodes the HTML' do + expect(status.preview_cards.first.title).to eq('SJISのページ') end end - context do + context 'with a URL of a page in Shift JIS encoding labeled as UTF-8' do let(:status) { Fabricate(:status, text: 'Check out http://example.com/sjis_with_wrong_charset') } - it 'works with SJIS even with wrong charset header' do - expect(a_request(:get, 'http://example.com/sjis_with_wrong_charset')).to have_been_made.at_least_once - expect(status.preview_cards.first.title).to eq("SJISのページ") + it 'decodes the HTML despite the wrong charset header' do + expect(status.preview_cards.first.title).to eq('SJISのページ') end end - context do + context 'with a URL of a page in KOI8-R encoding' do let(:status) { Fabricate(:status, text: 'Check out http://example.com/koi8-r') } - it 'works with koi8-r' do - expect(a_request(:get, 'http://example.com/koi8-r')).to have_been_made.at_least_once - expect(status.preview_cards.first.title).to eq("Московя начинаетъ только въ XVI ст. привлекать внимане иностранцевъ.") + it 'decodes the HTML' do + expect(status.preview_cards.first.title).to eq('Московя начинаетъ только въ XVI ст. привлекать внимане иностранцевъ.') end end - context do + context 'with a URL of a page in Windows-1251 encoding' do let(:status) { Fabricate(:status, text: 'Check out http://example.com/windows-1251') } - it 'works with windows-1251' do - expect(a_request(:get, 'http://example.com/windows-1251')).to have_been_made.at_least_once + it 'decodes the HTML' do expect(status.preview_cards.first.title).to eq('сэмпл текст') end end - context do + context 'with a URL of a page in ISO-8859-1 encoding, that charlock_holmes cannot detect' do + let(:status) { Fabricate(:status, text: 'Check out http://example.com/low_confidence_latin1') } + + it 'decodes the HTML' do + expect(status.preview_card.title).to eq("Tofu á l'orange") + end + end + + context 'with a Japanese path URL' do let(:status) { Fabricate(:status, text: 'テストhttp://example.com/日本語') } - it 'works with Japanese path string' do - expect(a_request(:get, 'http://example.com/日本語')).to have_been_made.at_least_once - expect(status.preview_cards.first.title).to eq("SJISのページ") + it 'fetches the URL' do + expect(a_request(:get, 'http://example.com/日本語')).to have_been_made.once end end - context do + context 'with a hyphen-suffixed URL' do let(:status) { Fabricate(:status, text: 'test http://example.com/test-') } - it 'works with a URL ending with a hyphen' do - expect(a_request(:get, 'http://example.com/test-')).to have_been_made.at_least_once + it 'fetches the URL' do + expect(a_request(:get, 'http://example.com/test-')).to have_been_made.once end end - context do + context 'with a caret-suffixed URL' do + let(:status) { Fabricate(:status, text: 'test http://example.com/test?data=file.gpx^1') } + + it 'fetches the URL' do + expect(a_request(:get, 'http://example.com/test?data=file.gpx%5E1')).to have_been_made.once + end + + it 'does not strip the caret before fetching' do + expect(a_request(:get, 'http://example.com/test?data=file.gpx')).to_not have_been_made + end + end + + context 'with a non-isolated URL' do let(:status) { Fabricate(:status, text: 'testhttp://example.com/sjis') } - it 'does not fetch URLs with not isolated from their surroundings' do + it 'does not fetch URLs not isolated from their surroundings' do expect(a_request(:get, 'http://example.com/sjis')).to_not have_been_made end end - context do - let(:status) { Fabricate(:status, text: 'test http://example.com/test?data=file.gpx^1') } + context 'with an URL too long for PostgreSQL unique indexes' do + let(:url) { "http://example.com/#{'a' * 2674}" } + let(:status) { Fabricate(:status, text: url) } - it 'does fetch URLs with a caret in search params' do - expect(a_request(:get, 'http://example.com/test?data=file.gpx')).to_not have_been_made - expect(a_request(:get, 'http://example.com/test?data=file.gpx%5E1')).to have_been_made.once + it 'does not fetch the URL' do + expect(a_request(:get, url)).to_not have_been_made + end + + it 'does not create a preview card' do + expect(status.preview_card).to be_nil + end + end + + context 'with a URL of a page with oEmbed support' do + let(:html) { 'Hello world' } + let(:status) { Fabricate(:status, text: 'http://example.com/html') } + + it 'fetches the oEmbed URL' do + expect(a_request(:get, 'http://example.com/oembed?url=http://example.com/html')).to have_been_made.once + end + + it 'creates preview card' do + expect(status.preview_card).to_not be_nil + expect(status.preview_card.url).to eq 'http://example.com/html' + expect(status.preview_card.title).to eq 'oEmbed title' + end + + context 'when oEmbed endpoint cache populated' do + let(:oembed_cache) { { endpoint: 'http://example.com/oembed?format=json&url={url}', format: :json } } + + it 'uses the cached oEmbed response' do + expect(a_request(:get, 'http://example.com/oembed?url=http://example.com/html')).to_not have_been_made + expect(a_request(:get, 'http://example.com/oembed?format=json&url=http://example.com/html')).to have_been_made + end + + it 'creates preview card' do + expect(status.preview_card).to_not be_nil + expect(status.preview_card.url).to eq 'http://example.com/html' + expect(status.preview_card.title).to eq 'oEmbed title' + end + end + + # If the original HTML URL for whatever reason (e.g. DOS protection) redirects to + # an error page, we can still use the cached oEmbed but should not use the + # redirect URL on the card. + context 'when oEmbed endpoint cache populated but page returns 404' do + let(:status) { Fabricate(:status, text: 'http://example.com/redirect-to-404') } + let(:oembed_cache) { { endpoint: 'http://example.com/oembed?url=http://example.com/html', format: :json } } + + it 'uses the cached oEmbed response' do + expect(a_request(:get, 'http://example.com/oembed?url=http://example.com/html')).to have_been_made + end + + it 'creates preview card' do + expect(status.preview_card).to_not be_nil + expect(status.preview_card.title).to eq 'oEmbed title' + end + + it 'uses the original URL' do + expect(status.preview_card&.url).to eq 'http://example.com/redirect-to-404' + end end end end - context 'in a remote status' do - let(:status) { Fabricate(:status, account: Fabricate(:account, domain: 'example.com'), text: 'Habt ihr ein paar gute Links zu foo #Wannacry herumfliegen? Ich will mal unter
https://github.com/qbi/WannaCry was sammeln. !security ') } + context 'with a remote status' do + let(:status) do + Fabricate(:status, account: Fabricate(:account, domain: 'example.com'), text: <<-TEXT) + Habt ihr ein paar gute Links zu foo + #Wannacry herumfliegen? + Ich will mal unter
http://example.com/not-found was sammeln. ! + security  + TEXT + end it 'parses out URLs' do - expect(a_request(:get, 'https://github.com/qbi/WannaCry')).to have_been_made.at_least_once + expect(a_request(:get, 'http://example.com/not-found')).to have_been_made.once end it 'ignores URLs to hashtags' do diff --git a/spec/services/fetch_oembed_service_spec.rb b/spec/services/fetch_oembed_service_spec.rb index 88f0113ed..777cbae3f 100644 --- a/spec/services/fetch_oembed_service_spec.rb +++ b/spec/services/fetch_oembed_service_spec.rb @@ -6,9 +6,9 @@ describe FetchOEmbedService, type: :service do subject { described_class.new } before do - stub_request(:get, "https://host.test/provider.json").to_return(status: 404) - stub_request(:get, "https://host.test/provider.xml").to_return(status: 404) - stub_request(:get, "https://host.test/empty_provider.json").to_return(status: 200) + stub_request(:get, 'https://host.test/provider.json').to_return(status: 404) + stub_request(:get, 'https://host.test/provider.xml').to_return(status: 404) + stub_request(:get, 'https://host.test/empty_provider.json').to_return(status: 200) end describe 'discover_provider' do @@ -18,7 +18,7 @@ describe FetchOEmbedService, type: :service do stub_request(:get, 'https://www.youtube.com/watch?v=IPSbNdBmWKE').to_return( status: 200, headers: { 'Content-Type': 'text/html' }, - body: request_fixture('oembed_youtube.html'), + body: request_fixture('oembed_youtube.html') ) stub_request(:get, 'https://www.youtube.com/oembed?format=json&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DIPSbNdBmWKE').to_return( status: 200, @@ -39,7 +39,7 @@ describe FetchOEmbedService, type: :service do end end - context 'Both of JSON and XML provider are discoverable' do + context 'when both of JSON and XML provider are discoverable' do before do stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, @@ -62,11 +62,11 @@ describe FetchOEmbedService, type: :service do it 'does not cache OEmbed endpoint' do subject.call('https://host.test/oembed.html', format: :xml) - expect(Rails.cache.exist?('oembed_endpoint:host.test')).to eq false + expect(Rails.cache.exist?('oembed_endpoint:host.test')).to be false end end - context 'JSON provider is discoverable while XML provider is not' do + context 'when JSON provider is discoverable while XML provider is not' do before do stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, @@ -83,11 +83,11 @@ describe FetchOEmbedService, type: :service do it 'does not cache OEmbed endpoint' do subject.call('https://host.test/oembed.html') - expect(Rails.cache.exist?('oembed_endpoint:host.test')).to eq false + expect(Rails.cache.exist?('oembed_endpoint:host.test')).to be false end end - context 'XML provider is discoverable while JSON provider is not' do + context 'when XML provider is discoverable while JSON provider is not' do before do stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, @@ -104,11 +104,11 @@ describe FetchOEmbedService, type: :service do it 'does not cache OEmbed endpoint' do subject.call('https://host.test/oembed.html') - expect(Rails.cache.exist?('oembed_endpoint:host.test')).to eq false + expect(Rails.cache.exist?('oembed_endpoint:host.test')).to be false end end - context 'Invalid XML provider is discoverable while JSON provider is not' do + context 'with Invalid XML provider is discoverable while JSON provider is not' do before do stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, @@ -122,7 +122,7 @@ describe FetchOEmbedService, type: :service do end end - context 'Neither of JSON and XML provider is discoverable' do + context 'with neither of JSON and XML provider is discoverable' do before do stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, @@ -136,7 +136,7 @@ describe FetchOEmbedService, type: :service do end end - context 'Empty JSON provider is discoverable' do + context 'when empty JSON provider is discoverable' do before do stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, @@ -151,7 +151,6 @@ describe FetchOEmbedService, type: :service do expect(subject.format).to eq :json end end - end context 'when endpoint is cached' do diff --git a/spec/services/fetch_remote_status_service_spec.rb b/spec/services/fetch_remote_status_service_spec.rb index fe5f1aed1..798740c9b 100644 --- a/spec/services/fetch_remote_status_service_spec.rb +++ b/spec/services/fetch_remote_status_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe FetchRemoteStatusService, type: :service do @@ -7,15 +9,16 @@ RSpec.describe FetchRemoteStatusService, type: :service do let(:note) do { '@context': 'https://www.w3.org/ns/activitystreams', - id: "https://example.org/@foo/1234", + id: 'https://example.org/@foo/1234', type: 'Note', content: 'Lorem ipsum', attributedTo: ActivityPub::TagManager.instance.uri_for(account), } end - context 'protocol is :activitypub' do - subject { described_class.new.call(note[:id], prefetched_body) } + context 'when protocol is :activitypub' do + subject { described_class.new.call(note[:id], prefetched_body: prefetched_body) } + let(:prefetched_body) { Oj.dump(note) } before do diff --git a/spec/services/fetch_resource_service_spec.rb b/spec/services/fetch_resource_service_spec.rb index 412c41057..78037a06c 100644 --- a/spec/services/fetch_resource_service_spec.rb +++ b/spec/services/fetch_resource_service_spec.rb @@ -1,13 +1,16 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe FetchResourceService, type: :service do describe '#call' do - let(:url) { 'http://example.com' } - subject { described_class.new.call(url) } + let(:url) { 'http://example.com' } + context 'with blank url' do let(:url) { '' } + it { is_expected.to be_nil } end @@ -21,7 +24,7 @@ RSpec.describe FetchResourceService, type: :service do context 'when OpenSSL::SSL::SSLError is raised' do before do - request = double() + request = instance_double(Request) allow(Request).to receive(:new).and_return(request) allow(request).to receive(:add_headers) allow(request).to receive(:on_behalf_of) @@ -33,7 +36,7 @@ RSpec.describe FetchResourceService, type: :service do context 'when HTTP::ConnectionError is raised' do before do - request = double() + request = instance_double(Request) allow(Request).to receive(:new).and_return(request) allow(request).to receive(:add_headers) allow(request).to receive(:on_behalf_of) @@ -62,6 +65,7 @@ RSpec.describe FetchResourceService, type: :service do before do stub_request(:get, url).to_return(status: 200, body: body, headers: headers) + stub_request(:get, 'http://example.com/foo').to_return(status: 200, body: json, headers: { 'Content-Type' => 'application/activity+json' }) end it 'signs request' do @@ -72,7 +76,7 @@ RSpec.describe FetchResourceService, type: :service do context 'when content type is application/atom+xml' do let(:content_type) { 'application/atom+xml' } - it { is_expected.to eq nil } + it { is_expected.to be_nil } end context 'when content type is activity+json' do @@ -89,13 +93,8 @@ RSpec.describe FetchResourceService, type: :service do it { is_expected.to eq ['http://example.com/foo', { prefetched_body: body }] } end - before do - stub_request(:get, url).to_return(status: 200, body: body, headers: headers) - stub_request(:get, 'http://example.com/foo').to_return(status: 200, body: json, headers: { 'Content-Type' => 'application/activity+json' }) - end - context 'when link header is present' do - let(:headers) { { 'Link' => '; rel="alternate"; type="application/activity+json"', } } + let(:headers) { { 'Link' => '; rel="alternate"; type="application/activity+json"' } } it { is_expected.to eq ['http://example.com/foo', { prefetched_body: json }] } end diff --git a/spec/services/follow_service_spec.rb b/spec/services/follow_service_spec.rb index 88346ec54..c2ad0d717 100644 --- a/spec/services/follow_service_spec.rb +++ b/spec/services/follow_service_spec.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe FollowService, type: :service do + subject { described_class.new } + let(:sender) { Fabricate(:account, username: 'alice') } - subject { FollowService.new } - - context 'local account' do + context 'when local account' do describe 'locked account' do let(:bob) { Fabricate(:account, locked: true, username: 'bob') } @@ -136,11 +138,11 @@ RSpec.describe FollowService, type: :service do end end - context 'remote ActivityPub account' do + context 'when remote ActivityPub account' do let(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') } before do - stub_request(:post, "http://example.com/inbox").to_return(:status => 200, :body => "", :headers => {}) + stub_request(:post, 'http://example.com/inbox').to_return(status: 200, body: '', headers: {}) subject.call(sender, bob) end diff --git a/spec/services/import_service_spec.rb b/spec/services/import_service_spec.rb index e2d182920..1904ac8dc 100644 --- a/spec/services/import_service_spec.rb +++ b/spec/services/import_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ImportService, type: :service do @@ -8,16 +10,17 @@ RSpec.describe ImportService, type: :service do let!(:eve) { Fabricate(:account, username: 'eve', domain: 'example.com', locked: false, protocol: :activitypub, inbox_url: 'https://example.com/inbox') } before do - stub_request(:post, "https://example.com/inbox").to_return(status: 200) + stub_request(:post, 'https://example.com/inbox').to_return(status: 200) end - context 'import old-style list of muted users' do - subject { ImportService.new } + context 'when importing old-style list of muted users' do + subject { described_class.new } let(:csv) { attachment_fixture('mute-imports.txt') } describe 'when no accounts are muted' do let(:import) { Import.create(account: account, type: 'muting', data: csv) } + it 'mutes the listed accounts, including notifications' do subject.call(import) expect(account.muting.count).to eq 2 @@ -48,13 +51,14 @@ RSpec.describe ImportService, type: :service do end end - context 'import new-style list of muted users' do - subject { ImportService.new } + context 'when importing new-style list of muted users' do + subject { described_class.new } let(:csv) { attachment_fixture('new-mute-imports.txt') } describe 'when no accounts are muted' do let(:import) { Import.create(account: account, type: 'muting', data: csv) } + it 'mutes the listed accounts, respecting notifications' do subject.call(import) expect(account.muting.count).to eq 2 @@ -88,13 +92,14 @@ RSpec.describe ImportService, type: :service do end end - context 'import old-style list of followed users' do - subject { ImportService.new } + context 'when importing old-style list of followed users' do + subject { described_class.new } let(:csv) { attachment_fixture('mute-imports.txt') } describe 'when no accounts are followed' do let(:import) { Import.create(account: account, type: 'following', data: csv) } + it 'follows the listed accounts, including boosts' do subject.call(import) @@ -129,13 +134,14 @@ RSpec.describe ImportService, type: :service do end end - context 'import new-style list of followed users' do - subject { ImportService.new } + context 'when importing new-style list of followed users' do + subject { described_class.new } let(:csv) { attachment_fixture('new-following-imports.txt') } describe 'when no accounts are followed' do let(:import) { Import.create(account: account, type: 'following', data: csv) } + it 'follows the listed accounts, respecting boosts' do subject.call(import) expect(account.following.count).to eq 1 @@ -175,30 +181,32 @@ RSpec.describe ImportService, type: :service do # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users # # https://github.com/mastodon/mastodon/issues/20571 - context 'utf-8 encoded domains' do - subject { ImportService.new } - - let!(:nare) { Fabricate(:account, username: 'nare', domain: 'թութ.հայ', locked: false, protocol: :activitypub, inbox_url: 'https://թութ.հայ/inbox') } - - # Make sure to not actually go to the remote server - before do - stub_request(:post, "https://թութ.հայ/inbox").to_return(status: 200) - end + context 'with a utf-8 encoded domains' do + subject { described_class.new } + let!(:nare) { Fabricate(:account, username: 'nare', domain: 'թութ.հայ', locked: false, protocol: :activitypub, inbox_url: 'https://թութ.հայ/inbox') } let(:csv) { attachment_fixture('utf8-followers.txt') } let(:import) { Import.create(account: account, type: 'following', data: csv) } + # Make sure to not actually go to the remote server + before do + stub_request(:post, 'https://թութ.հայ/inbox').to_return(status: 200) + end + it 'follows the listed account' do - expect(account.follow_requests.count).to eq 0 + expect(account.follow_requests.count).to eq 0 subject.call(import) expect(account.follow_requests.count).to eq 1 end end - context 'import bookmarks' do - subject { ImportService.new } + context 'when importing bookmarks' do + subject { described_class.new } let(:csv) { attachment_fixture('bookmark-imports.txt') } + let(:local_account) { Fabricate(:account, username: 'foo', domain: '') } + let!(:remote_status) { Fabricate(:status, uri: 'https://example.com/statuses/1312') } + let!(:direct_status) { Fabricate(:status, uri: 'https://example.com/statuses/direct', visibility: :direct) } around(:each) do |example| local_before = Rails.configuration.x.local_domain @@ -210,12 +218,8 @@ RSpec.describe ImportService, type: :service do Rails.configuration.x.local_domain = local_before end - let(:local_account) { Fabricate(:account, username: 'foo', domain: '') } - let!(:remote_status) { Fabricate(:status, uri: 'https://example.com/statuses/1312') } - let!(:direct_status) { Fabricate(:status, uri: 'https://example.com/statuses/direct', visibility: :direct) } - before do - service = double + service = instance_double(ActivityPub::FetchRemoteStatusService) allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service) allow(service).to receive(:call).with('https://unknown-remote.com/users/bar/statuses/1') do Fabricate(:status, uri: 'https://unknown-remote.com/users/bar/statuses/1') @@ -224,12 +228,13 @@ RSpec.describe ImportService, type: :service do describe 'when no bookmarks are set' do let(:import) { Import.create(account: account, type: 'bookmarks', data: csv) } + it 'adds the toots the user has access to to bookmarks' do local_status = Fabricate(:status, account: local_account, uri: 'https://local.com/users/foo/statuses/42', id: 42, local: true) subject.call(import) expect(account.bookmarks.map(&:status).map(&:id)).to include(local_status.id) expect(account.bookmarks.map(&:status).map(&:id)).to include(remote_status.id) - expect(account.bookmarks.map(&:status).map(&:id)).not_to include(direct_status.id) + expect(account.bookmarks.map(&:status).map(&:id)).to_not include(direct_status.id) expect(account.bookmarks.count).to eq 3 end end diff --git a/spec/services/mute_service_spec.rb b/spec/services/mute_service_spec.rb index 57d8c41de..50f74ff27 100644 --- a/spec/services/mute_service_spec.rb +++ b/spec/services/mute_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe MuteService, type: :service do diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb index 294c31b04..c2664e79c 100644 --- a/spec/services/notify_service_spec.rb +++ b/spec/services/notify_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe NotifyService, type: :service do @@ -47,22 +49,23 @@ RSpec.describe NotifyService, type: :service do expect { subject }.to_not change(Notification, :count) end - context 'for direct messages' do + context 'with direct messages' do let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct)) } let(:type) { :mention } before do - user.settings.interactions = user.settings.interactions.merge('must_be_following_dm' => enabled) + user.settings.update('interactions.must_be_following_dm': enabled) + user.save end - context 'if recipient is supposed to be following sender' do + context 'when recipient is supposed to be following sender' do let(:enabled) { true } it 'does not notify' do expect { subject }.to_not change(Notification, :count) end - context 'if the message chain is initiated by recipient, but is not direct message' do + context 'when the message chain is initiated by recipient, but is not direct message' do let(:reply_to) { Fabricate(:status, account: recipient) } let!(:mention) { Fabricate(:mention, account: sender, status: reply_to) } let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) } @@ -72,18 +75,18 @@ RSpec.describe NotifyService, type: :service do end end - context 'if the message chain is initiated by recipient, but without a mention to the sender, even if the sender sends multiple messages in a row' do - let(:reply_to) { Fabricate(:status, account: recipient) } - let!(:mention) { Fabricate(:mention, account: sender, status: reply_to) } - let(:dummy_reply) { Fabricate(:status, account: sender, visibility: :direct, thread: reply_to) } - let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: dummy_reply)) } + context 'when the message chain is initiated by recipient, but without a mention to the sender, even if the sender sends multiple messages in a row' do + let(:public_status) { Fabricate(:status, account: recipient) } + let(:intermediate_reply) { Fabricate(:status, account: sender, thread: public_status, visibility: :direct) } + let!(:intermediate_mention) { Fabricate(:mention, account: sender, status: intermediate_reply) } + let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: intermediate_reply)) } it 'does not notify' do expect { subject }.to_not change(Notification, :count) end end - context 'if the message chain is initiated by the recipient with a mention to the sender' do + context 'when the message chain is initiated by the recipient with a mention to the sender' do let(:reply_to) { Fabricate(:status, account: recipient, visibility: :direct) } let!(:mention) { Fabricate(:mention, account: sender, status: reply_to) } let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) } @@ -94,7 +97,7 @@ RSpec.describe NotifyService, type: :service do end end - context 'if recipient is NOT supposed to be following sender' do + context 'when recipient is NOT supposed to be following sender' do let(:enabled) { false } it 'does notify' do @@ -124,7 +127,7 @@ RSpec.describe NotifyService, type: :service do end end - context do + context 'with muted and blocked users' do let(:asshole) { Fabricate(:account, username: 'asshole') } let(:reply_to) { Fabricate(:status, account: asshole) } let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, thread: reply_to)) } @@ -141,7 +144,7 @@ RSpec.describe NotifyService, type: :service do end end - context do + context 'with sender as recipient' do let(:sender) { recipient } it 'does not notify when recipient is the sender' do @@ -153,8 +156,8 @@ RSpec.describe NotifyService, type: :service do before do ActionMailer::Base.deliveries.clear - notification_emails = user.settings.notification_emails - user.settings.notification_emails = notification_emails.merge('follow' => enabled) + user.settings.update('notification_emails.follow': enabled) + user.save end context 'when email notification is enabled' do diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index d21270c79..826af5479 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe PostStatusService, type: :service do - subject { PostStatusService.new } + subject { described_class.new } it 'creates a new status' do account = Fabricate(:account) - text = "test status update" + text = 'test status update' status = subject.call(account, text: text) @@ -16,7 +18,7 @@ RSpec.describe PostStatusService, type: :service do it 'creates a new response status' do in_reply_to_status = Fabricate(:status) account = Fabricate(:account) - text = "test status update" + text = 'test status update' status = subject.call(account, text: text, thread: in_reply_to_status) @@ -46,11 +48,28 @@ RSpec.describe PostStatusService, type: :service do expect(status.params['text']).to eq 'Hi future!' expect(status.params['media_ids']).to eq [media.id] expect(media.reload.status).to be_nil - expect(Status.where(text: 'Hi future!').exists?).to be_falsey + expect(Status.where(text: 'Hi future!')).to_not exist end it 'does not change statuses count' do - expect { subject.call(account, text: 'Hi future!', scheduled_at: future, thread: previous_status) }.not_to change { [account.statuses_count, previous_status.replies_count] } + expect { subject.call(account, text: 'Hi future!', scheduled_at: future, thread: previous_status) }.to_not(change { [account.statuses_count, previous_status.replies_count] }) + end + + it 'returns existing status when used twice with idempotency key' do + account = Fabricate(:account) + status1 = subject.call(account, text: 'test', idempotency: 'meepmeep', scheduled_at: future) + status2 = subject.call(account, text: 'test', idempotency: 'meepmeep', scheduled_at: future) + expect(status2.id).to eq status1.id + end + + context 'when scheduled_at is less than min offset' do + let(:invalid_scheduled_time) { 4.minutes.from_now } + + it 'raises invalid record error' do + expect do + subject.call(account, text: 'Hi future!', scheduled_at: invalid_scheduled_time) + end.to raise_error(ActiveRecord::RecordInvalid) + end end end @@ -58,7 +77,7 @@ RSpec.describe PostStatusService, type: :service do boosted_status = Fabricate(:status) in_reply_to_status = Fabricate(:status, reblog: boosted_status) account = Fabricate(:account) - text = "test status update" + text = 'test status update' status = subject.call(account, text: text, thread: in_reply_to_status) @@ -75,7 +94,7 @@ RSpec.describe PostStatusService, type: :service do end it 'creates a status with spoiler text' do - spoiler_text = "spoiler text" + spoiler_text = 'spoiler text' status = create_status_with_options(spoiler_text: spoiler_text) @@ -101,14 +120,14 @@ RSpec.describe PostStatusService, type: :service do status = create_status_with_options(visibility: :private) expect(status).to be_persisted - expect(status.visibility).to eq "private" + expect(status.visibility).to eq 'private' end it 'creates a status with limited visibility for silenced users' do status = subject.call(Fabricate(:account, silenced: true), text: 'test', visibility: :public) expect(status).to be_persisted - expect(status.visibility).to eq "unlisted" + expect(status.visibility).to eq 'unlisted' end it 'creates a status for the given application' do @@ -130,24 +149,43 @@ RSpec.describe PostStatusService, type: :service do end it 'processes mentions' do - mention_service = double(:process_mentions_service) + mention_service = instance_double(ProcessMentionsService) allow(mention_service).to receive(:call) allow(ProcessMentionsService).to receive(:new).and_return(mention_service) account = Fabricate(:account) - status = subject.call(account, text: "test status update") + status = subject.call(account, text: 'test status update') expect(ProcessMentionsService).to have_received(:new) - expect(mention_service).to have_received(:call).with(status) + expect(mention_service).to have_received(:call).with(status, save_records: false) + end + + it 'safeguards mentions' do + account = Fabricate(:account) + mentioned_account = Fabricate(:account, username: 'alice') + unexpected_mentioned_account = Fabricate(:account, username: 'bob') + + expect do + subject.call(account, text: '@alice hm, @bob is really annoying lately', allowed_mentions: [mentioned_account.id]) + end.to raise_error(an_instance_of(PostStatusService::UnexpectedMentionsError).and(having_attributes(accounts: [unexpected_mentioned_account]))) + end + + it 'processes duplicate mentions correctly' do + account = Fabricate(:account) + mentioned_account = Fabricate(:account, username: 'alice') + + expect do + subject.call(account, text: '@alice @alice @alice hey @alice') + end.to_not raise_error end it 'processes hashtags' do - hashtags_service = double(:process_hashtags_service) + hashtags_service = instance_double(ProcessHashtagsService) allow(hashtags_service).to receive(:call) allow(ProcessHashtagsService).to receive(:new).and_return(hashtags_service) account = Fabricate(:account) - status = subject.call(account, text: "test status update") + status = subject.call(account, text: 'test status update') expect(ProcessHashtagsService).to have_received(:new) expect(hashtags_service).to have_received(:call).with(status) @@ -159,7 +197,7 @@ RSpec.describe PostStatusService, type: :service do account = Fabricate(:account) - status = subject.call(account, text: "test status update") + status = subject.call(account, text: 'test status update') expect(DistributionWorker).to have_received(:perform_async).with(status.id) expect(ActivityPub::DistributionWorker).to have_received(:perform_async).with(status.id) @@ -169,7 +207,7 @@ RSpec.describe PostStatusService, type: :service do allow(LinkCrawlWorker).to receive(:perform_async) account = Fabricate(:account) - status = subject.call(account, text: "test status update") + status = subject.call(account, text: 'test status update') expect(LinkCrawlWorker).to have_received(:perform_async).with(status.id) end @@ -180,8 +218,8 @@ RSpec.describe PostStatusService, type: :service do status = subject.call( account, - text: "test status update", - media_ids: [media.id], + text: 'test status update', + media_ids: [media.id] ) expect(media.reload.status).to eq status @@ -193,11 +231,11 @@ RSpec.describe PostStatusService, type: :service do status = subject.call( account, - text: "test status update", - media_ids: [media.id], + text: 'test status update', + media_ids: [media.id] ) - expect(media.reload.status).to eq nil + expect(media.reload.status).to be_nil end it 'does not allow attaching more than 4 files' do @@ -206,18 +244,18 @@ RSpec.describe PostStatusService, type: :service do expect do subject.call( account, - text: "test status update", + text: 'test status update', media_ids: [ Fabricate(:media_attachment, account: account), Fabricate(:media_attachment, account: account), Fabricate(:media_attachment, account: account), Fabricate(:media_attachment, account: account), Fabricate(:media_attachment, account: account), - ].map(&:id), + ].map(&:id) ) end.to raise_error( Mastodon::ValidationError, - I18n.t('media_attachments.validations.too_many'), + I18n.t('media_attachments.validations.too_many') ) end @@ -231,15 +269,15 @@ RSpec.describe PostStatusService, type: :service do expect do subject.call( account, - text: "test status update", + text: 'test status update', media_ids: [ video, image, - ].map(&:id), + ].map(&:id) ) end.to raise_error( Mastodon::ValidationError, - I18n.t('media_attachments.validations.images_and_video'), + I18n.t('media_attachments.validations.images_and_video') ) end diff --git a/spec/services/precompute_feed_service_spec.rb b/spec/services/precompute_feed_service_spec.rb index 86b93b5d2..54e0d94ee 100644 --- a/spec/services/precompute_feed_service_spec.rb +++ b/spec/services/precompute_feed_service_spec.rb @@ -3,10 +3,11 @@ require 'rails_helper' RSpec.describe PrecomputeFeedService, type: :service do - subject { PrecomputeFeedService.new } + subject { described_class.new } describe 'call' do let(:account) { Fabricate(:account) } + it 'fills a user timeline with statuses' do account = Fabricate(:account) status = Fabricate(:status, account: account) @@ -18,7 +19,7 @@ RSpec.describe PrecomputeFeedService, type: :service do it 'does not raise an error even if it could not find any status' do account = Fabricate(:account) - subject.call(account) + expect { subject.call(account) }.to_not raise_error end it 'filters statuses' do @@ -30,7 +31,7 @@ RSpec.describe PrecomputeFeedService, type: :service do subject.call(account) - expect(redis.zscore(FeedManager.instance.key(:home, account.id), reblog.id)).to eq nil + expect(redis.zscore(FeedManager.instance.key(:home, account.id), reblog.id)).to be_nil end end end diff --git a/spec/services/process_mentions_service_spec.rb b/spec/services/process_mentions_service_spec.rb index 5b9d17a4c..0db73c41f 100644 --- a/spec/services/process_mentions_service_spec.rb +++ b/spec/services/process_mentions_service_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ProcessMentionsService, type: :service do - let(:account) { Fabricate(:account, username: 'alice') } + subject { described_class.new } - subject { ProcessMentionsService.new } + let(:account) { Fabricate(:account, username: 'alice') } context 'when mentions contain blocked accounts' do let(:non_blocked_account) { Fabricate(:account) } @@ -31,11 +33,11 @@ RSpec.describe ProcessMentionsService, type: :service do end end - context 'resolving a mention to a remote account' do + context 'with resolving a mention to a remote account' do let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}", visibility: :public) } - context 'ActivityPub' do - context do + context 'with ActivityPub' do + context 'with a valid remote user' do let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } before do @@ -47,9 +49,22 @@ RSpec.describe ProcessMentionsService, type: :service do end end + context 'when mentioning a user several times when not saving records' do + let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } + let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct} @#{remote_user.acct} @#{remote_user.acct}", visibility: :public) } + + before do + subject.call(status, save_records: false) + end + + it 'creates exactly one mention' do + expect(status.mentions.size).to eq 1 + end + end + context 'with an IDN domain' do let!(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') } - let!(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") } + let!(:status) { Fabricate(:status, account: account, text: 'Hello @sneak@hæresiar.ch') } before do subject.call(status) @@ -62,7 +77,7 @@ RSpec.describe ProcessMentionsService, type: :service do context 'with an IDN TLD' do let!(:remote_user) { Fabricate(:account, username: 'foo', protocol: :activitypub, domain: 'xn--y9a3aq.xn--y9a3aq', inbox_url: 'http://example.com/inbox') } - let!(:status) { Fabricate(:status, account: account, text: "Hello @foo@հայ.հայ") } + let!(:status) { Fabricate(:status, account: account, text: 'Hello @foo@հայ.հայ') } before do subject.call(status) @@ -74,12 +89,12 @@ RSpec.describe ProcessMentionsService, type: :service do end end - context 'Temporarily-unreachable ActivityPub user' do + context 'with a Temporarily-unreachable ActivityPub user' do let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox', last_webfingered_at: nil) } before do - stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404) - stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com").to_return(status: 500) + stub_request(:get, 'https://example.com/.well-known/host-meta').to_return(status: 404) + stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com').to_return(status: 500) subject.call(status) end diff --git a/spec/services/purge_domain_service_spec.rb b/spec/services/purge_domain_service_spec.rb index 59285f126..e96618310 100644 --- a/spec/services/purge_domain_service_spec.rb +++ b/spec/services/purge_domain_service_spec.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe PurgeDomainService, type: :service do - let!(:old_account) { Fabricate(:account, domain: 'obsolete.org') } - let!(:old_status1) { Fabricate(:status, account: old_account) } - let!(:old_status2) { Fabricate(:status, account: old_account) } - let!(:old_attachment) { Fabricate(:media_attachment, account: old_account, status: old_status2, file: attachment_fixture('attachment.jpg')) } + subject { described_class.new } - subject { PurgeDomainService.new } + let!(:old_account) { Fabricate(:account, domain: 'obsolete.org') } + let!(:old_status_plain) { Fabricate(:status, account: old_account) } + let!(:old_status_with_attachment) { Fabricate(:status, account: old_account) } + let!(:old_attachment) { Fabricate(:media_attachment, account: old_account, status: old_status_with_attachment, file: attachment_fixture('attachment.jpg')) } describe 'for a suspension' do before do @@ -15,8 +17,8 @@ RSpec.describe PurgeDomainService, type: :service do it 'removes the remote accounts\'s statuses and media attachments' do expect { old_account.reload }.to raise_exception ActiveRecord::RecordNotFound - expect { old_status1.reload }.to raise_exception ActiveRecord::RecordNotFound - expect { old_status2.reload }.to raise_exception ActiveRecord::RecordNotFound + expect { old_status_plain.reload }.to raise_exception ActiveRecord::RecordNotFound + expect { old_status_with_attachment.reload }.to raise_exception ActiveRecord::RecordNotFound expect { old_attachment.reload }.to raise_exception ActiveRecord::RecordNotFound end diff --git a/spec/services/reblog_service_spec.rb b/spec/services/reblog_service_spec.rb index c0ae5eedc..357b315af 100644 --- a/spec/services/reblog_service_spec.rb +++ b/spec/services/reblog_service_spec.rb @@ -1,15 +1,17 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ReblogService, type: :service do let(:alice) { Fabricate(:account, username: 'alice') } - context 'creates a reblog with appropriate visibility' do + context 'when creates a reblog with appropriate visibility' do + subject { described_class.new } + let(:visibility) { :public } let(:reblog_visibility) { :public } let(:status) { Fabricate(:status, account: alice, visibility: visibility) } - subject { ReblogService.new } - before do subject.call(alice, status, visibility: reblog_visibility) end @@ -33,10 +35,25 @@ RSpec.describe ReblogService, type: :service do end context 'when the reblogged status is discarded in the meantime' do - let(:status) { Fabricate(:status, account: alice, visibility: :public) } + let(:status) { Fabricate(:status, account: alice, visibility: :public, text: 'discard-status-text') } + # Add a callback to discard the status being reblogged after the + # validations pass but before the database commit is executed. before do - status.discard + Status.class_eval do + before_save :discard_status + def discard_status + Status + .where(id: reblog_of_id) + .where(text: 'discard-status-text') + .update_all(deleted_at: Time.now.utc) # rubocop:disable Rails/SkipsModelValidations + end + end + end + + # Remove race condition simulating `discard_status` callback. + after do + Status._save_callbacks.delete(:discard_status) end it 'raises an exception' do @@ -44,12 +61,12 @@ RSpec.describe ReblogService, type: :service do end end - context 'ActivityPub' do + context 'with ActivityPub' do + subject { described_class.new } + let(:bob) { Fabricate(:account, username: 'bob', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } let(:status) { Fabricate(:status, account: bob) } - subject { ReblogService.new } - before do stub_request(:post, bob.inbox_url) allow(ActivityPub::DistributionWorker).to receive(:perform_async) @@ -69,9 +86,5 @@ RSpec.describe ReblogService, type: :service do it 'distributes to followers' do expect(ActivityPub::DistributionWorker).to have_received(:perform_async) end - - it 'sends an announce activity to the author' do - expect(a_request(:post, bob.inbox_url)).to have_been_made.once - end end end diff --git a/spec/services/reject_follow_service_spec.rb b/spec/services/reject_follow_service_spec.rb index e14bfa78d..d28104b2c 100644 --- a/spec/services/reject_follow_service_spec.rb +++ b/spec/services/reject_follow_service_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe RejectFollowService, type: :service do - let(:sender) { Fabricate(:account, username: 'alice') } + subject { described_class.new } - subject { RejectFollowService.new } + let(:sender) { Fabricate(:account, username: 'alice') } describe 'local' do let(:bob) { Fabricate(:account) } diff --git a/spec/services/remove_from_follwers_service_spec.rb b/spec/services/remove_from_followers_service_spec.rb similarity index 94% rename from spec/services/remove_from_follwers_service_spec.rb rename to spec/services/remove_from_followers_service_spec.rb index a83f6f49a..1b29cdcbe 100644 --- a/spec/services/remove_from_follwers_service_spec.rb +++ b/spec/services/remove_from_followers_service_spec.rb @@ -1,13 +1,15 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe RemoveFromFollowersService, type: :service do - let(:bob) { Fabricate(:account, username: 'bob') } + subject { described_class.new } - subject { RemoveFromFollowersService.new } + let(:bob) { Fabricate(:account, username: 'bob') } describe 'local' do let(:sender) { Fabricate(:account, username: 'alice') } - + before do Follow.create(account: sender, target_account: bob) subject.call(bob, sender) diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb index 482068d58..7e81f3f6a 100644 --- a/spec/services/remove_status_service_spec.rb +++ b/spec/services/remove_status_service_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe RemoveStatusService, type: :service do - subject { RemoveStatusService.new } + subject { described_class.new } let!(:alice) { Fabricate(:account) } let!(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com') } @@ -26,40 +28,40 @@ RSpec.describe RemoveStatusService, type: :service do it 'removes status from author\'s home feed' do subject.call(@status) - expect(HomeFeed.new(alice).get(10)).to_not include(@status.id) + expect(HomeFeed.new(alice).get(10).pluck(:id)).to_not include(@status.id) end it 'removes status from local follower\'s home feed' do subject.call(@status) - expect(HomeFeed.new(jeff).get(10)).to_not include(@status.id) + expect(HomeFeed.new(jeff).get(10).pluck(:id)).to_not include(@status.id) end it 'sends Delete activity to followers' do subject.call(@status) expect(a_request(:post, 'http://example.com/inbox').with( - body: hash_including({ - 'type' => 'Delete', - 'object' => { - 'type' => 'Tombstone', - 'id' => ActivityPub::TagManager.instance.uri_for(@status), - 'atomUri' => OStatus::TagManager.instance.uri_for(@status), - }, - }) - )).to have_been_made.once + body: hash_including({ + 'type' => 'Delete', + 'object' => { + 'type' => 'Tombstone', + 'id' => ActivityPub::TagManager.instance.uri_for(@status), + 'atomUri' => OStatus::TagManager.instance.uri_for(@status), + }, + }) + )).to have_been_made.once end it 'sends Delete activity to rebloggers' do subject.call(@status) expect(a_request(:post, 'http://example2.com/inbox').with( - body: hash_including({ - 'type' => 'Delete', - 'object' => { - 'type' => 'Tombstone', - 'id' => ActivityPub::TagManager.instance.uri_for(@status), - 'atomUri' => OStatus::TagManager.instance.uri_for(@status), - }, - }) - )).to have_been_made.once + body: hash_including({ + 'type' => 'Delete', + 'object' => { + 'type' => 'Tombstone', + 'id' => ActivityPub::TagManager.instance.uri_for(@status), + 'atomUri' => OStatus::TagManager.instance.uri_for(@status), + }, + }) + )).to have_been_made.once end it 'remove status from notifications' do @@ -78,14 +80,14 @@ RSpec.describe RemoveStatusService, type: :service do it 'sends Undo activity to followers' do subject.call(@status) expect(a_request(:post, 'http://example.com/inbox').with( - body: hash_including({ - 'type' => 'Undo', - 'object' => hash_including({ - 'type' => 'Announce', - 'object' => ActivityPub::TagManager.instance.uri_for(@original_status), - }), - }) - )).to have_been_made.once + body: hash_including({ + 'type' => 'Undo', + 'object' => hash_including({ + 'type' => 'Announce', + 'object' => ActivityPub::TagManager.instance.uri_for(@original_status), + }), + }) + )).to have_been_made.once end end @@ -98,14 +100,32 @@ RSpec.describe RemoveStatusService, type: :service do it 'sends Undo activity to followers' do subject.call(@status) expect(a_request(:post, 'http://example.com/inbox').with( - body: hash_including({ - 'type' => 'Undo', - 'object' => hash_including({ - 'type' => 'Announce', - 'object' => ActivityPub::TagManager.instance.uri_for(@original_status), - }), - }) - )).to have_been_made.once + body: hash_including({ + 'type' => 'Undo', + 'object' => hash_including({ + 'type' => 'Announce', + 'object' => ActivityPub::TagManager.instance.uri_for(@original_status), + }), + }) + )).to have_been_made.once + end + end + + context 'when removed status is a reblog of a non-follower' do + let!(:original_status) { Fabricate(:status, account: bill, text: 'Hello ThisIsASecret', visibility: :public) } + let!(:status) { ReblogService.new.call(alice, original_status) } + + it 'sends Undo activity to followers' do + subject.call(status) + expect(a_request(:post, bill.inbox_url).with( + body: hash_including({ + 'type' => 'Undo', + 'object' => hash_including({ + 'type' => 'Announce', + 'object' => ActivityPub::TagManager.instance.uri_for(original_status), + }), + }) + )).to have_been_made.once end end end diff --git a/spec/services/report_service_spec.rb b/spec/services/report_service_spec.rb index 1737a05ae..d3bcd5d31 100644 --- a/spec/services/report_service_spec.rb +++ b/spec/services/report_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ReportService, type: :service do @@ -13,37 +15,94 @@ RSpec.describe ReportService, type: :service do end end - context 'for a remote account' do + context 'with a remote account' do let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') } + let(:forward) { false } before do stub_request(:post, 'http://example.com/inbox').to_return(status: 200) end - it 'sends ActivityPub payload when forward is true' do - subject.call(source_account, remote_account, forward: true) - expect(a_request(:post, 'http://example.com/inbox')).to have_been_made + context 'when forward is true' do + let(:forward) { true } + + it 'sends ActivityPub payload when forward is true' do + subject.call(source_account, remote_account, forward: forward) + expect(a_request(:post, 'http://example.com/inbox')).to have_been_made + end + + it 'has an uri' do + report = subject.call(source_account, remote_account, forward: forward) + expect(report.uri).to_not be_nil + end + + context 'when reporting a reply on a different remote server' do + let(:remote_thread_account) { Fabricate(:account, domain: 'foo.com', protocol: :activitypub, inbox_url: 'http://foo.com/inbox') } + let(:reported_status) { Fabricate(:status, account: remote_account, thread: Fabricate(:status, account: remote_thread_account)) } + + before do + stub_request(:post, 'http://foo.com/inbox').to_return(status: 200) + end + + context 'when forward_to_domains includes both the replied-to domain and the origin domain' do + it 'sends ActivityPub payload to both the author of the replied-to post and the reported user' do + subject.call(source_account, remote_account, status_ids: [reported_status.id], forward: forward, forward_to_domains: [remote_account.domain, remote_thread_account.domain]) + expect(a_request(:post, 'http://foo.com/inbox')).to have_been_made + expect(a_request(:post, 'http://example.com/inbox')).to have_been_made + end + end + + context 'when forward_to_domains includes only the replied-to domain' do + it 'sends ActivityPub payload only to the author of the replied-to post' do + subject.call(source_account, remote_account, status_ids: [reported_status.id], forward: forward, forward_to_domains: [remote_thread_account.domain]) + expect(a_request(:post, 'http://foo.com/inbox')).to have_been_made + expect(a_request(:post, 'http://example.com/inbox')).to_not have_been_made + end + end + + context 'when forward_to_domains does not include the replied-to domain' do + it 'does not send ActivityPub payload to the author of the replied-to post' do + subject.call(source_account, remote_account, status_ids: [reported_status.id], forward: forward) + expect(a_request(:post, 'http://foo.com/inbox')).to_not have_been_made + end + end + end + + context 'when reporting a reply on the same remote server as the person being replied-to' do + let(:remote_thread_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') } + let(:reported_status) { Fabricate(:status, account: remote_account, thread: Fabricate(:status, account: remote_thread_account)) } + + context 'when forward_to_domains includes both the replied-to domain and the origin domain' do + it 'sends ActivityPub payload only once' do + subject.call(source_account, remote_account, status_ids: [reported_status.id], forward: forward, forward_to_domains: [remote_account.domain]) + expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once + end + end + + context 'when forward_to_domains does not include the replied-to domain' do + it 'sends ActivityPub payload only once' do + subject.call(source_account, remote_account, status_ids: [reported_status.id], forward: forward) + expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once + end + end + end end - it 'does not send anything when forward is false' do - subject.call(source_account, remote_account, forward: false) - expect(a_request(:post, 'http://example.com/inbox')).to_not have_been_made - end - - it 'has an uri' do - report = subject.call(source_account, remote_account, forward: true) - expect(report.uri).to_not be_nil + context 'when forward is false' do + it 'does not send anything' do + subject.call(source_account, remote_account, forward: forward) + expect(a_request(:post, 'http://example.com/inbox')).to_not have_been_made + end end end context 'when the reported status is a DM' do - let(:target_account) { Fabricate(:account) } - let(:status) { Fabricate(:status, account: target_account, visibility: :direct) } - subject do -> { described_class.new.call(source_account, target_account, status_ids: [status.id]) } end + let(:status) { Fabricate(:status, account: target_account, visibility: :direct) } + context 'when it is addressed to the reporter' do before do status.mentions.create(account: source_account) @@ -93,16 +152,16 @@ RSpec.describe ReportService, type: :service do end context 'when other reports already exist for the same target' do - let!(:target_account) { Fabricate(:account) } - let!(:other_report) { Fabricate(:report, target_account: target_account) } - subject do -> { described_class.new.call(source_account, target_account) } end + let!(:other_report) { Fabricate(:report, target_account: target_account) } + before do ActionMailer::Base.deliveries.clear - source_account.user.settings.notification_emails['report'] = true + source_account.user.settings['notification_emails.report'] = true + source_account.user.save end it 'does not send an e-mail' do diff --git a/spec/services/resolve_account_service_spec.rb b/spec/services/resolve_account_service_spec.rb index 654606bea..f446d0ca6 100644 --- a/spec/services/resolve_account_service_spec.rb +++ b/spec/services/resolve_account_service_spec.rb @@ -1,19 +1,21 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe ResolveAccountService, type: :service do subject { described_class.new } before do - stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404) - stub_request(:get, "https://quitter.no/avatar/7477-300-20160211190340.png").to_return(request_fixture('avatar.txt')) - stub_request(:get, "https://ap.example.com/.well-known/webfinger?resource=acct:foo@ap.example.com").to_return(request_fixture('activitypub-webfinger.txt')) - stub_request(:get, "https://ap.example.com/users/foo").to_return(request_fixture('activitypub-actor.txt')) - stub_request(:get, "https://ap.example.com/users/foo.atom").to_return(request_fixture('activitypub-feed.txt')) - stub_request(:get, %r{https://ap.example.com/users/foo/\w+}).to_return(status: 404) + stub_request(:get, 'https://example.com/.well-known/host-meta').to_return(status: 404) + stub_request(:get, 'https://quitter.no/avatar/7477-300-20160211190340.png').to_return(request_fixture('avatar.txt')) + stub_request(:get, 'https://ap.example.com/.well-known/webfinger?resource=acct:foo@ap.example.com').to_return(request_fixture('activitypub-webfinger.txt')) + stub_request(:get, 'https://ap.example.com/users/foo').to_return(request_fixture('activitypub-actor.txt')) + stub_request(:get, 'https://ap.example.com/users/foo.atom').to_return(request_fixture('activitypub-feed.txt')) + stub_request(:get, %r{https://ap\.example\.com/users/foo/\w+}).to_return(status: 404) stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:hoge@example.com').to_return(status: 410) end - context 'using skip_webfinger' do + context 'when using skip_webfinger' do context 'when account is known' do let!(:remote_account) { Fabricate(:account, username: 'foo', domain: 'ap.example.com', protocol: 'activitypub') } @@ -56,8 +58,8 @@ RSpec.describe ResolveAccountService, type: :service do context 'when there is an LRDD endpoint but no resolvable account' do before do - stub_request(:get, "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt')) - stub_request(:get, "https://quitter.no/.well-known/webfinger?resource=acct:catsrgr8@quitter.no").to_return(status: 404) + stub_request(:get, 'https://quitter.no/.well-known/host-meta').to_return(request_fixture('.host-meta.txt')) + stub_request(:get, 'https://quitter.no/.well-known/webfinger?resource=acct:catsrgr8@quitter.no').to_return(status: 404) end it 'returns nil' do @@ -67,7 +69,7 @@ RSpec.describe ResolveAccountService, type: :service do context 'when there is no LRDD endpoint nor resolvable account' do before do - stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:catsrgr8@example.com").to_return(status: 404) + stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:catsrgr8@example.com').to_return(status: 404) end it 'returns nil' do @@ -76,7 +78,7 @@ RSpec.describe ResolveAccountService, type: :service do end context 'when webfinger returns http gone' do - context 'for a previously known account' do + context 'with a previously known account' do before do Fabricate(:account, username: 'hoge', domain: 'example.com', last_webfingered_at: nil) allow(AccountDeletionWorker).to receive(:perform_async) @@ -92,7 +94,7 @@ RSpec.describe ResolveAccountService, type: :service do end end - context 'for a previously unknown account' do + context 'with a previously unknown account' do it 'returns nil' do expect(subject.call('hoge@example.com')).to be_nil end @@ -108,7 +110,7 @@ RSpec.describe ResolveAccountService, type: :service do it 'returns new remote account' do account = subject.call('Foo@redirected.example.com') - expect(account.activitypub?).to eq true + expect(account.activitypub?).to be true expect(account.acct).to eq 'foo@ap.example.com' expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox' end @@ -123,7 +125,7 @@ RSpec.describe ResolveAccountService, type: :service do it 'returns new remote account' do account = subject.call('Foo@redirected.example.com') - expect(account.activitypub?).to eq true + expect(account.activitypub?).to be true expect(account.acct).to eq 'foo@ap.example.com' expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox' end @@ -146,20 +148,20 @@ RSpec.describe ResolveAccountService, type: :service do it 'returns new remote account' do account = subject.call('foo@ap.example.com') - expect(account.activitypub?).to eq true + expect(account.activitypub?).to be true expect(account.domain).to eq 'ap.example.com' expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox' end context 'with multiple types' do before do - stub_request(:get, "https://ap.example.com/users/foo").to_return(request_fixture('activitypub-actor-individual.txt')) + stub_request(:get, 'https://ap.example.com/users/foo').to_return(request_fixture('activitypub-actor-individual.txt')) end it 'returns new remote account' do account = subject.call('foo@ap.example.com') - expect(account.activitypub?).to eq true + expect(account.activitypub?).to be true expect(account.domain).to eq 'ap.example.com' expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox' expect(account.actor_type).to eq 'Person' @@ -174,7 +176,7 @@ RSpec.describe ResolveAccountService, type: :service do it 'returns new remote account' do account = subject.call('foo@ap.example.com') - expect(account.activitypub?).to eq true + expect(account.activitypub?).to be true expect(account.domain).to eq 'ap.example.com' expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox' expect(account.uri).to eq 'https://ap.example.com/users/foo' @@ -190,12 +192,12 @@ RSpec.describe ResolveAccountService, type: :service do context 'with an already-known acct: URI changing ActivityPub id' do let!(:old_account) { Fabricate(:account, username: 'foo', domain: 'ap.example.com', uri: 'https://old.example.com/users/foo', last_webfingered_at: nil) } - let!(:status) { Fabricate(:status, account: old_account, text: 'foo') } + let!(:status) { Fabricate(:status, account: old_account, text: 'foo') } it 'returns new remote account' do account = subject.call('foo@ap.example.com') - expect(account.activitypub?).to eq true + expect(account.activitypub?).to be true expect(account.domain).to eq 'ap.example.com' expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox' expect(account.uri).to eq 'https://ap.example.com/users/foo' @@ -207,11 +209,6 @@ RSpec.describe ResolveAccountService, type: :service do fail_occurred = false return_values = Concurrent::Array.new - # Preload classes that throw circular dependency errors in threads - Account - TagManager - DomainBlock - threads = Array.new(5) do Thread.new do true while wait_for_start diff --git a/spec/services/resolve_url_service_spec.rb b/spec/services/resolve_url_service_spec.rb index 85a672524..38d35a3a1 100644 --- a/spec/services/resolve_url_service_spec.rb +++ b/spec/services/resolve_url_service_spec.rb @@ -8,8 +8,8 @@ describe ResolveURLService, type: :service do describe '#call' do it 'returns nil when there is no resource url' do url = 'http://example.com/missing-resource' - known_account = Fabricate(:account, uri: url) - service = double + known_account = Fabricate(:account, uri: url, domain: 'example.com') + service = instance_double(FetchResourceService) allow(FetchResourceService).to receive(:new).and_return service allow(service).to receive(:response_code).and_return(404) @@ -20,8 +20,8 @@ describe ResolveURLService, type: :service do it 'returns known account on temporary error' do url = 'http://example.com/missing-resource' - known_account = Fabricate(:account, uri: url) - service = double + known_account = Fabricate(:account, uri: url, domain: 'example.com') + service = instance_double(FetchResourceService) allow(FetchResourceService).to receive(:new).and_return service allow(service).to receive(:response_code).and_return(500) @@ -30,7 +30,7 @@ describe ResolveURLService, type: :service do expect(subject.call(url)).to eq known_account end - context 'searching for a remote private status' do + context 'when searching for a remote private status' do let(:account) { Fabricate(:account) } let(:poster) { Fabricate(:account, domain: 'example.com') } let(:url) { 'https://example.com/@foo/42' } @@ -95,7 +95,7 @@ describe ResolveURLService, type: :service do end end - context 'searching for a local private status' do + context 'when searching for a local private status' do let(:account) { Fabricate(:account) } let(:poster) { Fabricate(:account) } let!(:status) { Fabricate(:status, account: poster, visibility: :private) } @@ -127,13 +127,13 @@ describe ResolveURLService, type: :service do end end - context 'searching for a link that redirects to a local public status' do + context 'when searching for a link that redirects to a local public status' do let(:account) { Fabricate(:account) } let(:poster) { Fabricate(:account) } let!(:status) { Fabricate(:status, account: poster, visibility: :public) } let(:url) { 'https://link.to/foobar' } let(:status_url) { ActivityPub::TagManager.instance.url_for(status) } - let(:uri) { ActivityPub::TagManager.instance.uri_for(status) } + let(:uri) { ActivityPub::TagManager.instance.uri_for(status) } before do stub_request(:get, url).to_return(status: 302, headers: { 'Location' => status_url }) diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index 5b52662ba..cb69af5f5 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -13,8 +13,8 @@ describe SearchService, type: :service do results = subject.call('', nil, 10) expect(results).to eq(empty_results) - expect(AccountSearchService).not_to have_received(:new) - expect(Tag).not_to have_received(:search_for) + expect(AccountSearchService).to_not have_received(:new) + expect(Tag).to_not have_received(:search_for) end end @@ -23,9 +23,9 @@ describe SearchService, type: :service do @query = 'http://test.host/query' end - context 'that does not find anything' do + context 'when it does not find anything' do it 'returns the empty results' do - service = double(call: nil) + service = instance_double(ResolveURLService, call: nil) allow(ResolveURLService).to receive(:new).and_return(service) results = subject.call(@query, nil, 10, resolve: true) @@ -34,10 +34,10 @@ describe SearchService, type: :service do end end - context 'that finds an account' do + context 'when it finds an account' do it 'includes the account in the results' do account = Account.new - service = double(call: account) + service = instance_double(ResolveURLService, call: account) allow(ResolveURLService).to receive(:new).and_return(service) results = subject.call(@query, nil, 10, resolve: true) @@ -46,10 +46,10 @@ describe SearchService, type: :service do end end - context 'that finds a status' do + context 'when it finds a status' do it 'includes the status in the results' do status = Status.new - service = double(call: status) + service = instance_double(ResolveURLService, call: status) allow(ResolveURLService).to receive(:new).and_return(service) results = subject.call(@query, nil, 10, resolve: true) @@ -60,45 +60,29 @@ describe SearchService, type: :service do end describe 'with a non-url query' do - context 'that matches an account' do + context 'when it matches an account' do it 'includes the account in the results' do query = 'username' account = Account.new - service = double(call: [account]) + service = instance_double(AccountSearchService, call: [account]) allow(AccountSearchService).to receive(:new).and_return(service) results = subject.call(query, nil, 10) - expect(service).to have_received(:call).with(query, nil, limit: 10, offset: 0, resolve: false) + expect(service).to have_received(:call).with(query, nil, limit: 10, offset: 0, resolve: false, start_with_hashtag: false, use_searchable_text: true, following: false) expect(results).to eq empty_results.merge(accounts: [account]) end end - context 'that matches a tag' do + context 'when it matches a tag' do it 'includes the tag in the results' do query = '#tag' tag = Tag.new - allow(Tag).to receive(:search_for).with('tag', 10, 0, exclude_unreviewed: nil).and_return([tag]) + allow(Tag).to receive(:search_for).with('tag', 10, 0, { exclude_unreviewed: nil }).and_return([tag]) results = subject.call(query, nil, 10) expect(Tag).to have_received(:search_for).with('tag', 10, 0, exclude_unreviewed: nil) expect(results).to eq empty_results.merge(hashtags: [tag]) end - it 'does not include tag when starts with @ character' do - query = '@username' - allow(Tag).to receive(:search_for) - - results = subject.call(query, nil, 10) - expect(Tag).not_to have_received(:search_for) - expect(results).to eq empty_results - end - it 'does not include account when starts with # character' do - query = '#tag' - allow(AccountSearchService).to receive(:new) - - results = subject.call(query, nil, 10) - expect(AccountSearchService).to_not have_received(:new) - expect(results).to eq empty_results - end end end end diff --git a/spec/services/software_update_check_service_spec.rb b/spec/services/software_update_check_service_spec.rb new file mode 100644 index 000000000..c8821348a --- /dev/null +++ b/spec/services/software_update_check_service_spec.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe SoftwareUpdateCheckService, type: :service do + subject { described_class.new } + + shared_examples 'when the feature is enabled' do + let(:full_update_check_url) { "#{update_check_url}?version=#{Mastodon::Version.to_s.split('+')[0]}" } + + let(:devops_role) { Fabricate(:user_role, name: 'DevOps', permissions: UserRole::FLAGS[:view_devops]) } + let(:owner_user) { Fabricate(:user, role: UserRole.find_by(name: 'Owner')) } + let(:old_devops_user) { Fabricate(:user) } + let(:none_user) { Fabricate(:user, role: devops_role) } + let(:patch_user) { Fabricate(:user, role: devops_role) } + let(:critical_user) { Fabricate(:user, role: devops_role) } + + around do |example| + queue_adapter = ActiveJob::Base.queue_adapter + ActiveJob::Base.queue_adapter = :test + + example.run + + ActiveJob::Base.queue_adapter = queue_adapter + end + + before do + Fabricate(:software_update, version: '3.5.0', type: 'major', urgent: false) + Fabricate(:software_update, version: '42.13.12', type: 'major', urgent: false) + + owner_user.settings.update('notification_emails.software_updates': 'all') + owner_user.save! + + old_devops_user.settings.update('notification_emails.software_updates': 'all') + old_devops_user.save! + + none_user.settings.update('notification_emails.software_updates': 'none') + none_user.save! + + patch_user.settings.update('notification_emails.software_updates': 'patch') + patch_user.save! + + critical_user.settings.update('notification_emails.software_updates': 'critical') + critical_user.save! + end + + context 'when the update server errors out' do + before do + stub_request(:get, full_update_check_url).to_return(status: 404) + end + + it 'deletes outdated update records but keeps valid update records' do + expect { subject.call }.to change { SoftwareUpdate.pluck(:version).sort }.from(['3.5.0', '42.13.12']).to(['42.13.12']) + end + end + + context 'when the server returns new versions' do + let(:server_json) do + { + updatesAvailable: [ + { + version: '4.2.1', + urgent: false, + type: 'patch', + releaseNotes: 'https://github.com/mastodon/mastodon/releases/v4.2.1', + }, + { + version: '4.3.0', + urgent: false, + type: 'minor', + releaseNotes: 'https://github.com/mastodon/mastodon/releases/v4.3.0', + }, + { + version: '5.0.0', + urgent: false, + type: 'minor', + releaseNotes: 'https://github.com/mastodon/mastodon/releases/v5.0.0', + }, + ], + } + end + + before do + stub_request(:get, full_update_check_url).to_return(body: Oj.dump(server_json)) + end + + it 'updates the list of known updates' do + expect { subject.call }.to change { SoftwareUpdate.pluck(:version).sort }.from(['3.5.0', '42.13.12']).to(['4.2.1', '4.3.0', '5.0.0']) + end + + context 'when no update is urgent' do + it 'sends e-mail notifications according to settings', :aggregate_failures do + expect { subject.call }.to have_enqueued_mail(AdminMailer, :new_software_updates) + .with(hash_including(params: { recipient: owner_user.account })).once + .and(have_enqueued_mail(AdminMailer, :new_software_updates).with(hash_including(params: { recipient: patch_user.account })).once) + .and(have_enqueued_mail.at_most(2)) + end + end + + context 'when an update is urgent' do + let(:server_json) do + { + updatesAvailable: [ + { + version: '5.0.0', + urgent: true, + type: 'minor', + releaseNotes: 'https://github.com/mastodon/mastodon/releases/v5.0.0', + }, + ], + } + end + + it 'sends e-mail notifications according to settings', :aggregate_failures do + expect { subject.call }.to have_enqueued_mail(AdminMailer, :new_critical_software_updates) + .with(hash_including(params: { recipient: owner_user.account })).once + .and(have_enqueued_mail(AdminMailer, :new_critical_software_updates).with(hash_including(params: { recipient: patch_user.account })).once) + .and(have_enqueued_mail(AdminMailer, :new_critical_software_updates).with(hash_including(params: { recipient: critical_user.account })).once) + .and(have_enqueued_mail.at_most(3)) + end + end + end + end + + context 'when update checking is disabled' do + around do |example| + ClimateControl.modify UPDATE_CHECK_URL: '' do + example.run + end + end + + before do + Fabricate(:software_update, version: '3.5.0', type: 'major', urgent: false) + end + + it 'deletes outdated update records' do + expect { subject.call }.to change(SoftwareUpdate, :count).from(1).to(0) + end + end + + context 'when using the default update checking API' do + let(:update_check_url) { 'https://api.joinmastodon.org/update-check' } + + it_behaves_like 'when the feature is enabled' + end + + context 'when using a custom update check URL' do + let(:update_check_url) { 'https://api.example.com/update_check' } + + around do |example| + ClimateControl.modify UPDATE_CHECK_URL: 'https://api.example.com/update_check' do + example.run + end + end + + it_behaves_like 'when the feature is enabled' + end +end diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb index 5d45e4ffd..edb705008 100644 --- a/spec/services/suspend_account_service_spec.rb +++ b/spec/services/suspend_account_service_spec.rb @@ -1,18 +1,21 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe SuspendAccountService, type: :service do shared_examples 'common behavior' do + subject { described_class.new.call(account) } + let!(:local_follower) { Fabricate(:user, current_sign_in_at: 1.hour.ago).account } let!(:list) { Fabricate(:list, account: local_follower) } - subject { described_class.new.call(account) } - before do - allow(FeedManager.instance).to receive(:unmerge_from_home).and_return(nil) - allow(FeedManager.instance).to receive(:unmerge_from_list).and_return(nil) + allow(FeedManager.instance).to receive_messages(unmerge_from_home: nil, unmerge_from_list: nil) local_follower.follow!(account) list.accounts << account + + account.suspend! end it "unmerges from local followers' feeds" do @@ -21,8 +24,8 @@ RSpec.describe SuspendAccountService, type: :service do expect(FeedManager.instance).to have_received(:unmerge_from_list).with(account, list) end - it 'marks account as suspended' do - expect { subject }.to change { account.suspended? }.from(false).to(true) + it 'does not change the “suspended” flag' do + expect { subject }.to_not change(account, :suspended?) end end @@ -40,8 +43,8 @@ RSpec.describe SuspendAccountService, type: :service do include_examples 'common behavior' do let!(:account) { Fabricate(:account) } - let!(:remote_follower) { Fabricate(:account, uri: 'https://alice.com', inbox_url: 'https://alice.com/inbox', protocol: :activitypub) } - let!(:remote_reporter) { Fabricate(:account, uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) } + let!(:remote_follower) { Fabricate(:account, uri: 'https://alice.com', inbox_url: 'https://alice.com/inbox', protocol: :activitypub, domain: 'alice.com') } + let!(:remote_reporter) { Fabricate(:account, uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub, domain: 'bob.com') } let!(:report) { Fabricate(:report, account: remote_reporter, target_account: account) } before do diff --git a/spec/services/translate_status_service_spec.rb b/spec/services/translate_status_service_spec.rb new file mode 100644 index 000000000..5f6418f5d --- /dev/null +++ b/spec/services/translate_status_service_spec.rb @@ -0,0 +1,234 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe TranslateStatusService, type: :service do + subject(:service) { described_class.new } + + let(:status) { Fabricate(:status, text: text, spoiler_text: spoiler_text, language: 'en', preloadable_poll: poll, media_attachments: media_attachments) } + let(:text) { 'Hello' } + let(:spoiler_text) { '' } + let(:poll) { nil } + let(:media_attachments) { [] } + + before do + Fabricate(:custom_emoji, shortcode: 'highfive') + end + + describe '#call' do + before do + translation_service = TranslationService.new + allow(translation_service).to receive(:languages).and_return({ 'en' => ['es'] }) + allow(translation_service).to receive(:translate) do |texts| + texts.map do |text| + TranslationService::Translation.new( + text: text.gsub('Hello', 'Hola').gsub('higfive', 'cincoaltos'), + detected_source_language: 'en', + provider: 'Dummy' + ) + end + end + + allow(TranslationService).to receive_messages(configured?: true, configured: translation_service) + end + + it 'returns translated status content' do + expect(service.call(status, 'es').content).to eq '

Hola

' + end + + it 'returns source language' do + expect(service.call(status, 'es').detected_source_language).to eq 'en' + end + + it 'returns translation provider' do + expect(service.call(status, 'es').provider).to eq 'Dummy' + end + + it 'returns original status' do + expect(service.call(status, 'es').status).to eq status + end + + describe 'status has content with custom emoji' do + let(:text) { 'Hello & :highfive:' } + + it 'does not translate shortcode' do + expect(service.call(status, 'es').content).to eq '

Hola & :highfive:

' + end + end + + describe 'status has no spoiler_text' do + it 'returns an empty string' do + expect(service.call(status, 'es').spoiler_text).to eq '' + end + end + + describe 'status has spoiler_text' do + let(:spoiler_text) { 'Hello & Hello!' } + + it 'translates the spoiler text' do + expect(service.call(status, 'es').spoiler_text).to eq 'Hola & Hola!' + end + end + + describe 'status has spoiler_text with custom emoji' do + let(:spoiler_text) { 'Hello :highfive:' } + + it 'does not translate shortcode' do + expect(service.call(status, 'es').spoiler_text).to eq 'Hola :highfive:' + end + end + + describe 'status has spoiler_text with unmatched custom emoji' do + let(:spoiler_text) { 'Hello :Hello:' } + + it 'translates the invalid shortcode' do + expect(service.call(status, 'es').spoiler_text).to eq 'Hola :Hola:' + end + end + + describe 'status has poll' do + let(:poll) { Fabricate(:poll, options: ['Hello 1', 'Hello 2']) } + + it 'translates the poll option title' do + status_translation = service.call(status, 'es') + expect(status_translation.poll_options.size).to eq 2 + expect(status_translation.poll_options.first.title).to eq 'Hola 1' + end + end + + describe 'status has media attachment' do + let(:media_attachments) { [Fabricate(:media_attachment, description: 'Hello & :highfive:')] } + + it 'translates the media attachment description' do + status_translation = service.call(status, 'es') + + media_attachment = status_translation.media_attachments.first + expect(media_attachment.id).to eq media_attachments.first.id + expect(media_attachment.description).to eq 'Hola & :highfive:' + end + end + end + + describe '#source_texts' do + before do + service.instance_variable_set(:@status, status) + end + + describe 'status only has content' do + it 'returns formatted content' do + expect(service.send(:source_texts)).to eq({ content: '

Hello

' }) + end + end + + describe 'status content contains custom emoji' do + let(:status) { Fabricate(:status, text: 'Hello :highfive:') } + + it 'returns formatted content' do + source_texts = service.send(:source_texts) + expect(source_texts[:content]).to eq '

Hello :highfive:

' + end + end + + describe 'status content contains tags' do + let(:status) { Fabricate(:status, text: 'Hello #hola') } + + it 'returns formatted content' do + source_texts = service.send(:source_texts) + expect(source_texts[:content]).to include '

Hello :highfive:' + end + end + + describe 'status has poll' do + let(:poll) { Fabricate(:poll, options: %w(Blue Green)) } + + context 'with source texts from the service' do + let!(:source_texts) { service.send(:source_texts) } + + it 'returns formatted poll options' do + expect(source_texts.size).to eq 3 + expect(source_texts.values).to eq %w(

Hello

Blue Green) + end + + it 'has a first key with content' do + expect(source_texts.keys.first).to eq :content + end + + it 'has the first option in the second key with correct options' do + option1 = source_texts.keys.second + expect(option1).to be_a Poll::Option + expect(option1.id).to eq '0' + expect(option1.title).to eq 'Blue' + end + + it 'has the second option in the third key with correct options' do + option2 = source_texts.keys.third + expect(option2).to be_a Poll::Option + expect(option2.id).to eq '1' + expect(option2.title).to eq 'Green' + end + end + end + + describe 'status has poll with custom emoji' do + let(:poll) { Fabricate(:poll, options: ['Blue', 'Green :highfive:']) } + + it 'returns formatted poll options' do + html = service.send(:source_texts).values.last + expect(html).to eq 'Green :highfive:' + end + end + + describe 'status has media attachments' do + let(:text) { '' } + let(:media_attachments) { [Fabricate(:media_attachment, description: 'Hello :highfive:')] } + + it 'returns media attachments without custom emoji rendering' do + source_texts = service.send(:source_texts) + expect(source_texts.size).to eq 1 + + key, text = source_texts.first + expect(key).to eq media_attachments.first + expect(text).to eq 'Hello :highfive:' + end + end + end + + describe '#wrap_emoji_shortcodes' do + before do + service.instance_variable_set(:@status, status) + end + + describe 'string contains custom emoji' do + let(:text) { ':highfive:' } + + it 'renders the emoji' do + html = service.send(:wrap_emoji_shortcodes, 'Hello :highfive:'.html_safe) + expect(html).to eq 'Hello :highfive:' + end + end + end + + describe '#unwrap_emoji_shortcodes' do + describe 'string contains custom emoji' do + it 'inserts the shortcode' do + fragment = service.send(:unwrap_emoji_shortcodes, '

Hello :highfive:!

') + expect(fragment.to_html).to eq '

Hello :highfive:!

' + end + + it 'preserves other attributes than translate=no' do + fragment = service.send(:unwrap_emoji_shortcodes, '

Hello :highfive:!

') + expect(fragment.to_html).to eq '

Hello :highfive:!

' + end + end + end +end diff --git a/spec/services/unallow_domain_service_spec.rb b/spec/services/unallow_domain_service_spec.rb index b93945b9a..19d40e7e8 100644 --- a/spec/services/unallow_domain_service_spec.rb +++ b/spec/services/unallow_domain_service_spec.rb @@ -1,18 +1,20 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe UnallowDomainService, type: :service do + subject { described_class.new } + let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') } - let!(:bad_status1) { Fabricate(:status, account: bad_account, text: 'You suck') } - let!(:bad_status2) { Fabricate(:status, account: bad_account, text: 'Hahaha') } - let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status2, file: attachment_fixture('attachment.jpg')) } + let!(:bad_status_harassment) { Fabricate(:status, account: bad_account, text: 'You suck') } + let!(:bad_status_mean) { Fabricate(:status, account: bad_account, text: 'Hahaha') } + let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status_mean, file: attachment_fixture('attachment.jpg')) } let!(:already_banned_account) { Fabricate(:account, username: 'badguy', domain: 'evil.org', suspended: true, silenced: true) } let!(:domain_allow) { Fabricate(:domain_allow, domain: 'evil.org') } - subject { UnallowDomainService.new } - - context 'in limited federation mode' do + context 'with limited federation mode' do before do - allow(subject).to receive(:whitelist_mode?).and_return(true) + allow(Rails.configuration.x).to receive(:limited_federation_mode).and_return(true) end describe '#call' do @@ -29,8 +31,8 @@ RSpec.describe UnallowDomainService, type: :service do end it 'removes the remote accounts\'s statuses and media attachments' do - expect { bad_status1.reload }.to raise_exception ActiveRecord::RecordNotFound - expect { bad_status2.reload }.to raise_exception ActiveRecord::RecordNotFound + expect { bad_status_harassment.reload }.to raise_exception ActiveRecord::RecordNotFound + expect { bad_status_mean.reload }.to raise_exception ActiveRecord::RecordNotFound expect { bad_attachment.reload }.to raise_exception ActiveRecord::RecordNotFound end end @@ -38,7 +40,7 @@ RSpec.describe UnallowDomainService, type: :service do context 'without limited federation mode' do before do - allow(subject).to receive(:whitelist_mode?).and_return(false) + allow(Rails.configuration.x).to receive(:limited_federation_mode).and_return(false) end describe '#call' do @@ -55,8 +57,8 @@ RSpec.describe UnallowDomainService, type: :service do end it 'removes the remote accounts\'s statuses and media attachments' do - expect { bad_status1.reload }.to_not raise_error - expect { bad_status2.reload }.to_not raise_error + expect { bad_status_harassment.reload }.to_not raise_error + expect { bad_status_mean.reload }.to_not raise_error expect { bad_attachment.reload }.to_not raise_error end end diff --git a/spec/services/unblock_service_spec.rb b/spec/services/unblock_service_spec.rb index 10448b340..86632c393 100644 --- a/spec/services/unblock_service_spec.rb +++ b/spec/services/unblock_service_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe UnblockService, type: :service do - let(:sender) { Fabricate(:account, username: 'alice') } + subject { described_class.new } - subject { UnblockService.new } + let(:sender) { Fabricate(:account, username: 'alice') } describe 'local' do let(:bob) { Fabricate(:account) } diff --git a/spec/services/unfollow_service_spec.rb b/spec/services/unfollow_service_spec.rb index bb5bef5c9..3e65e610b 100644 --- a/spec/services/unfollow_service_spec.rb +++ b/spec/services/unfollow_service_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe UnfollowService, type: :service do - let(:sender) { Fabricate(:account, username: 'alice') } + subject { described_class.new } - subject { UnfollowService.new } + let(:sender) { Fabricate(:account, username: 'alice') } describe 'local' do let(:bob) { Fabricate(:account, username: 'bob') } diff --git a/spec/services/unmute_service_spec.rb b/spec/services/unmute_service_spec.rb deleted file mode 100644 index 8463eb283..000000000 --- a/spec/services/unmute_service_spec.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'rails_helper' - -RSpec.describe UnmuteService, type: :service do - subject { UnmuteService.new } -end diff --git a/spec/services/unsuspend_account_service_spec.rb b/spec/services/unsuspend_account_service_spec.rb index 3ac4cc085..c555b661e 100644 --- a/spec/services/unsuspend_account_service_spec.rb +++ b/spec/services/unsuspend_account_service_spec.rb @@ -1,20 +1,21 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe UnsuspendAccountService, type: :service do - shared_examples 'common behavior' do + shared_context 'with common context' do + subject { described_class.new.call(account) } + let!(:local_follower) { Fabricate(:user, current_sign_in_at: 1.hour.ago).account } let!(:list) { Fabricate(:list, account: local_follower) } - subject { described_class.new.call(account) } - before do - allow(FeedManager.instance).to receive(:merge_into_home).and_return(nil) - allow(FeedManager.instance).to receive(:merge_into_list).and_return(nil) + allow(FeedManager.instance).to receive_messages(merge_into_home: nil, merge_into_list: nil) local_follower.follow!(account) list.accounts << account - account.suspend!(origin: :local) + account.unsuspend! end end @@ -30,14 +31,14 @@ RSpec.describe UnsuspendAccountService, type: :service do stub_request(:post, 'https://bob.com/inbox').to_return(status: 201) end - it 'marks account as unsuspended' do - expect { subject }.to change { account.suspended? }.from(true).to(false) + it 'does not change the “suspended” flag' do + expect { subject }.to_not change(account, :suspended?) end - include_examples 'common behavior' do + include_examples 'with common context' do let!(:account) { Fabricate(:account) } - let!(:remote_follower) { Fabricate(:account, uri: 'https://alice.com', inbox_url: 'https://alice.com/inbox', protocol: :activitypub) } - let!(:remote_reporter) { Fabricate(:account, uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) } + let!(:remote_follower) { Fabricate(:account, uri: 'https://alice.com', inbox_url: 'https://alice.com/inbox', protocol: :activitypub, domain: 'alice.com') } + let!(:remote_reporter) { Fabricate(:account, uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub, domain: 'bob.com') } let!(:report) { Fabricate(:report, account: remote_reporter, target_account: account) } before do @@ -59,9 +60,9 @@ RSpec.describe UnsuspendAccountService, type: :service do end describe 'unsuspending a remote account' do - include_examples 'common behavior' do + include_examples 'with common context' do let!(:account) { Fabricate(:account, domain: 'bob.com', uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) } - let!(:resolve_account_service) { double } + let!(:resolve_account_service) { instance_double(ResolveAccountService) } before do allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service) @@ -83,8 +84,8 @@ RSpec.describe UnsuspendAccountService, type: :service do expect(FeedManager.instance).to have_received(:merge_into_list).with(account, list) end - it 'marks account as unsuspended' do - expect { subject }.to change { account.suspended? }.from(true).to(false) + it 'does not change the “suspended” flag' do + expect { subject }.to_not change(account, :suspended?) end end @@ -107,8 +108,8 @@ RSpec.describe UnsuspendAccountService, type: :service do expect(FeedManager.instance).to_not have_received(:merge_into_list).with(account, list) end - it 'does not mark the account as unsuspended' do - expect { subject }.not_to change { account.suspended? } + it 'marks account as suspended' do + expect { subject }.to change(account, :suspended?).from(false).to(true) end end diff --git a/spec/services/update_account_service_spec.rb b/spec/services/update_account_service_spec.rb index c2dc791e4..6318cc95f 100644 --- a/spec/services/update_account_service_spec.rb +++ b/spec/services/update_account_service_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe UpdateAccountService, type: :service do - subject { UpdateAccountService.new } + subject { described_class.new } describe 'switching form locked to unlocked accounts' do let(:account) { Fabricate(:account, locked: true) } diff --git a/spec/services/update_status_service_spec.rb b/spec/services/update_status_service_spec.rb index 71a73be5b..9c53ebb2f 100644 --- a/spec/services/update_status_service_spec.rb +++ b/spec/services/update_status_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe UpdateStatusService, type: :service do @@ -87,16 +89,40 @@ RSpec.describe UpdateStatusService, type: :service do end end + context 'when already-attached media changes' do + let!(:status) { Fabricate(:status, text: 'Foo') } + let!(:media_attachment) { Fabricate(:media_attachment, account: status.account, description: 'Old description') } + + before do + status.media_attachments << media_attachment + subject.call(status, status.account_id, text: 'Foo', media_ids: [media_attachment.id], media_attributes: [{ id: media_attachment.id, description: 'New description' }]) + end + + it 'does not detach media attachment' do + expect(media_attachment.reload.status_id).to eq status.id + end + + it 'updates the media attachment description' do + expect(media_attachment.reload.description).to eq 'New description' + end + + it 'saves edit history' do + expect(status.edits.map { |edit| edit.ordered_media_attachments.map(&:description) }).to eq [['Old description'], ['New description']] + end + end + context 'when poll changes' do let(:account) { Fabricate(:account) } - let!(:status) { Fabricate(:status, text: 'Foo', account: account, poll_attributes: {options: %w(Foo Bar), account: account, multiple: false, hide_totals: false, expires_at: 7.days.from_now }) } + let!(:status) { Fabricate(:status, text: 'Foo', account: account, poll_attributes: { options: %w(Foo Bar), account: account, multiple: false, hide_totals: false, expires_at: 7.days.from_now }) } let!(:poll) { status.poll } let!(:voter) { Fabricate(:account) } before do status.update(poll: poll) VoteService.new.call(voter, poll, [0]) - subject.call(status, status.account_id, text: 'Foo', poll: { options: %w(Bar Baz Foo), expires_in: 5.days.to_i }) + Sidekiq::Testing.fake! do + subject.call(status, status.account_id, text: 'Foo', poll: { options: %w(Bar Baz Foo), expires_in: 5.days.to_i }) + end end it 'updates poll' do @@ -114,6 +140,11 @@ RSpec.describe UpdateStatusService, type: :service do it 'saves edit history' do expect(status.edits.pluck(:poll_options)).to eq [%w(Foo Bar), %w(Bar Baz Foo)] end + + it 'requeues expiration notification' do + poll = status.poll.reload + expect(PollExpirationNotifyWorker).to have_enqueued_sidekiq_job(poll.id).at(poll.expires_at + 5.minutes) + end end context 'when mentions in text change' do @@ -131,7 +162,7 @@ RSpec.describe UpdateStatusService, type: :service do end it 'keeps old mentions as silent mentions' do - expect(status.mentions.pluck(:account_id)).to match_array([alice.id, bob.id]) + expect(status.mentions.pluck(:account_id)).to contain_exactly(alice.id, bob.id) end end diff --git a/spec/services/verify_link_service_spec.rb b/spec/services/verify_link_service_spec.rb index 3fc88e60e..d06344f9c 100644 --- a/spec/services/verify_link_service_spec.rb +++ b/spec/services/verify_link_service_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'rails_helper' RSpec.describe VerifyLinkService, type: :service do subject { described_class.new } - context 'given a local account' do + context 'when given a local account' do let(:account) { Fabricate(:account, username: 'alice') } let(:field) { Account::Field.new(account, 'name' => 'Website', 'value' => 'http://example.com') } @@ -73,16 +75,62 @@ RSpec.describe VerifyLinkService, type: :service do end end + context 'when a document is truncated but the link back is valid' do + let(:html) do + " + + + + " + end + + it 'marks the field as verified' do + expect(field.verified?).to be true + end + end + + context 'when a link tag might be truncated' do + let(:html) do + " + + + + + + + + Follow me on Mastodon + + HTML + end + + it 'does not mark the field as verified' do expect(field.verified?).to be false end end end - context 'given a remote account' do + context 'when given a remote account' do let(:account) { Fabricate(:account, username: 'alice', domain: 'example.com', url: 'https://profile.example.com/alice') } let(:field) { Account::Field.new(account, 'name' => 'Website', 'value' => 'example.com') } @@ -105,5 +153,27 @@ RSpec.describe VerifyLinkService, type: :service do expect(field.verified?).to be true end end + + context 'when the link contains a link with a missing protocol slash' do + # This was seen in the wild where a user had three pages: + # 1. their mastodon profile, which linked to github and the personal website + # 2. their personal website correctly linking back to mastodon + # 3. a github profile that was linking to the personal website, but with + # a malformed protocol of http:/ + # + # This caused link verification between the mastodon profile and the + # website to fail. + # + # apparently github allows the user to enter website URLs with a single + # slash and makes no attempts to correct that. + let(:html) { 'Hello' } + + it 'does not crash' do + # We could probably put more effort into perhaps auto-correcting the + # link and following it anyway, but at the very least we shouldn't let + # exceptions bubble up + expect(field.verified?).to be false + end + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0414ba9ed..030bc81fb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,18 +1,19 @@ -GC.disable +# frozen_string_literal: true if ENV['DISABLE_SIMPLECOV'] != 'true' require 'simplecov' SimpleCov.start 'rails' do - add_group 'Services', 'app/services' + add_filter 'lib/linter' + add_group 'Policies', 'app/policies' add_group 'Presenters', 'app/presenters' + add_group 'Serializers', 'app/serializers' + add_group 'Services', 'app/services' add_group 'Validators', 'app/validators' end end -gc_counter = -1 - RSpec.configure do |config| - config.example_status_persistence_file_path = "tmp/rspec/examples.txt" + config.example_status_persistence_file_path = 'tmp/rspec/examples.txt' config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true end @@ -30,23 +31,16 @@ RSpec.configure do |config| config.before :suite do Rails.application.load_seed Chewy.strategy(:bypass) + + # NOTE: we switched registrations mode to closed by default, but the specs + # very heavily rely on having it enabled by default, as it relies on users + # being approved by default except in select cases where explicitly testing + # other registration modes + Setting.registrations_mode = 'open' end config.after :suite do - gc_counter = 0 - FileUtils.rm_rf(Dir["#{Rails.root}/spec/test_files/"]) - end - - config.after :each do - gc_counter += 1 - - if gc_counter > 19 - GC.enable - GC.start - GC.disable - - gc_counter = 0 - end + FileUtils.rm_rf(Dir[Rails.root.join('spec', 'test_files')]) end end @@ -60,7 +54,126 @@ end def expect_push_bulk_to_match(klass, matcher) expect(Sidekiq::Client).to receive(:push_bulk).with(hash_including({ - "class" => klass, - "args" => matcher + 'class' => klass, + 'args' => matcher, })) end + +class StreamingServerManager + @running_thread = nil + + def initialize + at_exit { stop } + end + + def start(port: 4020) + return if @running_thread + + queue = Queue.new + + @queue = queue + + @running_thread = Thread.new do + Open3.popen2e( + { + 'REDIS_NAMESPACE' => ENV.fetch('REDIS_NAMESPACE'), + 'DB_NAME' => "#{ENV.fetch('DB_NAME', 'mastodon')}_test#{ENV.fetch('TEST_ENV_NUMBER', '')}", + 'RAILS_ENV' => ENV.fetch('RAILS_ENV', 'test'), + 'NODE_ENV' => ENV.fetch('STREAMING_NODE_ENV', 'development'), + 'PORT' => port.to_s, + }, + 'node index.js', # must not call yarn here, otherwise it will fail because yarn does not send signals to its child process + chdir: Rails.root.join('streaming') + ) do |_stdin, stdout_err, process_thread| + status = :starting + + # Spawn a thread to listen on streaming server output + output_thread = Thread.new do + stdout_err.each_line do |line| + Rails.logger.info "Streaming server: #{line}" + + if status == :starting && line.match('Streaming API now listening on') + status = :started + @queue.enq 'started' + end + end + end + + # And another thread to listen on commands from the main thread + loop do + msg = queue.pop + + case msg + when 'stop' + # we need to properly stop the reading thread + output_thread.kill + + # Then stop the node process + Process.kill('KILL', process_thread.pid) + + # And we stop ourselves + @running_thread.kill + end + end + end + end + + # wait for 10 seconds for the streaming server to start + Timeout.timeout(10) do + loop do + break if @queue.pop == 'started' + end + end + end + + def stop + return unless @running_thread + + @queue.enq 'stop' + + # Wait for the thread to end + @running_thread.join + end +end + +class SearchDataManager + def prepare_test_data + 4.times do |i| + username = "search_test_account_#{i}" + account = Fabricate.create(:account, username: username, indexable: i.even?, discoverable: i.even?, note: "Lover of #{i}.") + 2.times do |j| + Fabricate.create(:status, account: account, text: "#{username}'s #{j} post", visibility: j.even? ? :public : :private) + end + end + + 3.times do |i| + Fabricate.create(:tag, name: "search_test_tag_#{i}") + end + end + + def indexes + [ + AccountsIndex, + PublicStatusesIndex, + StatusesIndex, + TagsIndex, + ] + end + + def populate_indexes + indexes.each do |index_class| + index_class.purge! + index_class.import! + end + end + + def remove_indexes + indexes.each(&:delete!) + end + + def cleanup_test_data + Status.destroy_all + Account.destroy_all + Tag.destroy_all + end +end diff --git a/spec/support/examples/api.rb b/spec/support/examples/api.rb new file mode 100644 index 000000000..d531860ab --- /dev/null +++ b/spec/support/examples/api.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + # Some examples have a subject which needs to be called to make a request + subject if request.nil? + + expect(response).to have_http_status(403) + end +end + +shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + # Some examples have a subject which needs to be called to make a request + subject if request.nil? + + expect(response).to have_http_status(403) + end +end diff --git a/spec/support/examples/lib/admin/checks.rb b/spec/support/examples/lib/admin/checks.rb new file mode 100644 index 000000000..b50faa77b --- /dev/null +++ b/spec/support/examples/lib/admin/checks.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +shared_examples 'a check available to devops users' do + describe 'skip?' do + context 'when user can view devops' do + before { allow(user).to receive(:can?).with(:view_devops).and_return(true) } + + it 'returns false' do + expect(check.skip?).to be false + end + end + + context 'when user cannot view devops' do + before { allow(user).to receive(:can?).with(:view_devops).and_return(false) } + + it 'returns true' do + expect(check.skip?).to be true + end + end + end +end diff --git a/spec/support/examples/lib/settings/scoped_settings.rb b/spec/support/examples/lib/settings/scoped_settings.rb deleted file mode 100644 index 2457dcfbf..000000000 --- a/spec/support/examples/lib/settings/scoped_settings.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'ScopedSettings' do - describe '[]' do - it 'inherits default settings' do - expect(Setting.boost_modal).to eq false - expect(Setting.interactions['must_be_follower']).to eq false - - settings = create! - - expect(settings['boost_modal']).to eq false - expect(settings['interactions']['must_be_follower']).to eq false - end - end - - describe 'all_as_records' do - # expecting [] and []= works - - it 'returns records merged with default values except hashes' do - expect(Setting.boost_modal).to eq false - expect(Setting.delete_modal).to eq true - - settings = create! - settings['boost_modal'] = true - - records = settings.all_as_records - - expect(records['boost_modal'].value).to eq true - expect(records['delete_modal'].value).to eq true - end - end - - describe 'missing methods' do - # expecting [] and []= works. - - it 'reads settings' do - expect(Setting.boost_modal).to eq false - settings = create! - expect(settings.boost_modal).to eq false - end - - it 'updates settings' do - settings = fabricate - settings.boost_modal = true - expect(settings['boost_modal']).to eq true - end - end - - it 'can update settings with [] and can read with []=' do - settings = fabricate - - settings['boost_modal'] = true - settings['interactions'] = settings['interactions'].merge('must_be_follower' => true) - - Setting.save! - - expect(settings['boost_modal']).to eq true - expect(settings['interactions']['must_be_follower']).to eq true - - Rails.cache.clear - - expect(settings['boost_modal']).to eq true - expect(settings['interactions']['must_be_follower']).to eq true - end - - xit 'does not mutate defaults via the cache' do - fabricate['interactions']['must_be_follower'] = true - # TODO - # This mutates the global settings default such that future - # instances will inherit the incorrect starting values - - expect(fabricate.settings['interactions']['must_be_follower']).to eq false - end -end diff --git a/spec/support/examples/lib/settings/settings_extended.rb b/spec/support/examples/lib/settings/settings_extended.rb deleted file mode 100644 index 5a9d34bb0..000000000 --- a/spec/support/examples/lib/settings/settings_extended.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -shared_examples 'Settings-extended' do - describe 'settings' do - def fabricate - super.settings - end - - def create! - super.settings - end - - it_behaves_like 'ScopedSettings' - end -end diff --git a/spec/support/examples/mailers.rb b/spec/support/examples/mailers.rb new file mode 100644 index 000000000..213e873b4 --- /dev/null +++ b/spec/support/examples/mailers.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +shared_examples 'localized subject' do |*args, **kwrest| + it 'renders subject localized for the locale of the receiver' do + locale = :de + receiver.update!(locale: locale) + expect(mail.subject).to eq I18n.t(*args, **kwrest.merge(locale: locale)) + end + + it 'renders subject localized for the default locale if the locale of the receiver is unavailable' do + receiver.update!(locale: nil) + expect(mail.subject).to eq I18n.t(*args, **kwrest.merge(locale: I18n.default_locale)) + end +end diff --git a/spec/support/examples/models/concerns/account_avatar.rb b/spec/support/examples/models/concerns/account_avatar.rb index 2180f5273..16ebda564 100644 --- a/spec/support/examples/models/concerns/account_avatar.rb +++ b/spec/support/examples/models/concerns/account_avatar.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true shared_examples 'AccountAvatar' do |fabricator| - describe 'static avatars' do + describe 'static avatars', paperclip_processing: true do describe 'when GIF' do it 'creates a png static style' do account = Fabricate(fabricator, avatar: attachment_fixture('avatar.gif')) @@ -17,7 +17,7 @@ shared_examples 'AccountAvatar' do |fabricator| end end - describe 'base64-encoded files' do + describe 'base64-encoded files', paperclip_processing: true do let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" } let(:account) { Fabricate(fabricator, avatar: base64_attachment) } diff --git a/spec/support/examples/models/concerns/account_header.rb b/spec/support/examples/models/concerns/account_header.rb index 77ee0e629..d65f54f00 100644 --- a/spec/support/examples/models/concerns/account_header.rb +++ b/spec/support/examples/models/concerns/account_header.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true shared_examples 'AccountHeader' do |fabricator| - describe 'base64-encoded files' do + describe 'base64-encoded files', paperclip_processing: true do let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" } let(:account) { Fabricate(fabricator, header: base64_attachment) } diff --git a/spec/support/matchers/json/match_json_schema.rb b/spec/support/matchers/json/match_json_schema.rb new file mode 100644 index 000000000..3a275199e --- /dev/null +++ b/spec/support/matchers/json/match_json_schema.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +RSpec::Matchers.define :match_json_schema do |schema| + match do |input_json| + schema_path = Rails.root.join('spec', 'support', 'schema', "#{schema}.json").to_s + JSON::Validator.validate(schema_path, input_json, validate_schema: true) + end +end diff --git a/spec/support/matchers/model/model_have_error_on_field.rb b/spec/support/matchers/model/model_have_error_on_field.rb index 85bdd8215..0f9c81a47 100644 --- a/spec/support/matchers/model/model_have_error_on_field.rb +++ b/spec/support/matchers/model/model_have_error_on_field.rb @@ -1,10 +1,10 @@ +# frozen_string_literal: true + RSpec::Matchers.define :model_have_error_on_field do |expected| match do |record| - if record.errors.empty? - record.valid? - end + record.valid? if record.errors.empty? - record.errors.has_key?(expected) + record.errors.key?(expected) end failure_message do |record| diff --git a/spec/support/schema/nodeinfo_2.0.json b/spec/support/schema/nodeinfo_2.0.json new file mode 100644 index 000000000..085ce542b --- /dev/null +++ b/spec/support/schema/nodeinfo_2.0.json @@ -0,0 +1,170 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://nodeinfo.diaspora.software/ns/schema/2.0#", + "description": "NodeInfo schema version 2.0.", + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "software", + "protocols", + "services", + "openRegistrations", + "usage", + "metadata" + ], + "properties": { + "version": { + "description": "The schema version, must be 2.0.", + "enum": ["2.0"] + }, + "software": { + "description": "Metadata about server software in use.", + "type": "object", + "additionalProperties": false, + "required": ["name", "version"], + "properties": { + "name": { + "description": "The canonical name of this server software.", + "type": "string", + "pattern": "^[a-z0-9-]+$" + }, + "version": { + "description": "The version of this server software.", + "type": "string" + } + } + }, + "protocols": { + "description": "The protocols supported on this server.", + "type": "array", + "minItems": 1, + "items": { + "enum": [ + "activitypub", + "buddycloud", + "dfrn", + "diaspora", + "libertree", + "ostatus", + "pumpio", + "tent", + "xmpp", + "zot" + ] + } + }, + "services": { + "description": "The third party sites this server can connect to via their application API.", + "type": "object", + "additionalProperties": false, + "required": ["inbound", "outbound"], + "properties": { + "inbound": { + "description": "The third party sites this server can retrieve messages from for combined display with regular traffic.", + "type": "array", + "minItems": 0, + "items": { + "enum": [ + "atom1.0", + "gnusocial", + "imap", + "pnut", + "pop3", + "pumpio", + "rss2.0", + "twitter" + ] + } + }, + "outbound": { + "description": "The third party sites this server can publish messages to on the behalf of a user.", + "type": "array", + "minItems": 0, + "items": { + "enum": [ + "atom1.0", + "blogger", + "buddycloud", + "diaspora", + "dreamwidth", + "drupal", + "facebook", + "friendica", + "gnusocial", + "google", + "insanejournal", + "libertree", + "linkedin", + "livejournal", + "mediagoblin", + "myspace", + "pinterest", + "pnut", + "posterous", + "pumpio", + "redmatrix", + "rss2.0", + "smtp", + "tent", + "tumblr", + "twitter", + "wordpress", + "xmpp" + ] + } + } + } + }, + "openRegistrations": { + "description": "Whether this server allows open self-registration.", + "type": "boolean" + }, + "usage": { + "description": "Usage statistics for this server.", + "type": "object", + "additionalProperties": false, + "required": ["users"], + "properties": { + "users": { + "description": "statistics about the users of this server.", + "type": "object", + "additionalProperties": false, + "properties": { + "total": { + "description": "The total amount of on this server registered users.", + "type": "integer", + "minimum": 0 + }, + "activeHalfyear": { + "description": "The amount of users that signed in at least once in the last 180 days.", + "type": "integer", + "minimum": 0 + }, + "activeMonth": { + "description": "The amount of users that signed in at least once in the last 30 days.", + "type": "integer", + "minimum": 0 + } + } + }, + "localPosts": { + "description": "The amount of posts that were made by users that are registered on this server.", + "type": "integer", + "minimum": 0 + }, + "localComments": { + "description": "The amount of comments that were made by users that are registered on this server.", + "type": "integer", + "minimum": 0 + } + } + }, + "metadata": { + "description": "Free form key value pairs for software specific values. Clients should not rely on any specific key present.", + "type": "object", + "minProperties": 0, + "additionalProperties": true + } + } +} diff --git a/spec/support/stories/profile_stories.rb b/spec/support/stories/profile_stories.rb index 0c4a14d1c..2b345ddef 100644 --- a/spec/support/stories/profile_stories.rb +++ b/spec/support/stories/profile_stories.rb @@ -9,6 +9,8 @@ module ProfileStories email: email, password: password, confirmed_at: confirmed_at, account: Fabricate(:account, username: 'bob') ) + + Web::Setting.where(user: bob).first_or_initialize(user: bob).update!(data: { introductionVersion: 201812160442020 }) if finished_onboarding # rubocop:disable Style/NumericLiterals end def as_a_logged_in_user @@ -20,8 +22,8 @@ module ProfileStories end def with_alice_as_local_user - @alice_bio = '@alice and @bob are fictional characters commonly used as'\ - 'placeholder names in #cryptology, as well as #science and'\ + @alice_bio = '@alice and @bob are fictional characters commonly used as' \ + 'placeholder names in #cryptology, as well as #science and' \ 'engineering 📖 literature. Not affiliated with @pepe.' @alice = Fabricate( @@ -42,4 +44,8 @@ module ProfileStories def password @password ||= 'password' end + + def finished_onboarding + @finished_onboarding || false + end end diff --git a/spec/system/new_statuses_spec.rb b/spec/system/new_statuses_spec.rb new file mode 100644 index 000000000..6faed6c80 --- /dev/null +++ b/spec/system/new_statuses_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'NewStatuses' do + include ProfileStories + + subject { page } + + let(:email) { 'test@example.com' } + let(:password) { 'password' } + let(:confirmed_at) { Time.zone.now } + let(:finished_onboarding) { true } + + before do + as_a_logged_in_user + visit root_path + end + + it 'can be posted' do + expect(subject).to have_css('div.app-holder') + + status_text = 'This is a new status!' + + within('.compose-form') do + fill_in "What's on your mind?", with: status_text + click_on 'Publish!' + end + + expect(subject).to have_selector('.status__content__text', text: status_text) + end + + it 'can be posted again' do + expect(subject).to have_css('div.app-holder') + + status_text = 'This is a second status!' + + within('.compose-form') do + fill_in "What's on your mind?", with: status_text + click_on 'Publish!' + end + + expect(subject).to have_selector('.status__content__text', text: status_text) + end +end diff --git a/spec/validators/blacklisted_email_validator_spec.rb b/spec/validators/blacklisted_email_validator_spec.rb index 351de0707..bfe2a11a9 100644 --- a/spec/validators/blacklisted_email_validator_spec.rb +++ b/spec/validators/blacklisted_email_validator_spec.rb @@ -4,21 +4,22 @@ require 'rails_helper' RSpec.describe BlacklistedEmailValidator, type: :validator do describe '#validate' do - let(:user) { double(email: 'info@mail.com', sign_up_ip: '1.2.3.4', errors: errors) } - let(:errors) { double(add: nil) } + subject { described_class.new.validate(user); errors } + + let(:user) { instance_double(User, email: 'info@mail.com', sign_up_ip: '1.2.3.4', errors: errors) } + let(:errors) { instance_double(ActiveModel::Errors, add: nil) } before do - allow(user).to receive(:valid_invitation?) { false } - allow_any_instance_of(described_class).to receive(:blocked_email_provider?) { blocked_email } + allow(user).to receive(:valid_invitation?).and_return(false) + allow(EmailDomainBlock).to receive(:block?) { blocked_email } end - subject { described_class.new.validate(user); errors } - context 'when e-mail provider is blocked' do let(:blocked_email) { true } it 'adds error' do - expect(subject).to have_received(:add).with(:email, :blocked) + described_class.new.validate(user) + expect(errors).to have_received(:add).with(:email, :blocked).once end end @@ -26,7 +27,8 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do let(:blocked_email) { false } it 'does not add errors' do - expect(subject).not_to have_received(:add).with(:email, :blocked) + described_class.new.validate(user) + expect(errors).to_not have_received(:add) end context 'when canonical e-mail is blocked' do @@ -37,7 +39,8 @@ RSpec.describe BlacklistedEmailValidator, type: :validator do end it 'adds error' do - expect(subject).to have_received(:add).with(:email, :taken) + described_class.new.validate(user) + expect(errors).to have_received(:add).with(:email, :taken).once end end end diff --git a/spec/validators/disallowed_hashtags_validator_spec.rb b/spec/validators/disallowed_hashtags_validator_spec.rb index 9deec0bb9..7144d2891 100644 --- a/spec/validators/disallowed_hashtags_validator_spec.rb +++ b/spec/validators/disallowed_hashtags_validator_spec.rb @@ -11,19 +11,19 @@ RSpec.describe DisallowedHashtagsValidator, type: :validator do described_class.new.validate(status) end - let(:status) { double(errors: errors, local?: local, reblog?: reblog, text: disallowed_tags.map { |x| '#' + x }.join(' ')) } - let(:errors) { double(add: nil) } + let(:status) { instance_double(Status, errors: errors, local?: local, reblog?: reblog, text: disallowed_tags.map { |x| "##{x}" }.join(' ')) } + let(:errors) { instance_double(ActiveModel::Errors, add: nil) } - context 'for a remote reblog' do + context 'with a remote reblog' do let(:local) { false } let(:reblog) { true } it 'does not add errors' do - expect(errors).not_to have_received(:add).with(:text, any_args) + expect(errors).to_not have_received(:add).with(:text, any_args) end end - context 'for a local original status' do + context 'with a local original status' do let(:local) { true } let(:reblog) { false } @@ -31,7 +31,7 @@ RSpec.describe DisallowedHashtagsValidator, type: :validator do let(:disallowed_tags) { [] } it 'does not add errors' do - expect(errors).not_to have_received(:add).with(:text, any_args) + expect(errors).to_not have_received(:add).with(:text, any_args) end end diff --git a/spec/validators/email_mx_validator_spec.rb b/spec/validators/email_mx_validator_spec.rb index 4feedd0c7..876d73c18 100644 --- a/spec/validators/email_mx_validator_spec.rb +++ b/spec/validators/email_mx_validator_spec.rb @@ -4,9 +4,9 @@ require 'rails_helper' describe EmailMxValidator do describe '#validate' do - let(:user) { double(email: 'foo@example.com', sign_up_ip: '1.2.3.4', errors: double(add: nil)) } + let(:user) { instance_double(User, email: 'foo@example.com', sign_up_ip: '1.2.3.4', errors: instance_double(ActiveModel::Errors, add: nil)) } - context 'for an e-mail domain that is explicitly allowed' do + context 'with an e-mail domain that is explicitly allowed' do around do |block| tmp = Rails.configuration.x.email_domains_whitelist Rails.configuration.x.email_domains_whitelist = 'example.com' @@ -15,7 +15,7 @@ describe EmailMxValidator do end it 'does not add errors if there are no DNS records' do - resolver = double + resolver = instance_double(Resolv::DNS) allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([]) allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([]) @@ -28,8 +28,51 @@ describe EmailMxValidator do end end + it 'adds no error if there are DNS records for the e-mail domain' do + resolver = instance_double(Resolv::DNS) + + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([]) + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([Resolv::DNS::Resource::IN::A.new('192.0.2.42')]) + allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([]) + allow(resolver).to receive(:timeouts=).and_return(nil) + allow(Resolv::DNS).to receive(:open).and_yield(resolver) + + subject.validate(user) + expect(user.errors).to_not have_received(:add) + end + + it 'adds an error if the TagManager fails to normalize domain' do + double = instance_double(TagManager) + allow(TagManager).to receive(:instance).and_return(double) + allow(double).to receive(:normalize_domain).with('example.com').and_raise(Addressable::URI::InvalidURIError) + + user = instance_double(User, email: 'foo@example.com', errors: instance_double(ActiveModel::Errors, add: nil)) + subject.validate(user) + expect(user.errors).to have_received(:add) + end + + it 'adds an error if the domain email portion is blank' do + user = instance_double(User, email: 'foo@', errors: instance_double(ActiveModel::Errors, add: nil)) + subject.validate(user) + expect(user.errors).to have_received(:add) + end + + it 'adds an error if the email domain name contains empty labels' do + resolver = instance_double(Resolv::DNS) + + allow(resolver).to receive(:getresources).with('example..com', Resolv::DNS::Resource::IN::MX).and_return([]) + allow(resolver).to receive(:getresources).with('example..com', Resolv::DNS::Resource::IN::A).and_return([Resolv::DNS::Resource::IN::A.new('192.0.2.42')]) + allow(resolver).to receive(:getresources).with('example..com', Resolv::DNS::Resource::IN::AAAA).and_return([]) + allow(resolver).to receive(:timeouts=).and_return(nil) + allow(Resolv::DNS).to receive(:open).and_yield(resolver) + + user = instance_double(User, email: 'foo@example..com', sign_up_ip: '1.2.3.4', errors: instance_double(ActiveModel::Errors, add: nil)) + subject.validate(user) + expect(user.errors).to have_received(:add) + end + it 'adds an error if there are no DNS records for the e-mail domain' do - resolver = double + resolver = instance_double(Resolv::DNS) allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([]) allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([]) @@ -42,9 +85,11 @@ describe EmailMxValidator do end it 'adds an error if a MX record does not lead to an IP' do - resolver = double + resolver = instance_double(Resolv::DNS) - allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([double(exchange: 'mail.example.com')]) + allow(resolver).to receive(:getresources) + .with('example.com', Resolv::DNS::Resource::IN::MX) + .and_return([instance_double(Resolv::DNS::Resource::MX, exchange: 'mail.example.com')]) allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([]) allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([]) allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::A).and_return([]) @@ -58,13 +103,15 @@ describe EmailMxValidator do it 'adds an error if the MX record is blacklisted' do EmailDomainBlock.create!(domain: 'mail.example.com') - resolver = double + resolver = instance_double(Resolv::DNS) - allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::MX).and_return([double(exchange: 'mail.example.com')]) + allow(resolver).to receive(:getresources) + .with('example.com', Resolv::DNS::Resource::IN::MX) + .and_return([instance_double(Resolv::DNS::Resource::MX, exchange: 'mail.example.com')]) allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([]) allow(resolver).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([]) - allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::A).and_return([double(address: '2.3.4.5')]) - allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::AAAA).and_return([double(address: 'fd00::2')]) + allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::A).and_return([instance_double(Resolv::DNS::Resource::IN::A, address: '2.3.4.5')]) + allow(resolver).to receive(:getresources).with('mail.example.com', Resolv::DNS::Resource::IN::AAAA).and_return([instance_double(Resolv::DNS::Resource::IN::A, address: 'fd00::2')]) allow(resolver).to receive(:timeouts=).and_return(nil) allow(Resolv::DNS).to receive(:open).and_yield(resolver) diff --git a/spec/validators/follow_limit_validator_spec.rb b/spec/validators/follow_limit_validator_spec.rb index cc8fbb631..86b6511d6 100644 --- a/spec/validators/follow_limit_validator_spec.rb +++ b/spec/validators/follow_limit_validator_spec.rb @@ -12,25 +12,25 @@ RSpec.describe FollowLimitValidator, type: :validator do described_class.new.validate(follow) end - let(:follow) { double(account: account, errors: errors) } - let(:errors) { double(add: nil) } - let(:account) { double(nil?: _nil, local?: local, following_count: 0, followers_count: 0) } + let(:follow) { instance_double(Follow, account: account, errors: errors) } + let(:errors) { instance_double(ActiveModel::Errors, add: nil) } + let(:account) { instance_double(Account, nil?: _nil, local?: local, following_count: 0, followers_count: 0) } let(:_nil) { true } let(:local) { false } - context 'follow.account.nil? || !follow.account.local?' do + context 'with follow.account.nil? || !follow.account.local?' do let(:_nil) { true } it 'not calls errors.add' do - expect(errors).not_to have_received(:add).with(:base, any_args) + expect(errors).to_not have_received(:add).with(:base, any_args) end end - context '!(follow.account.nil? || !follow.account.local?)' do + context 'with !(follow.account.nil? || !follow.account.local?)' do let(:_nil) { false } let(:local) { true } - context 'limit_reached?' do + context 'when limit_reached?' do let(:limit_reached) { true } it 'calls errors.add' do @@ -39,11 +39,11 @@ RSpec.describe FollowLimitValidator, type: :validator do end end - context '!limit_reached?' do + context 'with !limit_reached?' do let(:limit_reached) { false } it 'not calls errors.add' do - expect(errors).not_to have_received(:add).with(:base, any_args) + expect(errors).to_not have_received(:add).with(:base, any_args) end end end diff --git a/spec/validators/language_validator_spec.rb b/spec/validators/language_validator_spec.rb new file mode 100644 index 000000000..cb693dcd8 --- /dev/null +++ b/spec/validators/language_validator_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe LanguageValidator do + let(:record_class) do + Class.new do + include ActiveModel::Validations + attr_accessor :locale + + validates :locale, language: true + end + end + let(:record) { record_class.new } + + describe '#validate_each' do + context 'with a nil value' do + it 'does not add errors' do + record.locale = nil + + expect(record).to be_valid + expect(record.errors).to be_empty + end + end + + context 'with an array of values' do + it 'does not add errors with array of existing locales' do + record.locale = %w(en fr) + + expect(record).to be_valid + expect(record.errors).to be_empty + end + + it 'adds errors with array having some non-existing locales' do + record.locale = %w(en fr missing) + + expect(record).to_not be_valid + expect(record.errors.first.attribute).to eq(:locale) + expect(record.errors.first.type).to eq(:invalid) + end + end + + context 'with a locale string' do + it 'does not add errors when string is an existing locale' do + record.locale = 'en' + + expect(record).to be_valid + expect(record.errors).to be_empty + end + + it 'adds errors when string is non-existing locale' do + record.locale = 'missing' + + expect(record).to_not be_valid + expect(record.errors.first.attribute).to eq(:locale) + expect(record.errors.first.type).to eq(:invalid) + end + end + end +end diff --git a/spec/validators/note_length_validator_spec.rb b/spec/validators/note_length_validator_spec.rb index 6e9b4e132..66fccad3e 100644 --- a/spec/validators/note_length_validator_spec.rb +++ b/spec/validators/note_length_validator_spec.rb @@ -3,31 +3,37 @@ require 'rails_helper' describe NoteLengthValidator do - subject { NoteLengthValidator.new(attributes: { note: true }, maximum: 500) } + subject { described_class.new(attributes: { note: true }, maximum: 500) } describe '#validate' do it 'adds an error when text is over 500 characters' do text = 'a' * 520 - account = double(note: text, errors: double(add: nil)) + account = instance_double(Account, note: text, errors: activemodel_errors) subject.validate_each(account, 'note', text) expect(account.errors).to have_received(:add) end it 'counts URLs as 23 characters flat' do - text = ('a' * 476) + " http://#{'b' * 30}.com/example" - account = double(note: text, errors: double(add: nil)) + text = ('a' * 476) + " http://#{'b' * 30}.com/example" + account = instance_double(Account, note: text, errors: activemodel_errors) subject.validate_each(account, 'note', text) expect(account.errors).to_not have_received(:add) end it 'does not count non-autolinkable URLs as 23 characters flat' do - text = ('a' * 476) + "http://#{'b' * 30}.com/example" - account = double(note: text, errors: double(add: nil)) + text = ('a' * 476) + "http://#{'b' * 30}.com/example" + account = instance_double(Account, note: text, errors: activemodel_errors) subject.validate_each(account, 'note', text) expect(account.errors).to have_received(:add) end + + private + + def activemodel_errors + instance_double(ActiveModel::Errors, add: nil) + end end end diff --git a/spec/validators/poll_validator_spec.rb b/spec/validators/poll_validator_spec.rb index 941b83401..95feb043d 100644 --- a/spec/validators/poll_validator_spec.rb +++ b/spec/validators/poll_validator_spec.rb @@ -9,19 +9,20 @@ RSpec.describe PollValidator, type: :validator do end let(:validator) { described_class.new } - let(:poll) { double(options: options, expires_at: expires_at, errors: errors) } - let(:errors) { double(add: nil) } + let(:poll) { instance_double(Poll, options: options, expires_at: expires_at, errors: errors) } + let(:errors) { instance_double(ActiveModel::Errors, add: nil) } let(:options) { %w(foo bar) } let(:expires_at) { 1.day.from_now } it 'have no errors' do - expect(errors).not_to have_received(:add) + expect(errors).to_not have_received(:add) end - context 'expires just 5 min ago' do + context 'when expires is just 5 min ago' do let(:expires_at) { 5.minutes.from_now } + it 'not calls errors add' do - expect(errors).not_to have_received(:add) + expect(errors).to_not have_received(:add) end end end diff --git a/spec/validators/status_length_validator_spec.rb b/spec/validators/status_length_validator_spec.rb index db9c728a8..98ea15e03 100644 --- a/spec/validators/status_length_validator_spec.rb +++ b/spec/validators/status_length_validator_spec.rb @@ -5,38 +5,38 @@ require 'rails_helper' describe StatusLengthValidator do describe '#validate' do it 'does not add errors onto remote statuses' do - status = double(local?: false) + status = instance_double(Status, local?: false) subject.validate(status) - expect(status).not_to receive(:errors) + expect(status).to_not receive(:errors) end it 'does not add errors onto local reblogs' do - status = double(local?: false, reblog?: true) + status = instance_double(Status, local?: false, reblog?: true) subject.validate(status) - expect(status).not_to receive(:errors) + expect(status).to_not receive(:errors) end it 'adds an error when content warning is over 500 characters' do - status = double(spoiler_text: 'a' * 520, text: '', errors: double(add: nil), local?: true, reblog?: false) + status = instance_double(Status, spoiler_text: 'a' * 520, text: '', errors: activemodel_errors, local?: true, reblog?: false) subject.validate(status) expect(status.errors).to have_received(:add) end it 'adds an error when text is over 500 characters' do - status = double(spoiler_text: '', text: 'a' * 520, errors: double(add: nil), local?: true, reblog?: false) + status = instance_double(Status, spoiler_text: '', text: 'a' * 520, errors: activemodel_errors, local?: true, reblog?: false) subject.validate(status) expect(status.errors).to have_received(:add) end it 'adds an error when text and content warning are over 500 characters total' do - status = double(spoiler_text: 'a' * 250, text: 'b' * 251, errors: double(add: nil), local?: true, reblog?: false) + status = instance_double(Status, spoiler_text: 'a' * 250, text: 'b' * 251, errors: activemodel_errors, local?: true, reblog?: false) subject.validate(status) expect(status.errors).to have_received(:add) end it 'counts URLs as 23 characters flat' do text = ('a' * 476) + " http://#{'b' * 30}.com/example" - status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false) + status = instance_double(Status, spoiler_text: '', text: text, errors: activemodel_errors, local?: true, reblog?: false) subject.validate(status) expect(status.errors).to_not have_received(:add) @@ -44,7 +44,7 @@ describe StatusLengthValidator do it 'does not count non-autolinkable URLs as 23 characters flat' do text = ('a' * 476) + "http://#{'b' * 30}.com/example" - status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false) + status = instance_double(Status, spoiler_text: '', text: text, errors: activemodel_errors, local?: true, reblog?: false) subject.validate(status) expect(status.errors).to have_received(:add) @@ -52,14 +52,14 @@ describe StatusLengthValidator do it 'does not count overly long URLs as 23 characters flat' do text = "http://example.com/valid?#{'#foo?' * 1000}" - status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false) + status = instance_double(Status, spoiler_text: '', text: text, errors: activemodel_errors, local?: true, reblog?: false) subject.validate(status) expect(status.errors).to have_received(:add) end it 'counts only the front part of remote usernames' do text = ('a' * 475) + " @alice@#{'b' * 30}.com" - status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false) + status = instance_double(Status, spoiler_text: '', text: text, errors: activemodel_errors, local?: true, reblog?: false) subject.validate(status) expect(status.errors).to_not have_received(:add) @@ -67,10 +67,16 @@ describe StatusLengthValidator do it 'does count both parts of remote usernames for overly long domains' do text = "@alice@#{'b' * 500}.com" - status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false) + status = instance_double(Status, spoiler_text: '', text: text, errors: activemodel_errors, local?: true, reblog?: false) subject.validate(status) expect(status.errors).to have_received(:add) end end + + private + + def activemodel_errors + instance_double(ActiveModel::Errors, add: nil) + end end diff --git a/spec/validators/status_pin_validator_spec.rb b/spec/validators/status_pin_validator_spec.rb index d5bd0d1b8..e8f8a4543 100644 --- a/spec/validators/status_pin_validator_spec.rb +++ b/spec/validators/status_pin_validator_spec.rb @@ -8,11 +8,11 @@ RSpec.describe StatusPinValidator, type: :validator do subject.validate(pin) end - let(:pin) { double(account: account, errors: errors, status: status, account_id: pin_account_id) } - let(:status) { double(reblog?: reblog, account_id: status_account_id, visibility: visibility, direct_visibility?: visibility == 'direct') } - let(:account) { double(status_pins: status_pins, local?: local) } - let(:status_pins) { double(count: count) } - let(:errors) { double(add: nil) } + let(:pin) { instance_double(StatusPin, account: account, errors: errors, status: status, account_id: pin_account_id) } + let(:status) { instance_double(Status, reblog?: reblog, account_id: status_account_id, visibility: visibility, direct_visibility?: visibility == 'direct') } + let(:account) { instance_double(Account, status_pins: status_pins, local?: local) } + let(:status_pins) { instance_double(Array, count: count) } + let(:errors) { instance_double(ActiveModel::Errors, add: nil) } let(:pin_account_id) { 1 } let(:status_account_id) { 1 } let(:visibility) { 'public' } @@ -20,7 +20,7 @@ RSpec.describe StatusPinValidator, type: :validator do let(:reblog) { false } let(:count) { 0 } - context 'pin.status.reblog?' do + context 'when pin.status.reblog?' do let(:reblog) { true } it 'calls errors.add' do @@ -28,7 +28,7 @@ RSpec.describe StatusPinValidator, type: :validator do end end - context 'pin.account_id != pin.status.account_id' do + context 'when pin.account_id != pin.status.account_id' do let(:pin_account_id) { 1 } let(:status_account_id) { 2 } @@ -37,7 +37,7 @@ RSpec.describe StatusPinValidator, type: :validator do end end - context 'if pin.status.direct_visibility?' do + context 'when pin.status.direct_visibility?' do let(:visibility) { 'direct' } it 'calls errors.add' do @@ -45,7 +45,7 @@ RSpec.describe StatusPinValidator, type: :validator do end end - context 'pin.account.status_pins.count > 4 && pin.account.local?' do + context 'when pin.account.status_pins.count > 4 && pin.account.local?' do let(:count) { 5 } let(:local) { true } diff --git a/spec/validators/unique_username_validator_spec.rb b/spec/validators/unique_username_validator_spec.rb index 6867cbc6c..0d172c840 100644 --- a/spec/validators/unique_username_validator_spec.rb +++ b/spec/validators/unique_username_validator_spec.rb @@ -6,7 +6,7 @@ describe UniqueUsernameValidator do describe '#validate' do context 'when local account' do it 'does not add errors if username is nil' do - account = double(username: nil, domain: nil, persisted?: false, errors: double(add: nil)) + account = instance_double(Account, username: nil, domain: nil, persisted?: false, errors: activemodel_errors) subject.validate(account) expect(account.errors).to_not have_received(:add) end @@ -18,14 +18,14 @@ describe UniqueUsernameValidator do it 'adds an error when the username is already used with ignoring cases' do Fabricate(:account, username: 'ABCdef') - account = double(username: 'abcDEF', domain: nil, persisted?: false, errors: double(add: nil)) + account = instance_double(Account, username: 'abcDEF', domain: nil, persisted?: false, errors: activemodel_errors) subject.validate(account) expect(account.errors).to have_received(:add) end it 'does not add errors when same username remote account exists' do Fabricate(:account, username: 'abcdef', domain: 'example.com') - account = double(username: 'abcdef', domain: nil, persisted?: false, errors: double(add: nil)) + account = instance_double(Account, username: 'abcdef', domain: nil, persisted?: false, errors: activemodel_errors) subject.validate(account) expect(account.errors).to_not have_received(:add) end @@ -34,7 +34,7 @@ describe UniqueUsernameValidator do context 'when remote account' do it 'does not add errors if username is nil' do - account = double(username: nil, domain: 'example.com', persisted?: false, errors: double(add: nil)) + account = instance_double(Account, username: nil, domain: 'example.com', persisted?: false, errors: activemodel_errors) subject.validate(account) expect(account.errors).to_not have_received(:add) end @@ -46,23 +46,29 @@ describe UniqueUsernameValidator do it 'adds an error when the username is already used with ignoring cases' do Fabricate(:account, username: 'ABCdef', domain: 'example.com') - account = double(username: 'abcDEF', domain: 'example.com', persisted?: false, errors: double(add: nil)) + account = instance_double(Account, username: 'abcDEF', domain: 'example.com', persisted?: false, errors: activemodel_errors) subject.validate(account) expect(account.errors).to have_received(:add) end it 'adds an error when the domain is already used with ignoring cases' do Fabricate(:account, username: 'ABCdef', domain: 'example.com') - account = double(username: 'ABCdef', domain: 'EXAMPLE.COM', persisted?: false, errors: double(add: nil)) + account = instance_double(Account, username: 'ABCdef', domain: 'EXAMPLE.COM', persisted?: false, errors: activemodel_errors) subject.validate(account) expect(account.errors).to have_received(:add) end it 'does not add errors when account with the same username and another domain exists' do Fabricate(:account, username: 'abcdef', domain: 'example.com') - account = double(username: 'abcdef', domain: 'example2.com', persisted?: false, errors: double(add: nil)) + account = instance_double(Account, username: 'abcdef', domain: 'example2.com', persisted?: false, errors: activemodel_errors) subject.validate(account) expect(account.errors).to_not have_received(:add) end end + + private + + def activemodel_errors + instance_double(ActiveModel::Errors, add: nil) + end end diff --git a/spec/validators/unreserved_username_validator_spec.rb b/spec/validators/unreserved_username_validator_spec.rb index 746b3866c..6f353eeaf 100644 --- a/spec/validators/unreserved_username_validator_spec.rb +++ b/spec/validators/unreserved_username_validator_spec.rb @@ -10,21 +10,21 @@ RSpec.describe UnreservedUsernameValidator, type: :validator do end let(:validator) { described_class.new } - let(:account) { double(username: username, errors: errors) } - let(:errors ) { double(add: nil) } + let(:account) { instance_double(Account, username: username, errors: errors) } + let(:errors) { instance_double(ActiveModel::Errors, add: nil) } - context '@username.blank?' do - let(:username) { nil } + context 'when @username is blank?' do + let(:username) { nil } it 'not calls errors.add' do - expect(errors).not_to have_received(:add).with(:username, any_args) + expect(errors).to_not have_received(:add).with(:username, any_args) end end - context '!@username.blank?' do - let(:username) { 'f' } + context 'when @username is not blank?' do + let(:username) { 'f' } - context 'reserved_username?' do + context 'with reserved_username?' do let(:reserved_username) { true } it 'calls errors.add' do @@ -32,11 +32,11 @@ RSpec.describe UnreservedUsernameValidator, type: :validator do end end - context '!reserved_username?' do + context 'when username is not reserved' do let(:reserved_username) { false } it 'not calls errors.add' do - expect(errors).not_to have_received(:add).with(:username, any_args) + expect(errors).to_not have_received(:add).with(:username, any_args) end end end diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb index 85eadeb63..4f32b7b39 100644 --- a/spec/validators/url_validator_spec.rb +++ b/spec/validators/url_validator_spec.rb @@ -2,32 +2,64 @@ require 'rails_helper' -RSpec.describe URLValidator, type: :validator do - describe '#validate_each' do - before do - allow(validator).to receive(:compliant?).with(value) { compliant } - validator.validate_each(record, attribute, value) +describe URLValidator do + let(:record_class) do + Class.new do + include ActiveModel::Validations + attr_accessor :profile + + validates :profile, url: true end + end + let(:record) { record_class.new } - let(:validator) { described_class.new(attributes: [attribute]) } - let(:record) { double(errors: errors) } - let(:errors) { double(add: nil) } - let(:value) { '' } - let(:attribute) { :foo } + describe '#validate_each' do + context 'with a nil value' do + it 'adds errors' do + record.profile = nil - context 'unless compliant?' do - let(:compliant) { false } - - it 'calls errors.add' do - expect(errors).to have_received(:add).with(attribute, :invalid) + expect(record).to_not be_valid + expect(record.errors.first.attribute).to eq(:profile) + expect(record.errors.first.type).to eq(:invalid) end end - context 'if compliant?' do - let(:compliant) { true } + context 'with an invalid url scheme' do + it 'adds errors' do + record.profile = 'ftp://example.com/page' - it 'not calls errors.add' do - expect(errors).not_to have_received(:add).with(attribute, any_args) + expect(record).to_not be_valid + expect(record.errors.first.attribute).to eq(:profile) + expect(record.errors.first.type).to eq(:invalid) + end + end + + context 'without a hostname' do + it 'adds errors' do + record.profile = 'https:///page' + + expect(record).to_not be_valid + expect(record.errors.first.attribute).to eq(:profile) + expect(record.errors.first.type).to eq(:invalid) + end + end + + context 'with an unparseable value' do + it 'adds errors' do + record.profile = 'https://host:port/page' # non-numeric port string causes invalid uri error + + expect(record).to_not be_valid + expect(record.errors.first.attribute).to eq(:profile) + expect(record.errors.first.type).to eq(:invalid) + end + end + + context 'with a valid url' do + it 'does not add errors' do + record.profile = 'https://example.com/page' + + expect(record).to be_valid + expect(record.errors).to be_empty end end end diff --git a/spec/views/statuses/show.html.haml_spec.rb b/spec/views/statuses/show.html.haml_spec.rb index eeea2f698..354f9d3e6 100644 --- a/spec/views/statuses/show.html.haml_spec.rb +++ b/spec/views/statuses/show.html.haml_spec.rb @@ -4,15 +4,9 @@ require 'rails_helper' describe 'statuses/show.html.haml', without_verify_partial_doubles: true do before do - double(:api_oembed_url => '') - allow(view).to receive(:show_landing_strip?).and_return(true) - allow(view).to receive(:site_title).and_return('example site') - allow(view).to receive(:site_hostname).and_return('example.com') - allow(view).to receive(:full_asset_url).and_return('//asset.host/image.svg') + allow(view).to receive_messages(api_oembed_url: '', show_landing_strip?: true, site_title: 'example site', site_hostname: 'example.com', full_asset_url: '//asset.host/image.svg', current_account: nil, single_user_mode?: false) allow(view).to receive(:local_time) allow(view).to receive(:local_time_ago) - allow(view).to receive(:current_account).and_return(nil) - allow(view).to receive(:single_user_mode?).and_return(false) assign(:instance_presenter, InstancePresenter.new) end @@ -29,10 +23,10 @@ describe 'statuses/show.html.haml', without_verify_partial_doubles: true do header_tags = view.content_for(:header_tags) - expect(header_tags).to match(%r{}) - expect(header_tags).to match(%r{}) - expect(header_tags).to match(%r{}) - expect(header_tags).to match(%r{}) + expect(header_tags).to match(//) + expect(header_tags).to match(//) + expect(header_tags).to match(//) + expect(header_tags).to match(%r{}) end it 'has twitter player tag' do @@ -48,7 +42,7 @@ describe 'statuses/show.html.haml', without_verify_partial_doubles: true do header_tags = view.content_for(:header_tags) - expect(header_tags).to match(%r{}) - expect(header_tags).to match(%r{}) + expect(header_tags).to match(%r{}) + expect(header_tags).to match(//) end end diff --git a/spec/workers/activitypub/distribute_poll_update_worker_spec.rb b/spec/workers/activitypub/distribute_poll_update_worker_spec.rb index d68a695b7..0bdbf6390 100644 --- a/spec/workers/activitypub/distribute_poll_update_worker_spec.rb +++ b/spec/workers/activitypub/distribute_poll_update_worker_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'rails_helper' describe ActivityPub::DistributePollUpdateWorker do subject { described_class.new } let(:account) { Fabricate(:account) } - let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com') } + let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com', domain: 'example.com') } let(:poll) { Fabricate(:poll, account: account) } let!(:status) { Fabricate(:status, account: account, poll: poll) } diff --git a/spec/workers/activitypub/distribution_worker_spec.rb b/spec/workers/activitypub/distribution_worker_spec.rb index 3a5900d9b..d8803f6b8 100644 --- a/spec/workers/activitypub/distribution_worker_spec.rb +++ b/spec/workers/activitypub/distribution_worker_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'rails_helper' describe ActivityPub::DistributionWorker do subject { described_class.new } let(:status) { Fabricate(:status) } - let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com') } + let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com', domain: 'example.com') } describe '#perform' do before do @@ -34,7 +36,7 @@ describe ActivityPub::DistributionWorker do end context 'with direct status' do - let(:mentioned_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/inbox')} + let(:mentioned_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/inbox', domain: 'foo.bar') } before do status.update(visibility: :direct) diff --git a/spec/workers/activitypub/fetch_replies_worker_spec.rb b/spec/workers/activitypub/fetch_replies_worker_spec.rb index 64cfcd8cb..2d080e286 100644 --- a/spec/workers/activitypub/fetch_replies_worker_spec.rb +++ b/spec/workers/activitypub/fetch_replies_worker_spec.rb @@ -5,7 +5,7 @@ require 'rails_helper' describe ActivityPub::FetchRepliesWorker do subject { described_class.new } - let(:account) { Fabricate(:account, uri: 'https://example.com/user/1') } + let(:account) { Fabricate(:account, domain: 'example.com') } let(:status) { Fabricate(:status, account: account) } let(:payload) do diff --git a/spec/workers/activitypub/move_distribution_worker_spec.rb b/spec/workers/activitypub/move_distribution_worker_spec.rb index af8c44cc0..b8601f78c 100644 --- a/spec/workers/activitypub/move_distribution_worker_spec.rb +++ b/spec/workers/activitypub/move_distribution_worker_spec.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + require 'rails_helper' describe ActivityPub::MoveDistributionWorker do subject { described_class.new } - let(:migration) { Fabricate(:account_migration) } - let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com') } - let(:blocker) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example2.com') } + let(:migration) { Fabricate(:account_migration) } + let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com', domain: 'example.com') } + let(:blocker) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example2.com', domain: 'example2.com') } describe '#perform' do before do @@ -15,9 +17,9 @@ describe ActivityPub::MoveDistributionWorker do it 'delivers to followers and known blockers' do expect_push_bulk_to_match(ActivityPub::DeliveryWorker, [ - [kind_of(String), migration.account.id, 'http://example.com'], - [kind_of(String), migration.account.id, 'http://example2.com'] - ]) + [kind_of(String), migration.account.id, 'http://example.com'], + [kind_of(String), migration.account.id, 'http://example2.com'], + ]) subject.perform(migration.id) end end diff --git a/spec/workers/activitypub/processing_worker_spec.rb b/spec/workers/activitypub/processing_worker_spec.rb index b42c0bdbc..66d1cf489 100644 --- a/spec/workers/activitypub/processing_worker_spec.rb +++ b/spec/workers/activitypub/processing_worker_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rails_helper' describe ActivityPub::ProcessingWorker do @@ -7,7 +9,8 @@ describe ActivityPub::ProcessingWorker do describe '#perform' do it 'delegates to ActivityPub::ProcessCollectionService' do - allow(ActivityPub::ProcessCollectionService).to receive(:new).and_return(double(:service, call: nil)) + allow(ActivityPub::ProcessCollectionService).to receive(:new) + .and_return(instance_double(ActivityPub::ProcessCollectionService, call: nil)) subject.perform(account.id, '') expect(ActivityPub::ProcessCollectionService).to have_received(:new) end diff --git a/spec/workers/activitypub/status_update_distribution_worker_spec.rb b/spec/workers/activitypub/status_update_distribution_worker_spec.rb index c014c6790..c500bac95 100644 --- a/spec/workers/activitypub/status_update_distribution_worker_spec.rb +++ b/spec/workers/activitypub/status_update_distribution_worker_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'rails_helper' describe ActivityPub::StatusUpdateDistributionWorker do subject { described_class.new } let(:status) { Fabricate(:status, text: 'foo') } - let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com') } + let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com', domain: 'example.com') } describe '#perform' do before do diff --git a/spec/workers/activitypub/update_distribution_worker_spec.rb b/spec/workers/activitypub/update_distribution_worker_spec.rb index 0e057fd0b..d0eeda43b 100644 --- a/spec/workers/activitypub/update_distribution_worker_spec.rb +++ b/spec/workers/activitypub/update_distribution_worker_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'rails_helper' describe ActivityPub::UpdateDistributionWorker do subject { described_class.new } let(:account) { Fabricate(:account) } - let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com') } + let(:follower) { Fabricate(:account, protocol: :activitypub, inbox_url: 'http://example.com', domain: 'example.com') } describe '#perform' do before do diff --git a/spec/workers/add_to_public_statuses_index_worker_spec.rb b/spec/workers/add_to_public_statuses_index_worker_spec.rb new file mode 100644 index 000000000..fa1507224 --- /dev/null +++ b/spec/workers/add_to_public_statuses_index_worker_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe AddToPublicStatusesIndexWorker do + describe '#perform' do + let(:account) { Fabricate(:account, indexable: indexable) } + let(:account_id) { account.id } + + before do + allow(Account).to receive(:find).with(account_id).and_return(account) unless account.nil? + allow(account).to receive(:add_to_public_statuses_index!) unless account.nil? + end + + context 'when account is indexable' do + let(:indexable) { true } + + it 'adds the account to the public statuses index' do + subject.perform(account_id) + expect(account).to have_received(:add_to_public_statuses_index!) + end + end + + context 'when account is not indexable' do + let(:indexable) { false } + + it 'does not add the account to public statuses index' do + subject.perform(account_id) + expect(account).to_not have_received(:add_to_public_statuses_index!) + end + end + + context 'when account does not exist' do + let(:account) { nil } + let(:account_id) { 999 } + + it 'does not raise an error' do + expect { subject.perform(account_id) }.to_not raise_error + end + end + end +end diff --git a/spec/workers/admin/account_deletion_worker_spec.rb b/spec/workers/admin/account_deletion_worker_spec.rb new file mode 100644 index 000000000..631cab664 --- /dev/null +++ b/spec/workers/admin/account_deletion_worker_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::AccountDeletionWorker do + let(:worker) { described_class.new } + + describe 'perform' do + let(:account) { Fabricate(:account) } + let(:service) { instance_double(DeleteAccountService, call: true) } + + it 'calls delete account service' do + allow(DeleteAccountService).to receive(:new).and_return(service) + worker.perform(account.id) + + expect(service).to have_received(:call).with(account, { reserve_email: true, reserve_username: true }) + end + end +end diff --git a/spec/workers/admin/domain_purge_worker_spec.rb b/spec/workers/admin/domain_purge_worker_spec.rb index b67c58b23..861fd71a7 100644 --- a/spec/workers/admin/domain_purge_worker_spec.rb +++ b/spec/workers/admin/domain_purge_worker_spec.rb @@ -7,7 +7,7 @@ describe Admin::DomainPurgeWorker do describe 'perform' do it 'calls domain purge service for relevant domain block' do - service = double(call: nil) + service = instance_double(PurgeDomainService, call: nil) allow(PurgeDomainService).to receive(:new).and_return(service) result = subject.perform('example.com') diff --git a/spec/workers/bulk_import_worker_spec.rb b/spec/workers/bulk_import_worker_spec.rb new file mode 100644 index 000000000..91f51fbb4 --- /dev/null +++ b/spec/workers/bulk_import_worker_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe BulkImportWorker do + subject { described_class.new } + + let(:import) { Fabricate(:bulk_import, state: :scheduled) } + + describe '#perform' do + let(:service_double) { instance_double(BulkImportService, call: nil) } + + before do + allow(BulkImportService).to receive(:new).and_return(service_double) + end + + it 'changes the import\'s state as appropriate' do + expect { subject.perform(import.id) }.to change { import.reload.state.to_sym }.from(:scheduled).to(:in_progress) + end + + it 'calls BulkImportService' do + subject.perform(import.id) + expect(service_double).to have_received(:call).with(import) + end + end +end diff --git a/spec/workers/cache_buster_worker_spec.rb b/spec/workers/cache_buster_worker_spec.rb new file mode 100644 index 000000000..adeb287fa --- /dev/null +++ b/spec/workers/cache_buster_worker_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe CacheBusterWorker do + let(:worker) { described_class.new } + + describe 'perform' do + let(:path) { 'https://example.com' } + let(:service) { instance_double(CacheBuster, bust: true) } + + it 'calls the cache buster' do + allow(CacheBuster).to receive(:new).and_return(service) + worker.perform(path) + + expect(service).to have_received(:bust).with(path) + end + end +end diff --git a/spec/workers/domain_block_worker_spec.rb b/spec/workers/domain_block_worker_spec.rb index bd8fc4a62..33c3ca009 100644 --- a/spec/workers/domain_block_worker_spec.rb +++ b/spec/workers/domain_block_worker_spec.rb @@ -9,7 +9,7 @@ describe DomainBlockWorker do let(:domain_block) { Fabricate(:domain_block) } it 'calls domain block service for relevant domain block' do - service = double(call: nil) + service = instance_double(BlockDomainService, call: nil) allow(BlockDomainService).to receive(:new).and_return(service) result = subject.perform(domain_block.id) @@ -20,7 +20,7 @@ describe DomainBlockWorker do it 'returns true for non-existent domain block' do result = subject.perform('aaa') - expect(result).to eq(true) + expect(result).to be(true) end end end diff --git a/spec/workers/domain_clear_media_worker_spec.rb b/spec/workers/domain_clear_media_worker_spec.rb index 36251b1ec..21f8f87b2 100644 --- a/spec/workers/domain_clear_media_worker_spec.rb +++ b/spec/workers/domain_clear_media_worker_spec.rb @@ -9,7 +9,7 @@ describe DomainClearMediaWorker do let(:domain_block) { Fabricate(:domain_block, severity: :silence, reject_media: true) } it 'calls domain clear media service for relevant domain block' do - service = double(call: nil) + service = instance_double(ClearDomainMediaService, call: nil) allow(ClearDomainMediaService).to receive(:new).and_return(service) result = subject.perform(domain_block.id) @@ -20,7 +20,7 @@ describe DomainClearMediaWorker do it 'returns true for non-existent domain block' do result = subject.perform('aaa') - expect(result).to eq(true) + expect(result).to be(true) end end end diff --git a/spec/workers/feed_insert_worker_spec.rb b/spec/workers/feed_insert_worker_spec.rb index fb34970fc..97c73c599 100644 --- a/spec/workers/feed_insert_worker_spec.rb +++ b/spec/workers/feed_insert_worker_spec.rb @@ -11,36 +11,36 @@ describe FeedInsertWorker do context 'when there are no records' do it 'skips push with missing status' do - instance = double(push_to_home: nil) + instance = instance_double(FeedManager, push_to_home: nil) allow(FeedManager).to receive(:instance).and_return(instance) result = subject.perform(nil, follower.id) - expect(result).to eq true - expect(instance).not_to have_received(:push_to_home) + expect(result).to be true + expect(instance).to_not have_received(:push_to_home) end it 'skips push with missing account' do - instance = double(push_to_home: nil) + instance = instance_double(FeedManager, push_to_home: nil) allow(FeedManager).to receive(:instance).and_return(instance) result = subject.perform(status.id, nil) - expect(result).to eq true - expect(instance).not_to have_received(:push_to_home) + expect(result).to be true + expect(instance).to_not have_received(:push_to_home) end end context 'when there are real records' do it 'skips the push when there is a filter' do - instance = double(push_to_home: nil, filter?: true) + instance = instance_double(FeedManager, push_to_home: nil, filter?: true) allow(FeedManager).to receive(:instance).and_return(instance) result = subject.perform(status.id, follower.id) expect(result).to be_nil - expect(instance).not_to have_received(:push_to_home) + expect(instance).to_not have_received(:push_to_home) end it 'pushes the status onto the home timeline without filter' do - instance = double(push_to_home: nil, filter?: false) + instance = instance_double(FeedManager, push_to_home: nil, filter?: false) allow(FeedManager).to receive(:instance).and_return(instance) result = subject.perform(status.id, follower.id) diff --git a/spec/workers/import/row_worker_spec.rb b/spec/workers/import/row_worker_spec.rb new file mode 100644 index 000000000..0a71a838f --- /dev/null +++ b/spec/workers/import/row_worker_spec.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Import::RowWorker do + subject { described_class.new } + + let(:row) { Fabricate(:bulk_import_row, bulk_import: import) } + + describe '#perform' do + before do + allow(BulkImportRowService).to receive(:new).and_return(service_double) + end + + shared_examples 'clean failure' do + let(:service_double) { instance_double(BulkImportRowService, call: false) } + + it 'calls BulkImportRowService' do + subject.perform(row.id) + expect(service_double).to have_received(:call).with(row) + end + + it 'increases the number of processed items' do + expect { subject.perform(row.id) }.to(change { import.reload.processed_items }.by(+1)) + end + + it 'does not increase the number of imported items' do + expect { subject.perform(row.id) }.to_not(change { import.reload.imported_items }) + end + + it 'does not delete the row' do + subject.perform(row.id) + expect(BulkImportRow.exists?(row.id)).to be true + end + end + + shared_examples 'unclean failure' do + let(:service_double) { instance_double(BulkImportRowService) } + + before do + allow(service_double).to receive(:call) do + raise 'dummy error' + end + end + + it 'raises an error and does not change processed items count' do + expect { subject.perform(row.id) }.to raise_error(StandardError, 'dummy error').and(not_change { import.reload.processed_items }) + end + + it 'does not delete the row' do + expect { subject.perform(row.id) }.to raise_error(StandardError, 'dummy error').and(not_change { BulkImportRow.exists?(row.id) }) + end + end + + shared_examples 'clean success' do + let(:service_double) { instance_double(BulkImportRowService, call: true) } + + it 'calls BulkImportRowService' do + subject.perform(row.id) + expect(service_double).to have_received(:call).with(row) + end + + it 'increases the number of processed items' do + expect { subject.perform(row.id) }.to(change { import.reload.processed_items }.by(+1)) + end + + it 'increases the number of imported items' do + expect { subject.perform(row.id) }.to(change { import.reload.imported_items }.by(+1)) + end + + it 'deletes the row' do + expect { subject.perform(row.id) }.to change { BulkImportRow.exists?(row.id) }.from(true).to(false) + end + end + + context 'when there are multiple rows to process' do + let(:import) { Fabricate(:bulk_import, total_items: 2, processed_items: 0, imported_items: 0, state: :in_progress) } + + context 'with a clean failure' do + include_examples 'clean failure' + + it 'does not mark the import as finished' do + expect { subject.perform(row.id) }.to_not(change { import.reload.state.to_sym }) + end + end + + context 'with an unclean failure' do + include_examples 'unclean failure' + + it 'does not mark the import as finished' do + expect { subject.perform(row.id) }.to raise_error(StandardError).and(not_change { import.reload.state.to_sym }) + end + end + + context 'with a clean success' do + include_examples 'clean success' + + it 'does not mark the import as finished' do + expect { subject.perform(row.id) }.to_not(change { import.reload.state.to_sym }) + end + end + end + + context 'when this is the last row to process' do + let(:import) { Fabricate(:bulk_import, total_items: 2, processed_items: 1, imported_items: 0, state: :in_progress) } + + context 'with a clean failure' do + include_examples 'clean failure' + + it 'marks the import as finished' do + expect { subject.perform(row.id) }.to change { import.reload.state.to_sym }.from(:in_progress).to(:finished) + end + end + + # NOTE: sidekiq retry logic may be a bit too difficult to test, so leaving this blind spot for now + it_behaves_like 'unclean failure' + + context 'with a clean success' do + include_examples 'clean success' + + it 'marks the import as finished' do + expect { subject.perform(row.id) }.to change { import.reload.state.to_sym }.from(:in_progress).to(:finished) + end + end + end + end +end diff --git a/spec/workers/move_worker_spec.rb b/spec/workers/move_worker_spec.rb index 3ca6aaf4d..7577f6e89 100644 --- a/spec/workers/move_worker_spec.rb +++ b/spec/workers/move_worker_spec.rb @@ -3,24 +3,30 @@ require 'rails_helper' describe MoveWorker do - let(:local_follower) { Fabricate(:account) } + subject { described_class.new } + + let(:local_follower) { Fabricate(:account, domain: nil) } let(:blocking_account) { Fabricate(:account) } let(:muting_account) { Fabricate(:account) } - let(:source_account) { Fabricate(:account, protocol: :activitypub, domain: 'example.com') } - let(:target_account) { Fabricate(:account, protocol: :activitypub, domain: 'example.com') } + let(:source_account) { Fabricate(:account, protocol: :activitypub, domain: 'example.com', uri: 'https://example.org/a', inbox_url: 'https://example.org/a/inbox') } + let(:target_account) { Fabricate(:account, protocol: :activitypub, domain: 'example.com', uri: 'https://example.org/b', inbox_url: 'https://example.org/b/inbox') } let(:local_user) { Fabricate(:user) } let(:comment) { 'old note prior to move' } let!(:account_note) { Fabricate(:account_note, account: local_user.account, target_account: source_account, comment: comment) } + let(:list) { Fabricate(:list, account: local_follower) } - let(:block_service) { double } - - subject { described_class.new } + let(:block_service) { instance_double(BlockService) } before do + stub_request(:post, 'https://example.org/a/inbox').to_return(status: 200) + stub_request(:post, 'https://example.org/b/inbox').to_return(status: 200) + local_follower.follow!(source_account) blocking_account.block!(source_account) muting_account.mute!(source_account) + list.accounts << source_account + allow(BlockService).to receive(:new).and_return(block_service) allow(block_service).to receive(:call) end @@ -86,55 +92,100 @@ describe MoveWorker do end end - context 'both accounts are distant' do - describe 'perform' do - it 'calls UnfollowFollowWorker' do - expect_push_bulk_to_match(UnfollowFollowWorker, [[local_follower.id, source_account.id, target_account.id, false]]) - subject.perform(source_account.id, target_account.id) + shared_examples 'lists handling' do + it 'puts the new account on the list' do + subject.perform(source_account.id, target_account.id) + expect(list.accounts.include?(target_account)).to be true + end + + it 'does not create invalid list memberships' do + subject.perform(source_account.id, target_account.id) + expect(ListAccount.all).to all be_valid + end + end + + shared_examples 'common tests' do + include_examples 'user note handling' + include_examples 'block and mute handling' + include_examples 'followers count handling' + include_examples 'lists handling' + + context 'when a local user already follows both source and target' do + before do + local_follower.request_follow!(target_account) end include_examples 'user note handling' include_examples 'block and mute handling' include_examples 'followers count handling' + include_examples 'lists handling' + + context 'when the local user already has the target in a list' do + before do + list.accounts << target_account + end + + include_examples 'lists handling' + end end - end - context 'target account is local' do - let(:target_account) { Fabricate(:account) } - - describe 'perform' do - it 'calls UnfollowFollowWorker' do - expect_push_bulk_to_match(UnfollowFollowWorker, [[local_follower.id, source_account.id, target_account.id, true]]) - subject.perform(source_account.id, target_account.id) + context 'when a local follower already has a pending request to the target' do + before do + local_follower.follow!(target_account) end include_examples 'user note handling' include_examples 'block and mute handling' include_examples 'followers count handling' + include_examples 'lists handling' + + context 'when the local user already has the target in a list' do + before do + list.accounts << target_account + end + + include_examples 'lists handling' + end end end - context 'both target and source accounts are local' do - let(:target_account) { Fabricate(:account) } - let(:source_account) { Fabricate(:account) } + describe '#perform' do + context 'when both accounts are distant' do + it 'calls UnfollowFollowWorker' do + Sidekiq::Testing.fake! do + subject.perform(source_account.id, target_account.id) + expect(UnfollowFollowWorker).to have_enqueued_sidekiq_job(local_follower.id, source_account.id, target_account.id, false) + Sidekiq::Worker.drain_all + end + end + + include_examples 'common tests' + end + + context 'when target account is local' do + let(:target_account) { Fabricate(:account) } + + it 'calls UnfollowFollowWorker' do + Sidekiq::Testing.fake! do + subject.perform(source_account.id, target_account.id) + expect(UnfollowFollowWorker).to have_enqueued_sidekiq_job(local_follower.id, source_account.id, target_account.id, true) + Sidekiq::Worker.clear_all + end + end + + include_examples 'common tests' + end + + context 'when both target and source accounts are local' do + let(:target_account) { Fabricate(:account) } + let(:source_account) { Fabricate(:account) } - describe 'perform' do it 'calls makes local followers follow the target account' do subject.perform(source_account.id, target_account.id) expect(local_follower.following?(target_account)).to be true end - include_examples 'user note handling' - include_examples 'block and mute handling' - include_examples 'followers count handling' - - it 'does not fail when a local user is already following both accounts' do - double_follower = Fabricate(:account) - double_follower.follow!(source_account) - double_follower.follow!(target_account) - subject.perform(source_account.id, target_account.id) - expect(local_follower.following?(target_account)).to be true - end + include_examples 'common tests' it 'does not allow the moved account to follow themselves' do source_account.follow!(target_account) diff --git a/spec/workers/poll_expiration_notify_worker_spec.rb b/spec/workers/poll_expiration_notify_worker_spec.rb new file mode 100644 index 000000000..78cbc1ee4 --- /dev/null +++ b/spec/workers/poll_expiration_notify_worker_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe PollExpirationNotifyWorker do + let(:worker) { described_class.new } + let(:account) { Fabricate(:account, domain: remote? ? 'example.com' : nil) } + let(:status) { Fabricate(:status, account: account) } + let(:poll) { Fabricate(:poll, status: status, account: account) } + let(:remote?) { false } + let(:poll_vote) { Fabricate(:poll_vote, poll: poll) } + + describe '#perform' do + around do |example| + Sidekiq::Testing.fake! do + example.run + end + end + + it 'runs without error for missing record' do + expect { worker.perform(nil) }.to_not raise_error + end + + context 'when poll is not expired' do + it 'requeues job' do + worker.perform(poll.id) + expect(described_class.sidekiq_options_hash['lock']).to be :until_executing + expect(described_class).to have_enqueued_sidekiq_job(poll.id).at(poll.expires_at + 5.minutes) + end + end + + context 'when poll is expired' do + before do + poll_vote + + travel_to poll.expires_at + 5.minutes + + worker.perform(poll.id) + end + + context 'when poll is local' do + it 'notifies voters' do + expect(ActivityPub::DistributePollUpdateWorker).to have_enqueued_sidekiq_job(poll.status.id) + end + + it 'notifies owner' do + expect(LocalNotificationWorker).to have_enqueued_sidekiq_job(poll.account.id, poll.id, 'Poll', 'poll') + end + + it 'notifies local voters' do + expect(LocalNotificationWorker).to have_enqueued_sidekiq_job(poll_vote.account.id, poll.id, 'Poll', 'poll') + end + end + + context 'when poll is remote' do + let(:remote?) { true } + + it 'does not notify remote voters' do + expect(ActivityPub::DistributePollUpdateWorker).to_not have_enqueued_sidekiq_job(poll.status.id) + end + + it 'does not notify owner' do + expect(LocalNotificationWorker).to_not have_enqueued_sidekiq_job(poll.account.id, poll.id, 'Poll', 'poll') + end + + it 'notifies local voters' do + expect(LocalNotificationWorker).to have_enqueued_sidekiq_job(poll_vote.account.id, poll.id, 'Poll', 'poll') + end + end + end + end +end diff --git a/spec/workers/post_process_media_worker_spec.rb b/spec/workers/post_process_media_worker_spec.rb new file mode 100644 index 000000000..33072704b --- /dev/null +++ b/spec/workers/post_process_media_worker_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe PostProcessMediaWorker do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error for missing record' do + expect { worker.perform(nil) }.to_not raise_error + end + end +end diff --git a/spec/workers/publish_scheduled_announcement_worker_spec.rb b/spec/workers/publish_scheduled_announcement_worker_spec.rb index 0977bba1e..2e50d4a50 100644 --- a/spec/workers/publish_scheduled_announcement_worker_spec.rb +++ b/spec/workers/publish_scheduled_announcement_worker_spec.rb @@ -12,7 +12,7 @@ describe PublishScheduledAnnouncementWorker do describe 'perform' do before do - service = double + service = instance_double(FetchRemoteStatusService) allow(FetchRemoteStatusService).to receive(:new).and_return(service) allow(service).to receive(:call).with('https://domain.com/users/foo/12345') { remote_status.reload } diff --git a/spec/workers/push_conversation_worker_spec.rb b/spec/workers/push_conversation_worker_spec.rb new file mode 100644 index 000000000..5fbb4c685 --- /dev/null +++ b/spec/workers/push_conversation_worker_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe PushConversationWorker do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error for missing record' do + expect { worker.perform(nil) }.to_not raise_error + end + end +end diff --git a/spec/workers/push_encrypted_message_worker_spec.rb b/spec/workers/push_encrypted_message_worker_spec.rb new file mode 100644 index 000000000..3cd04ce7b --- /dev/null +++ b/spec/workers/push_encrypted_message_worker_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe PushEncryptedMessageWorker do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error for missing record' do + expect { worker.perform(nil) }.to_not raise_error + end + end +end diff --git a/spec/workers/push_update_worker_spec.rb b/spec/workers/push_update_worker_spec.rb new file mode 100644 index 000000000..c8f94fa82 --- /dev/null +++ b/spec/workers/push_update_worker_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe PushUpdateWorker do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error for missing record' do + account_id = nil + status_id = nil + + expect { worker.perform(account_id, status_id) }.to_not raise_error + end + end +end diff --git a/spec/workers/redownload_avatar_worker_spec.rb b/spec/workers/redownload_avatar_worker_spec.rb new file mode 100644 index 000000000..b44ae9f03 --- /dev/null +++ b/spec/workers/redownload_avatar_worker_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe RedownloadAvatarWorker do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error for missing record' do + expect { worker.perform(nil) }.to_not raise_error + end + end +end diff --git a/spec/workers/redownload_header_worker_spec.rb b/spec/workers/redownload_header_worker_spec.rb new file mode 100644 index 000000000..767ae7a5a --- /dev/null +++ b/spec/workers/redownload_header_worker_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe RedownloadHeaderWorker do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error for missing record' do + expect { worker.perform(nil) }.to_not raise_error + end + end +end diff --git a/spec/workers/refollow_worker_spec.rb b/spec/workers/refollow_worker_spec.rb index d9c2293b6..5718d4db4 100644 --- a/spec/workers/refollow_worker_spec.rb +++ b/spec/workers/refollow_worker_spec.rb @@ -4,12 +4,13 @@ require 'rails_helper' describe RefollowWorker do subject { described_class.new } + let(:account) { Fabricate(:account, domain: 'example.org', protocol: :activitypub) } let(:alice) { Fabricate(:account, domain: nil, username: 'alice') } let(:bob) { Fabricate(:account, domain: nil, username: 'bob') } describe 'perform' do - let(:service) { double } + let(:service) { instance_double(FollowService) } before do allow(FollowService).to receive(:new).and_return(service) diff --git a/spec/workers/regeneration_worker_spec.rb b/spec/workers/regeneration_worker_spec.rb index c6bdfa0e5..37b0a04c4 100644 --- a/spec/workers/regeneration_worker_spec.rb +++ b/spec/workers/regeneration_worker_spec.rb @@ -9,7 +9,7 @@ describe RegenerationWorker do let(:account) { Fabricate(:account) } it 'calls the precompute feed service for the account' do - service = double(call: nil) + service = instance_double(PrecomputeFeedService, call: nil) allow(PrecomputeFeedService).to receive(:new).and_return(service) result = subject.perform(account.id) @@ -20,7 +20,7 @@ describe RegenerationWorker do it 'fails when account does not exist' do result = subject.perform('aaa') - expect(result).to eq(true) + expect(result).to be(true) end end end diff --git a/spec/workers/remove_featured_tag_worker_spec.rb b/spec/workers/remove_featured_tag_worker_spec.rb new file mode 100644 index 000000000..a64bd0605 --- /dev/null +++ b/spec/workers/remove_featured_tag_worker_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe RemoveFeaturedTagWorker do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error for missing record' do + account_id = nil + featured_tag_id = nil + expect { worker.perform(account_id, featured_tag_id) }.to_not raise_error + end + end +end diff --git a/spec/workers/remove_from_public_statuses_index_worker_spec.rb b/spec/workers/remove_from_public_statuses_index_worker_spec.rb new file mode 100644 index 000000000..43ff211ea --- /dev/null +++ b/spec/workers/remove_from_public_statuses_index_worker_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe RemoveFromPublicStatusesIndexWorker do + describe '#perform' do + let(:account) { Fabricate(:account, indexable: indexable) } + let(:account_id) { account.id } + + before do + allow(Account).to receive(:find).with(account_id).and_return(account) unless account.nil? + allow(account).to receive(:remove_from_public_statuses_index!) unless account.nil? + end + + context 'when account is not indexable' do + let(:indexable) { false } + + it 'removes the account from public statuses index' do + subject.perform(account_id) + expect(account).to have_received(:remove_from_public_statuses_index!) + end + end + + context 'when account is indexable' do + let(:indexable) { true } + + it 'does not remove the account from public statuses index' do + subject.perform(account_id) + expect(account).to_not have_received(:remove_from_public_statuses_index!) + end + end + + context 'when account does not exist' do + let(:account) { nil } + let(:account_id) { 999 } + + it 'does not raise an error' do + expect { subject.perform(account_id) }.to_not raise_error + end + end + end +end diff --git a/spec/workers/resolve_account_worker_spec.rb b/spec/workers/resolve_account_worker_spec.rb new file mode 100644 index 000000000..6f3cff099 --- /dev/null +++ b/spec/workers/resolve_account_worker_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ResolveAccountWorker do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error for missing record' do + expect { worker.perform(nil) }.to_not raise_error + end + end +end diff --git a/spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb b/spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb index 8f20725c8..4d9185093 100644 --- a/spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb +++ b/spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb @@ -1,17 +1,16 @@ +# frozen_string_literal: true + require 'rails_helper' describe Scheduler::AccountsStatusesCleanupScheduler do subject { described_class.new } - let!(:account1) { Fabricate(:account, domain: nil) } - let!(:account2) { Fabricate(:account, domain: nil) } - let!(:account3) { Fabricate(:account, domain: nil) } - let!(:account4) { Fabricate(:account, domain: nil) } - let!(:remote) { Fabricate(:account) } - - let!(:policy1) { Fabricate(:account_statuses_cleanup_policy, account: account1) } - let!(:policy2) { Fabricate(:account_statuses_cleanup_policy, account: account3) } - let!(:policy3) { Fabricate(:account_statuses_cleanup_policy, account: account4, enabled: false) } + let!(:account_alice) { Fabricate(:account, domain: nil, username: 'alice') } + let!(:account_bob) { Fabricate(:account, domain: nil, username: 'bob') } + let!(:account_chris) { Fabricate(:account, domain: nil, username: 'chris') } + let!(:account_dave) { Fabricate(:account, domain: nil, username: 'dave') } + let!(:account_erin) { Fabricate(:account, domain: nil, username: 'erin') } + let!(:remote) { Fabricate(:account) } let(:queue_size) { 0 } let(:queue_latency) { 0 } @@ -19,40 +18,18 @@ describe Scheduler::AccountsStatusesCleanupScheduler do [ { 'concurrency' => 2, - 'queues' => ['push', 'default'], + 'queues' => %w(push default), }, ] end - let(:retry_size) { 0 } before do - queue_stub = double - allow(queue_stub).to receive(:size).and_return(queue_size) - allow(queue_stub).to receive(:latency).and_return(queue_latency) + queue_stub = instance_double(Sidekiq::Queue, size: queue_size, latency: queue_latency) allow(Sidekiq::Queue).to receive(:new).and_return(queue_stub) allow(Sidekiq::ProcessSet).to receive(:new).and_return(process_set_stub) - sidekiq_stats_stub = double - allow(sidekiq_stats_stub).to receive(:retry_size).and_return(retry_size) + sidekiq_stats_stub = instance_double(Sidekiq::Stats) allow(Sidekiq::Stats).to receive(:new).and_return(sidekiq_stats_stub) - - # Create a bunch of old statuses - 10.times do - Fabricate(:status, account: account1, created_at: 3.years.ago) - Fabricate(:status, account: account2, created_at: 3.years.ago) - Fabricate(:status, account: account3, created_at: 3.years.ago) - Fabricate(:status, account: account4, created_at: 3.years.ago) - Fabricate(:status, account: remote, created_at: 3.years.ago) - end - - # Create a bunch of newer statuses - 5.times do - Fabricate(:status, account: account1, created_at: 3.minutes.ago) - Fabricate(:status, account: account2, created_at: 3.minutes.ago) - Fabricate(:status, account: account3, created_at: 3.minutes.ago) - Fabricate(:status, account: account4, created_at: 3.minutes.ago) - Fabricate(:status, account: remote, created_at: 3.minutes.ago) - end end describe '#under_load?' do @@ -70,29 +47,21 @@ describe Scheduler::AccountsStatusesCleanupScheduler do expect(subject.under_load?).to be true end end - - context 'when there is a huge amount of jobs to retry' do - let(:retry_size) { 1_000_000 } - - it 'returns true' do - expect(subject.under_load?).to be true - end - end end - describe '#get_budget' do - context 'on a single thread' do - let(:process_set_stub) { [ { 'concurrency' => 1, 'queues' => ['push', 'default'] } ] } + describe '#compute_budget' do + context 'with a single thread' do + let(:process_set_stub) { [{ 'concurrency' => 1, 'queues' => %w(push default) }] } it 'returns a low value' do expect(subject.compute_budget).to be < 10 end end - context 'on a lot of threads' do + context 'with a lot of threads' do let(:process_set_stub) do [ - { 'concurrency' => 2, 'queues' => ['push', 'default'] }, + { 'concurrency' => 2, 'queues' => %w(push default) }, { 'concurrency' => 2, 'queues' => ['push'] }, { 'concurrency' => 2, 'queues' => ['push'] }, { 'concurrency' => 2, 'queues' => ['push'] }, @@ -106,21 +75,96 @@ describe Scheduler::AccountsStatusesCleanupScheduler do end describe '#perform' do + around do |example| + Timeout.timeout(30) do + example.run + end + end + + before do + # Policies for the accounts + Fabricate(:account_statuses_cleanup_policy, account: account_alice) + Fabricate(:account_statuses_cleanup_policy, account: account_chris) + Fabricate(:account_statuses_cleanup_policy, account: account_dave, enabled: false) + Fabricate(:account_statuses_cleanup_policy, account: account_erin) + + # Create a bunch of old statuses + 4.times do + Fabricate(:status, account: account_alice, created_at: 3.years.ago) + Fabricate(:status, account: account_bob, created_at: 3.years.ago) + Fabricate(:status, account: account_chris, created_at: 3.years.ago) + Fabricate(:status, account: account_dave, created_at: 3.years.ago) + Fabricate(:status, account: account_erin, created_at: 3.years.ago) + Fabricate(:status, account: remote, created_at: 3.years.ago) + end + + # Create a bunch of newer statuses + Fabricate(:status, account: account_alice, created_at: 3.minutes.ago) + Fabricate(:status, account: account_bob, created_at: 3.minutes.ago) + Fabricate(:status, account: account_chris, created_at: 3.minutes.ago) + Fabricate(:status, account: account_dave, created_at: 3.minutes.ago) + Fabricate(:status, account: remote, created_at: 3.minutes.ago) + end + context 'when the budget is lower than the number of toots to delete' do - it 'deletes as many statuses as the given budget' do - expect { subject.perform }.to change { Status.count }.by(-subject.compute_budget) + it 'deletes the appropriate statuses' do + expect(Status.count).to be > (subject.compute_budget) # Data check + + expect { subject.perform } + .to change(Status, :count).by(-subject.compute_budget) # Cleanable statuses + .and (not_change { account_bob.statuses.count }) # No cleanup policy for account + .and(not_change { account_dave.statuses.count }) # Disabled cleanup policy end - it 'does not delete from accounts with no cleanup policy' do - expect { subject.perform }.to_not change { account2.statuses.count } + it 'eventually deletes every deletable toot given enough runs' do + stub_const 'Scheduler::AccountsStatusesCleanupScheduler::MAX_BUDGET', 4 + + expect { 3.times { subject.perform } }.to change(Status, :count).by(-cleanable_statuses_count) end - it 'does not delete from accounts with disabled cleanup policies' do - expect { subject.perform }.to_not change { account4.statuses.count } + it 'correctly round-trips between users across several runs' do + stub_const 'Scheduler::AccountsStatusesCleanupScheduler::MAX_BUDGET', 3 + stub_const 'Scheduler::AccountsStatusesCleanupScheduler::PER_ACCOUNT_BUDGET', 2 + + expect { 3.times { subject.perform } } + .to change(Status, :count).by(-3 * 3) + .and change { account_alice.statuses.count } + .and change { account_chris.statuses.count } + .and(change { account_erin.statuses.count }) end - it 'eventually deletes every deletable toot' do - expect { subject.perform; subject.perform; subject.perform; subject.perform }.to change { Status.count }.by(-20) + context 'when given a big budget' do + let(:process_set_stub) { [{ 'concurrency' => 400, 'queues' => %w(push default) }] } + + before do + stub_const 'Scheduler::AccountsStatusesCleanupScheduler::MAX_BUDGET', 400 + end + + it 'correctly handles looping in a single run' do + expect(subject.compute_budget).to eq(400) + expect { subject.perform }.to change(Status, :count).by(-cleanable_statuses_count) + end + end + + context 'when there is no work to be done' do + let(:process_set_stub) { [{ 'concurrency' => 400, 'queues' => %w(push default) }] } + + before do + stub_const 'Scheduler::AccountsStatusesCleanupScheduler::MAX_BUDGET', 400 + subject.perform + end + + it 'does not get stuck' do + expect(subject.compute_budget).to eq(400) + expect { subject.perform }.to_not change(Status, :count) + end + end + + def cleanable_statuses_count + Status + .where(account_id: [account_alice, account_chris, account_erin]) # Accounts with enabled policies + .where('created_at < ?', 2.weeks.ago) # Policy defaults is 2.weeks + .count end end end diff --git a/spec/workers/scheduler/auto_close_registrations_scheduler_spec.rb b/spec/workers/scheduler/auto_close_registrations_scheduler_spec.rb new file mode 100644 index 000000000..c0c50b128 --- /dev/null +++ b/spec/workers/scheduler/auto_close_registrations_scheduler_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Scheduler::AutoCloseRegistrationsScheduler do + subject { described_class.new } + + describe '#perform' do + let(:moderator_activity_date) { Time.now.utc } + + before do + Fabricate(:user, role: UserRole.find_by(name: 'Owner'), current_sign_in_at: 10.years.ago) + Fabricate(:user, role: UserRole.find_by(name: 'Moderator'), current_sign_in_at: moderator_activity_date) + end + + context 'when registrations are open' do + before do + Setting.registrations_mode = 'open' + end + + context 'when a moderator has logged in recently' do + let(:moderator_activity_date) { Time.now.utc } + + it 'does not change registrations mode' do + expect { subject.perform }.to_not change(Setting, :registrations_mode) + end + end + + context 'when a moderator has not recently signed in' do + let(:moderator_activity_date) { 1.year.ago } + + it 'changes registrations mode from open to approved' do + expect { subject.perform }.to change(Setting, :registrations_mode).from('open').to('approved') + end + end + end + + context 'when registrations are closed' do + before do + Setting.registrations_mode = 'none' + end + + context 'when a moderator has logged in recently' do + let(:moderator_activity_date) { Time.now.utc } + + it 'does not change registrations mode' do + expect { subject.perform }.to_not change(Setting, :registrations_mode) + end + end + + context 'when a moderator has not recently signed in' do + let(:moderator_activity_date) { 1.year.ago } + + it 'does not change registrations mode' do + expect { subject.perform }.to_not change(Setting, :registrations_mode) + end + end + end + end +end diff --git a/spec/workers/scheduler/follow_recommendations_scheduler_spec.rb b/spec/workers/scheduler/follow_recommendations_scheduler_spec.rb new file mode 100644 index 000000000..18d5260e4 --- /dev/null +++ b/spec/workers/scheduler/follow_recommendations_scheduler_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Scheduler::FollowRecommendationsScheduler do + let!(:target_accounts) do + Fabricate.times(3, :account) do + statuses(count: 6) + end + end + let!(:follower_accounts) do + Fabricate.times(5, :account) do + statuses(count: 6) + end + end + + describe '#perform' do + subject(:scheduled_run) { described_class.new.perform } + + context 'when there are accounts to recommend' do + before do + # Follow the target accounts by follow accounts to make them recommendable + follower_accounts.each do |follower_account| + target_accounts.each do |target_account| + Fabricate(:follow, account: follower_account, target_account: target_account) + end + end + end + + it 'creates recommendations' do + expect { scheduled_run }.to change(FollowRecommendation, :count).from(0).to(target_accounts.size) + expect(redis.zrange('follow_recommendations:en', 0, -1)).to match_array(target_accounts.pluck(:id).map(&:to_s)) + end + end + + context 'when there are no accounts to recommend' do + it 'does not create follow recommendations' do + expect { scheduled_run }.to_not change(FollowRecommendation, :count) + expect(redis.zrange('follow_recommendations:en', 0, -1)).to be_empty + end + end + end +end diff --git a/spec/workers/scheduler/indexing_scheduler_spec.rb b/spec/workers/scheduler/indexing_scheduler_spec.rb new file mode 100644 index 000000000..568f0fc84 --- /dev/null +++ b/spec/workers/scheduler/indexing_scheduler_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Scheduler::IndexingScheduler do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error' do + expect { worker.perform }.to_not raise_error + end + end +end diff --git a/spec/workers/scheduler/instance_refresh_scheduler_spec.rb b/spec/workers/scheduler/instance_refresh_scheduler_spec.rb new file mode 100644 index 000000000..8f686a699 --- /dev/null +++ b/spec/workers/scheduler/instance_refresh_scheduler_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Scheduler::InstanceRefreshScheduler do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error' do + expect { worker.perform }.to_not raise_error + end + end +end diff --git a/spec/workers/scheduler/ip_cleanup_scheduler_spec.rb b/spec/workers/scheduler/ip_cleanup_scheduler_spec.rb new file mode 100644 index 000000000..50af03011 --- /dev/null +++ b/spec/workers/scheduler/ip_cleanup_scheduler_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Scheduler::IpCleanupScheduler do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error' do + expect { worker.perform }.to_not raise_error + end + end +end diff --git a/spec/workers/scheduler/pghero_scheduler_spec.rb b/spec/workers/scheduler/pghero_scheduler_spec.rb new file mode 100644 index 000000000..e404e5fe4 --- /dev/null +++ b/spec/workers/scheduler/pghero_scheduler_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Scheduler::PgheroScheduler do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error' do + expect { worker.perform }.to_not raise_error + end + end +end diff --git a/spec/workers/scheduler/scheduled_statuses_scheduler_spec.rb b/spec/workers/scheduler/scheduled_statuses_scheduler_spec.rb new file mode 100644 index 000000000..13c853c62 --- /dev/null +++ b/spec/workers/scheduler/scheduled_statuses_scheduler_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Scheduler::ScheduledStatusesScheduler do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error' do + expect { worker.perform }.to_not raise_error + end + end +end diff --git a/spec/workers/scheduler/software_update_check_scheduler_spec.rb b/spec/workers/scheduler/software_update_check_scheduler_spec.rb new file mode 100644 index 000000000..f596c0a1e --- /dev/null +++ b/spec/workers/scheduler/software_update_check_scheduler_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Scheduler::SoftwareUpdateCheckScheduler do + subject { described_class.new } + + describe 'perform' do + let(:service_double) { instance_double(SoftwareUpdateCheckService, call: nil) } + + before do + allow(SoftwareUpdateCheckService).to receive(:new).and_return(service_double) + end + + it 'calls SoftwareUpdateCheckService' do + subject.perform + expect(service_double).to have_received(:call) + end + end +end diff --git a/spec/workers/scheduler/suspended_user_cleanup_scheduler_spec.rb b/spec/workers/scheduler/suspended_user_cleanup_scheduler_spec.rb new file mode 100644 index 000000000..25f0e1fce --- /dev/null +++ b/spec/workers/scheduler/suspended_user_cleanup_scheduler_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Scheduler::SuspendedUserCleanupScheduler do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error' do + expect { worker.perform }.to_not raise_error + end + end +end diff --git a/spec/workers/scheduler/trends/refresh_scheduler_spec.rb b/spec/workers/scheduler/trends/refresh_scheduler_spec.rb new file mode 100644 index 000000000..c0c5f032b --- /dev/null +++ b/spec/workers/scheduler/trends/refresh_scheduler_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Scheduler::Trends::RefreshScheduler do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error' do + expect { worker.perform }.to_not raise_error + end + end +end diff --git a/spec/workers/scheduler/trends/review_notifications_scheduler_spec.rb b/spec/workers/scheduler/trends/review_notifications_scheduler_spec.rb new file mode 100644 index 000000000..cc971c24b --- /dev/null +++ b/spec/workers/scheduler/trends/review_notifications_scheduler_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Scheduler::Trends::ReviewNotificationsScheduler do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error' do + expect { worker.perform }.to_not raise_error + end + end +end diff --git a/spec/workers/scheduler/user_cleanup_scheduler_spec.rb b/spec/workers/scheduler/user_cleanup_scheduler_spec.rb new file mode 100644 index 000000000..990979500 --- /dev/null +++ b/spec/workers/scheduler/user_cleanup_scheduler_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Scheduler::UserCleanupScheduler do + subject { described_class.new } + + let!(:new_unconfirmed_user) { Fabricate(:user) } + let!(:old_unconfirmed_user) { Fabricate(:user) } + let!(:confirmed_user) { Fabricate(:user) } + let!(:moderation_note) { Fabricate(:account_moderation_note, account: Fabricate(:account), target_account: old_unconfirmed_user.account) } + + describe '#perform' do + before do + # Need to update the already-existing users because their initialization overrides confirmation_sent_at + new_unconfirmed_user.update!(confirmed_at: nil, confirmation_sent_at: Time.now.utc) + old_unconfirmed_user.update!(confirmed_at: nil, confirmation_sent_at: 1.week.ago) + confirmed_user.update!(confirmed_at: 1.day.ago) + end + + it 'deletes the old unconfirmed user' do + expect { subject.perform }.to change { User.exists?(old_unconfirmed_user.id) }.from(true).to(false) + end + + it "deletes the old unconfirmed user's account" do + expect { subject.perform }.to change { Account.exists?(old_unconfirmed_user.account_id) }.from(true).to(false) + end + + it 'does not delete the new unconfirmed user or their account' do + subject.perform + expect(User.exists?(new_unconfirmed_user.id)).to be true + expect(Account.exists?(new_unconfirmed_user.account_id)).to be true + end + + it 'does not delete the confirmed user or their account' do + subject.perform + expect(User.exists?(confirmed_user.id)).to be true + expect(Account.exists?(confirmed_user.account_id)).to be true + end + end +end diff --git a/spec/workers/scheduler/vacuum_scheduler_spec.rb b/spec/workers/scheduler/vacuum_scheduler_spec.rb new file mode 100644 index 000000000..36ecc93d8 --- /dev/null +++ b/spec/workers/scheduler/vacuum_scheduler_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Scheduler::VacuumScheduler do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error' do + expect { worker.perform }.to_not raise_error + end + end +end diff --git a/spec/workers/tag_unmerge_worker_spec.rb b/spec/workers/tag_unmerge_worker_spec.rb new file mode 100644 index 000000000..5d3a12c44 --- /dev/null +++ b/spec/workers/tag_unmerge_worker_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe TagUnmergeWorker do + subject { described_class.new } + + describe 'perform' do + let(:follower) { Fabricate(:account) } + let(:followed) { Fabricate(:account) } + let(:followed_tag) { Fabricate(:tag) } + let(:unchanged_followed_tag) { Fabricate(:tag) } + let(:status_from_followed) { Fabricate(:status, created_at: 2.hours.ago, account: followed) } + let(:tagged_status) { Fabricate(:status, created_at: 1.hour.ago) } + let(:unchanged_tagged_status) { Fabricate(:status) } + + before do + tagged_status.tags << followed_tag + unchanged_tagged_status.tags << followed_tag + unchanged_tagged_status.tags << unchanged_followed_tag + + tag_follow = TagFollow.create_with(rate_limit: false).find_or_create_by!(tag: followed_tag, account: follower) + TagFollow.create_with(rate_limit: false).find_or_create_by!(tag: unchanged_followed_tag, account: follower) + + FeedManager.instance.push_to_home(follower, status_from_followed, update: false) + FeedManager.instance.push_to_home(follower, tagged_status, update: false) + FeedManager.instance.push_to_home(follower, unchanged_tagged_status, update: false) + + tag_follow.destroy! + end + + it 'removes the expected status from the feed' do + expect { subject.perform(followed_tag.id, follower.id) } + .to change { HomeFeed.new(follower).get(10).pluck(:id) } + .from([unchanged_tagged_status.id, tagged_status.id, status_from_followed.id]) + .to([unchanged_tagged_status.id, status_from_followed.id]) + end + end +end diff --git a/spec/workers/unfollow_follow_worker_spec.rb b/spec/workers/unfollow_follow_worker_spec.rb index 5ea4256a9..8025b88c0 100644 --- a/spec/workers/unfollow_follow_worker_spec.rb +++ b/spec/workers/unfollow_follow_worker_spec.rb @@ -3,13 +3,13 @@ require 'rails_helper' describe UnfollowFollowWorker do + subject { described_class.new } + let(:local_follower) { Fabricate(:account) } let(:source_account) { Fabricate(:account) } let(:target_account) { Fabricate(:account) } let(:show_reblogs) { true } - subject { described_class.new } - before do local_follower.follow!(source_account, reblogs: show_reblogs) end diff --git a/spec/workers/unpublish_announcement_worker_spec.rb b/spec/workers/unpublish_announcement_worker_spec.rb new file mode 100644 index 000000000..c742c30bc --- /dev/null +++ b/spec/workers/unpublish_announcement_worker_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe UnpublishAnnouncementWorker do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error for missing record' do + expect { worker.perform(nil) }.to_not raise_error + end + end +end diff --git a/spec/workers/verify_account_links_worker_spec.rb b/spec/workers/verify_account_links_worker_spec.rb new file mode 100644 index 000000000..227591392 --- /dev/null +++ b/spec/workers/verify_account_links_worker_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe VerifyAccountLinksWorker do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error for missing record' do + expect { worker.perform(nil) }.to_not raise_error + end + end +end diff --git a/spec/workers/web/push_notification_worker_spec.rb b/spec/workers/web/push_notification_worker_spec.rb index 5bc24f888..822ef5257 100644 --- a/spec/workers/web/push_notification_worker_spec.rb +++ b/spec/workers/web/push_notification_worker_spec.rb @@ -37,7 +37,7 @@ describe Web::PushNotificationWorker do expect(a_request(:post, endpoint).with(headers: { 'Content-Encoding' => 'aesgcm', 'Content-Type' => 'application/octet-stream', - 'Crypto-Key' => 'dh=BAgtUks5d90kFmxGevk9tH7GEmvz9DB0qcEMUsOBgKwMf-TMjsKIIG6LQvGcFAf6jcmAod15VVwmYwGIIxE4VWE;p256ecdsa=' + vapid_public_key.delete('='), + 'Crypto-Key' => "dh=BAgtUks5d90kFmxGevk9tH7GEmvz9DB0qcEMUsOBgKwMf-TMjsKIIG6LQvGcFAf6jcmAod15VVwmYwGIIxE4VWE;p256ecdsa=#{vapid_public_key.delete('=')}", 'Encryption' => 'salt=WJeVM-RY-F9351SVxTFx_g', 'Ttl' => '172800', 'Urgency' => 'normal', diff --git a/spec/workers/webhooks/delivery_worker_spec.rb b/spec/workers/webhooks/delivery_worker_spec.rb new file mode 100644 index 000000000..daf8a3e28 --- /dev/null +++ b/spec/workers/webhooks/delivery_worker_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Webhooks::DeliveryWorker do + let(:worker) { described_class.new } + + describe 'perform' do + it 'runs without error' do + expect { worker.perform(nil, nil) }.to_not raise_error + end + end +end diff --git a/streaming/index.js b/streaming/index.js index c0db7eec5..ef1bb94fd 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -1,96 +1,39 @@ // @ts-check -const os = require('os'); -const throng = require('throng'); +const fs = require('fs'); +const http = require('http'); +const url = require('url'); + const dotenv = require('dotenv'); const express = require('express'); -const http = require('http'); -const redis = require('redis'); -const pg = require('pg'); -const log = require('npmlog'); -const url = require('url'); -const uuid = require('uuid'); -const fs = require('fs'); -const WebSocket = require('ws'); +const Redis = require('ioredis'); const { JSDOM } = require('jsdom'); +const log = require('npmlog'); +const pg = require('pg'); +const dbUrlToConfig = require('pg-connection-string').parse; +const metrics = require('prom-client'); +const uuid = require('uuid'); +const WebSocket = require('ws'); -const env = process.env.NODE_ENV || 'development'; -const alwaysRequireAuth = process.env.LIMITED_FEDERATION_MODE === 'true' || process.env.WHITELIST_MODE === 'true' || process.env.AUTHORIZED_FETCH === 'true'; +const environment = process.env.NODE_ENV || 'development'; dotenv.config({ - path: env === 'production' ? '.env.production' : '.env', + path: environment === 'production' ? '.env.production' : '.env', }); log.level = process.env.LOG_LEVEL || 'verbose'; /** - * @param {string} dbUrl - * @return {Object.} + * @param {Object.} config */ -const dbUrlToConfig = (dbUrl) => { - if (!dbUrl) { - return {}; - } - - const params = url.parse(dbUrl, true); - const config = {}; - - if (params.auth) { - [config.user, config.password] = params.auth.split(':'); - } - - if (params.hostname) { - config.host = params.hostname; - } - - if (params.port) { - config.port = params.port; - } - - if (params.pathname) { - config.database = params.pathname.split('/')[1]; - } - - const ssl = params.query && params.query.ssl; - - if (ssl && ssl === 'true' || ssl === '1') { - config.ssl = true; - } - - return config; -}; - -/** - * @param {Object.} defaultConfig - * @param {string} redisUrl - */ -const redisUrlToClient = async (defaultConfig, redisUrl) => { - const config = defaultConfig; - - let client; - - if (!redisUrl) { - client = redis.createClient(config); - } else if (redisUrl.startsWith('unix://')) { - client = redis.createClient(Object.assign(config, { - socket: { - path: redisUrl.slice(7), - }, - })); - } else { - client = redis.createClient(Object.assign(config, { - url: redisUrl, - })); - } - +const createRedisClient = async (config) => { + const { redisParams, redisUrl } = config; + const client = new Redis(redisUrl, redisParams); client.on('error', (err) => log.error('Redis Client Error!', err)); - await client.connect(); return client; }; -const numWorkers = +process.env.STREAMING_CLUSTER_NUM || (env === 'development' ? 1 : Math.max(os.cpus().length - 1, 1)); - /** * Attempts to safely parse a string as JSON, used when both receiving a message * from redis and when receiving a message from a client over a websocket @@ -122,76 +65,229 @@ const parseJSON = (json, req) => { } }; -const startMaster = () => { - if (!process.env.SOCKET && process.env.PORT && isNaN(+process.env.PORT)) { - log.warn('UNIX domain socket is now supported by using SOCKET. Please migrate from PORT hack.'); - } - - log.warn(`Starting streaming API server master with ${numWorkers} workers`); -}; - -const startWorker = async (workerId) => { - log.warn(`Starting worker ${workerId}`); - +/** + * @param {Object.} env the `process.env` value to read configuration from + * @returns {Object.} the configuration for the PostgreSQL connection + */ +const pgConfigFromEnv = (env) => { const pgConfigs = { development: { - user: process.env.DB_USER || pg.defaults.user, - password: process.env.DB_PASS || pg.defaults.password, - database: process.env.DB_NAME || 'mastodon_development', - host: process.env.DB_HOST || pg.defaults.host, - port: process.env.DB_PORT || pg.defaults.port, - max: 10, + user: env.DB_USER || pg.defaults.user, + password: env.DB_PASS || pg.defaults.password, + database: env.DB_NAME || 'mastodon_development', + host: env.DB_HOST || pg.defaults.host, + port: env.DB_PORT || pg.defaults.port, }, production: { - user: process.env.DB_USER || 'mastodon', - password: process.env.DB_PASS || '', - database: process.env.DB_NAME || 'mastodon_production', - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 5432, - max: 10, + user: env.DB_USER || 'mastodon', + password: env.DB_PASS || '', + database: env.DB_NAME || 'mastodon_production', + host: env.DB_HOST || 'localhost', + port: env.DB_PORT || 5432, }, }; - if (!!process.env.DB_SSLMODE && process.env.DB_SSLMODE !== 'disable') { - pgConfigs.development.ssl = true; - pgConfigs.production.ssl = true; + let baseConfig; + + if (env.DATABASE_URL) { + baseConfig = dbUrlToConfig(env.DATABASE_URL); + + // Support overriding the database password in the connection URL + if (!baseConfig.password && env.DB_PASS) { + baseConfig.password = env.DB_PASS; + } + } else { + baseConfig = pgConfigs[environment]; + + if (env.DB_SSLMODE) { + switch(env.DB_SSLMODE) { + case 'disable': + case '': + baseConfig.ssl = false; + break; + case 'no-verify': + baseConfig.ssl = { rejectUnauthorized: false }; + break; + default: + baseConfig.ssl = {}; + break; + } + } } + return { + ...baseConfig, + max: env.DB_POOL || 10, + connectionTimeoutMillis: 15000, + application_name: '', + }; +}; + +/** + * @param {Object.} env the `process.env` value to read configuration from + * @returns {Object.} configuration for the Redis connection + */ +const redisConfigFromEnv = (env) => { + // ioredis *can* transparently add prefixes for us, but it doesn't *in some cases*, + // which means we can't use it. But this is something that should be looked into. + const redisPrefix = env.REDIS_NAMESPACE ? `${env.REDIS_NAMESPACE}:` : ''; + + const redisParams = { + host: env.REDIS_HOST || '127.0.0.1', + port: env.REDIS_PORT || 6379, + db: env.REDIS_DB || 0, + password: env.REDIS_PASSWORD || undefined, + }; + + // redisParams.path takes precedence over host and port. + if (env.REDIS_URL && env.REDIS_URL.startsWith('unix://')) { + redisParams.path = env.REDIS_URL.slice(7); + } + + return { + redisParams, + redisPrefix, + redisUrl: env.REDIS_URL, + }; +}; + +const PUBLIC_CHANNELS = [ + 'public', + 'public:media', + 'public:local', + 'public:local:media', + 'public:remote', + 'public:remote:media', + 'hashtag', + 'hashtag:local', +]; + +// Used for priming the counters/gauges for the various metrics that are +// per-channel +const CHANNEL_NAMES = [ + 'system', + 'user', + 'user:notification', + 'list', + 'direct', + ...PUBLIC_CHANNELS +]; + +const startServer = async () => { const app = express(); app.set('trust proxy', process.env.TRUSTED_PROXY_IP ? process.env.TRUSTED_PROXY_IP.split(/(?:\s*,\s*|\s+)/) : 'loopback,uniquelocal'); - const pgPool = new pg.Pool(Object.assign(pgConfigs[env], dbUrlToConfig(process.env.DATABASE_URL))); + const pgPool = new pg.Pool(pgConfigFromEnv(process.env)); const server = http.createServer(app); - const redisNamespace = process.env.REDIS_NAMESPACE || null; - - const redisParams = { - socket: { - host: process.env.REDIS_HOST || '127.0.0.1', - port: process.env.REDIS_PORT || 6379, - }, - database: process.env.REDIS_DB || 0, - password: process.env.REDIS_PASSWORD || undefined, - }; - - if (redisNamespace) { - redisParams.namespace = redisNamespace; - } - - const redisPrefix = redisNamespace ? `${redisNamespace}:` : ''; /** * @type {Object.): void>>} */ const subs = {}; - const redisSubscribeClient = await redisUrlToClient(redisParams, process.env.REDIS_URL); - const redisClient = await redisUrlToClient(redisParams, process.env.REDIS_URL); + const redisConfig = redisConfigFromEnv(process.env); + const redisSubscribeClient = await createRedisClient(redisConfig); + const redisClient = await createRedisClient(redisConfig); + const { redisPrefix } = redisConfig; + + // Collect metrics from Node.js + metrics.collectDefaultMetrics(); + + new metrics.Gauge({ + name: 'pg_pool_total_connections', + help: 'The total number of clients existing within the pool', + collect() { + this.set(pgPool.totalCount); + }, + }); + + new metrics.Gauge({ + name: 'pg_pool_idle_connections', + help: 'The number of clients which are not checked out but are currently idle in the pool', + collect() { + this.set(pgPool.idleCount); + }, + }); + + new metrics.Gauge({ + name: 'pg_pool_waiting_queries', + help: 'The number of queued requests waiting on a client when all clients are checked out', + collect() { + this.set(pgPool.waitingCount); + }, + }); + + const connectedClients = new metrics.Gauge({ + name: 'connected_clients', + help: 'The number of clients connected to the streaming server', + labelNames: ['type'], + }); + + const connectedChannels = new metrics.Gauge({ + name: 'connected_channels', + help: 'The number of channels the streaming server is streaming to', + labelNames: [ 'type', 'channel' ] + }); + + const redisSubscriptions = new metrics.Gauge({ + name: 'redis_subscriptions', + help: 'The number of Redis channels the streaming server is subscribed to', + }); + + const redisMessagesReceived = new metrics.Counter({ + name: 'redis_messages_received_total', + help: 'The total number of messages the streaming server has received from redis subscriptions' + }); + + const messagesSent = new metrics.Counter({ + name: 'messages_sent_total', + help: 'The total number of messages the streaming server sent to clients per connection type', + labelNames: [ 'type' ] + }); + + // Prime the gauges so we don't loose metrics between restarts: + redisSubscriptions.set(0); + connectedClients.set({ type: 'websocket' }, 0); + connectedClients.set({ type: 'eventsource' }, 0); + + // For each channel, initialize the gauges at zero; There's only a finite set of channels available + CHANNEL_NAMES.forEach(( channel ) => { + connectedChannels.set({ type: 'websocket', channel }, 0); + connectedChannels.set({ type: 'eventsource', channel }, 0); + }) + + // Prime the counters so that we don't loose metrics between restarts. + // Unfortunately counters don't support the set() API, so instead I'm using + // inc(0) to achieve the same result. + redisMessagesReceived.inc(0); + messagesSent.inc({ type: 'websocket' }, 0); + messagesSent.inc({ type: 'eventsource' }, 0); + + // When checking metrics in the browser, the favicon is requested this + // prevents the request from falling through to the API Router, which would + // error for this endpoint: + app.get('/favicon.ico', (req, res) => res.status(404).end()); + + app.get('/api/v1/streaming/health', (req, res) => { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('OK'); + }); + + app.get('/metrics', async (req, res) => { + try { + res.set('Content-Type', metrics.register.contentType); + res.end(await metrics.register.metrics()); + } catch (ex) { + log.error(ex); + res.status(500).end(); + } + }); /** * @param {string[]} channels - * @return {function(): void} + * @returns {function(): void} */ const subscriptionHeartbeat = channels => { const interval = 6 * 60; @@ -210,13 +306,15 @@ const startWorker = async (workerId) => { }; /** - * @param {string} message * @param {string} channel + * @param {string} message */ - const onRedisMessage = (message, channel) => { + const onRedisMessage = (channel, message) => { + redisMessagesReceived.inc(); + const callbacks = subs[channel]; - log.silly(`New message on channel ${channel}`); + log.silly(`New message on channel ${redisPrefix}${channel}`); if (!callbacks) { return; @@ -227,6 +325,7 @@ const startWorker = async (workerId) => { callbacks.forEach(callback => callback(json)); }; + redisSubscribeClient.on("message", onRedisMessage); /** * @callback SubscriptionListener @@ -245,7 +344,14 @@ const startWorker = async (workerId) => { if (subs[channel].length === 0) { log.verbose(`Subscribe ${channel}`); - redisSubscribeClient.subscribe(channel, onRedisMessage); + redisSubscribeClient.subscribe(channel, (err, count) => { + if (err) { + log.error(`Error subscribing to ${channel}`); + } + else { + redisSubscriptions.set(count); + } + }); } subs[channel].push(callback); @@ -266,7 +372,14 @@ const startWorker = async (workerId) => { if (subs[channel].length === 0) { log.verbose(`Unsubscribe ${channel}`); - redisSubscribeClient.unsubscribe(channel); + redisSubscribeClient.unsubscribe(channel, (err, count) => { + if (err) { + log.error(`Error unsubscribing to ${channel}`); + } + else { + redisSubscriptions.set(count); + } + }); delete subs[channel]; } }; @@ -285,7 +398,7 @@ const startWorker = async (workerId) => { /** * @param {any} value - * @return {boolean} + * @returns {boolean} */ const isTruthy = value => value && !FALSE_VALUES.includes(value); @@ -293,7 +406,7 @@ const startWorker = async (workerId) => { /** * @param {any} req * @param {any} res - * @param {function(Error=): void} + * @param {function(Error=): void} next */ const allowCrossDomain = (req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); @@ -306,7 +419,7 @@ const startWorker = async (workerId) => { /** * @param {any} req * @param {any} res - * @param {function(Error=): void} + * @param {function(Error=): void} next */ const setRequestId = (req, res, next) => { req.requestId = uuid.v4(); @@ -318,7 +431,7 @@ const startWorker = async (workerId) => { /** * @param {any} req * @param {any} res - * @param {function(Error=): void} + * @param {function(Error=): void} next */ const setRemoteAddress = (req, res, next) => { req.remoteAddress = req.connection.remoteAddress; @@ -329,7 +442,7 @@ const startWorker = async (workerId) => { /** * @param {any} req * @param {string[]} necessaryScopes - * @return {boolean} + * @returns {boolean} */ const isInScope = (req, necessaryScopes) => req.scopes.some(scope => necessaryScopes.includes(scope)); @@ -337,7 +450,7 @@ const startWorker = async (workerId) => { /** * @param {string} token * @param {any} req - * @return {Promise.} + * @returns {Promise.} */ const accountFromToken = (token, req) => new Promise((resolve, reject) => { pgPool.connect((err, client, done) => { @@ -375,25 +488,19 @@ const startWorker = async (workerId) => { /** * @param {any} req - * @param {boolean=} required - * @return {Promise.} + * @returns {Promise.} */ - const accountFromRequest = (req, required = true) => new Promise((resolve, reject) => { + const accountFromRequest = (req) => new Promise((resolve, reject) => { const authorization = req.headers.authorization; const location = url.parse(req.url, true); const accessToken = location.query.access_token || req.headers['sec-websocket-protocol']; if (!authorization && !accessToken) { - if (required) { - const err = new Error('Missing access token'); - err.status = 401; + const err = new Error('Missing access token'); + err.status = 401; - reject(err); - return; - } else { - resolve(); - return; - } + reject(err); + return; } const token = authorization ? authorization.replace(/^Bearer /, '') : accessToken; @@ -433,21 +540,10 @@ const startWorker = async (workerId) => { } }; - const PUBLIC_CHANNELS = [ - 'public', - 'public:media', - 'public:local', - 'public:local:media', - 'public:remote', - 'public:remote:media', - 'hashtag', - 'hashtag:local', - ]; - /** * @param {any} req - * @param {string} channelName - * @return {Promise.} + * @param {string|undefined} channelName + * @returns {Promise.} */ const checkScopes = (req, channelName) => new Promise((resolve, reject) => { log.silly(req.requestId, `Checking OAuth scopes for ${channelName}`); @@ -496,7 +592,7 @@ const startWorker = async (workerId) => { // variables. OAuth scope checks are moved to the point of subscription // to a specific stream. - accountFromRequest(info.req, alwaysRequireAuth).then(() => { + accountFromRequest(info.req).then(() => { callback(true, undefined, undefined); }).catch(err => { log.error(info.req.requestId, err.toString()); @@ -549,10 +645,14 @@ const startWorker = async (workerId) => { res.on('close', () => { unsubscribe(`${redisPrefix}${accessTokenChannelId}`, listener); unsubscribe(`${redisPrefix}${systemChannelId}`, listener); + + connectedChannels.labels({ type: 'eventsource', channel: 'system' }).dec(2); }); subscribe(`${redisPrefix}${accessTokenChannelId}`, listener); subscribe(`${redisPrefix}${systemChannelId}`, listener); + + connectedChannels.labels({ type: 'eventsource', channel: 'system' }).inc(2); }; /** @@ -566,7 +666,19 @@ const startWorker = async (workerId) => { return; } - accountFromRequest(req, alwaysRequireAuth).then(() => checkScopes(req, channelNameFromPath(req))).then(() => { + const channelName = channelNameFromPath(req); + + // If no channelName can be found for the request, then we should terminate + // the connection, as there's nothing to stream back + if (!channelName) { + const err = new Error('Unknown channel requested'); + err.status = 400; + + next(err); + return; + } + + accountFromRequest(req).then(() => checkScopes(req, channelName)).then(() => { subscribeHttpToSystemChannel(req, res); }).then(() => { next(); @@ -596,14 +708,14 @@ const startWorker = async (workerId) => { /** * @param {array} arr * @param {number=} shift - * @return {string} + * @returns {string} */ const placeholders = (arr, shift = 0) => arr.map((_, i) => `$${i + 1 + shift}`).join(', '); /** * @param {string} listId * @param {any} req - * @return {Promise.} + * @returns {Promise.} */ const authorizeListAccess = (listId, req) => new Promise((resolve, reject) => { const { accountId } = req; @@ -632,10 +744,11 @@ const startWorker = async (workerId) => { * @param {any} req * @param {function(string, string): void} output * @param {undefined | function(string[], SubscriptionListener): void} attachCloseHandler + * @param {'websocket' | 'eventsource'} destinationType * @param {boolean=} needsFiltering * @returns {SubscriptionListener} */ - const streamFrom = (ids, req, output, attachCloseHandler, needsFiltering = false) => { + const streamFrom = (ids, req, output, attachCloseHandler, destinationType, needsFiltering = false) => { const accountId = req.accountId || req.remoteAddress; log.verbose(req.requestId, `Starting stream from ${ids.join(', ')} for ${accountId}`); @@ -644,6 +757,8 @@ const startWorker = async (workerId) => { // TODO: Replace "string"-based delete payloads with object payloads: const encodedPayload = typeof payload === 'object' ? JSON.stringify(payload) : payload; + messagesSent.labels({ type: destinationType }).inc(1); + log.silly(req.requestId, `Transmitting for ${accountId}: ${event} ${encodedPayload}`); output(event, encodedPayload); }; @@ -775,7 +890,7 @@ const startWorker = async (workerId) => { // constructing the regular expression Object.keys(req.cachedFilters).forEach((key) => { req.cachedFilters[key].regexp = new RegExp(req.cachedFilters[key].keywords.map(([keyword, whole_word]) => { - let expr = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');; + let expr = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); if (whole_word) { if (/^[\w]/.test(expr)) { @@ -842,8 +957,8 @@ const startWorker = async (workerId) => { transmit(event, payload); } }).catch(err => { - releasePgConnection(); log.error(err); + releasePgConnection(); }); }); }; @@ -862,11 +977,20 @@ const startWorker = async (workerId) => { /** * @param {any} req * @param {any} res - * @return {function(string, string): void} + * @returns {function(string, string): void} */ const streamToHttp = (req, res) => { const accountId = req.accountId || req.remoteAddress; + const channelName = channelNameFromPath(req); + + connectedClients.labels({ type: 'eventsource' }).inc(); + + // In theory we'll always have a channel name, but channelNameFromPath can return undefined: + if (typeof channelName === 'string') { + connectedChannels.labels({ type: 'eventsource', channel: channelName }).inc(); + } + res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-store'); res.setHeader('Transfer-Encoding', 'chunked'); @@ -877,6 +1001,14 @@ const startWorker = async (workerId) => { req.on('close', () => { log.verbose(req.requestId, `Ending stream for ${accountId}`); + // We decrement these counters here instead of in streamHttpEnd as in that + // method we don't have knowledge of the channel names + connectedClients.labels({ type: 'eventsource' }).dec(); + // In theory we'll always have a channel name, but channelNameFromPath can return undefined: + if (typeof channelName === 'string') { + connectedChannels.labels({ type: 'eventsource', channel: channelName }).dec(); + } + clearInterval(heartbeat); }); @@ -908,7 +1040,7 @@ const startWorker = async (workerId) => { * @param {any} req * @param {any} ws * @param {string[]} streamName - * @return {function(string, string): void} + * @returns {function(string, string): void} */ const streamToWs = (req, ws, streamName) => (event, payload) => { if (ws.readyState !== ws.OPEN) { @@ -916,7 +1048,11 @@ const startWorker = async (workerId) => { return; } - ws.send(JSON.stringify({ stream: streamName, event, payload })); + ws.send(JSON.stringify({ stream: streamName, event, payload }), (err) => { + if (err) { + log.error(req.requestId, `Failed to send to websocket: ${err}`); + } + }); }; /** @@ -927,24 +1063,23 @@ const startWorker = async (workerId) => { res.end(JSON.stringify({ error: 'Not found' })); }; - app.use(setRequestId); - app.use(setRemoteAddress); - app.use(allowCrossDomain); + const api = express.Router(); - app.get('/api/v1/streaming/health', (req, res) => { - res.writeHead(200, { 'Content-Type': 'text/plain' }); - res.end('OK'); - }); + app.use(api); - app.use(authenticationMiddleware); - app.use(errorMiddleware); + api.use(setRequestId); + api.use(setRemoteAddress); + api.use(allowCrossDomain); - app.get('/api/v1/streaming/*', (req, res) => { + api.use(authenticationMiddleware); + api.use(errorMiddleware); + + api.get('/api/v1/streaming/*', (req, res) => { channelNameToIds(req, channelNameFromPath(req), req.query).then(({ channelIds, options }) => { const onSend = streamToHttp(req, res); const onEnd = streamHttpEnd(req, subscriptionHeartbeat(channelIds)); - streamFrom(channelIds, req, onSend, onEnd, options.needsFiltering); + streamFrom(channelIds, req, onSend, onEnd, 'eventsource', options.needsFiltering); }).catch(err => { log.verbose(req.requestId, 'Subscription error:', err.toString()); httpNotFound(res); @@ -962,7 +1097,7 @@ const startWorker = async (workerId) => { /** * @param {any} req - * @return {string[]} + * @returns {string[]} */ const channelsForUserStream = req => { const arr = [`timeline:${req.accountId}`]; @@ -987,7 +1122,7 @@ const startWorker = async (workerId) => { /** * @param {string} str - * @return {string} + * @returns {string} */ const foldToASCII = str => { const regex = new RegExp(NON_ASCII_CHARS.split('').join('|'), 'g'); @@ -1000,7 +1135,7 @@ const startWorker = async (workerId) => { /** * @param {string} str - * @return {string} + * @returns {string} */ const normalizeHashtag = str => { return foldToASCII(str.normalize('NFKC').toLowerCase()).replace(/[^\p{L}\p{N}_\u00b7\u200c]/gu, ''); @@ -1010,7 +1145,7 @@ const startWorker = async (workerId) => { * @param {any} req * @param {string} name * @param {StreamParams} params - * @return {Promise.<{ channelIds: string[], options: { needsFiltering: boolean } }>} + * @returns {Promise.<{ channelIds: string[], options: { needsFiltering: boolean } }>} */ const channelNameToIds = (req, name, params) => new Promise((resolve, reject) => { switch (name) { @@ -1118,7 +1253,7 @@ const startWorker = async (workerId) => { /** * @param {string} channelName * @param {StreamParams} params - * @return {string[]} + * @returns {string[]} */ const streamNameFromChannelName = (channelName, params) => { if (channelName === 'list') { @@ -1134,15 +1269,16 @@ const startWorker = async (workerId) => { * @typedef WebSocketSession * @property {any} socket * @property {any} request - * @property {Object.} subscriptions + * @property {Object.} subscriptions */ /** * @param {WebSocketSession} session * @param {string} channelName * @param {StreamParams} params + * @returns {void} */ - const subscribeWebsocketToChannel = ({ socket, request, subscriptions }, channelName, params) => + const subscribeWebsocketToChannel = ({ socket, request, subscriptions }, channelName, params) => { checkScopes(request, channelName).then(() => channelNameToIds(request, channelName, params)).then(({ channelIds, options, @@ -1153,9 +1289,12 @@ const startWorker = async (workerId) => { const onSend = streamToWs(request, socket, streamNameFromChannelName(channelName, params)); const stopHeartbeat = subscriptionHeartbeat(channelIds); - const listener = streamFrom(channelIds, request, onSend, undefined, options.needsFiltering); + const listener = streamFrom(channelIds, request, onSend, undefined, 'websocket', options.needsFiltering); + + connectedChannels.labels({ type: 'websocket', channel: channelName }).inc(); subscriptions[channelIds.join(';')] = { + channelName, listener, stopHeartbeat, }; @@ -1163,35 +1302,47 @@ const startWorker = async (workerId) => { log.verbose(request.requestId, 'Subscription error:', err.toString()); socket.send(JSON.stringify({ error: err.toString() })); }); + } + + + const removeSubscription = (subscriptions, channelIds, request) => { + log.verbose(request.requestId, `Ending stream from ${channelIds.join(', ')} for ${request.accountId}`); + + const subscription = subscriptions[channelIds.join(';')]; + + if (!subscription) { + return; + } + + channelIds.forEach(channelId => { + unsubscribe(`${redisPrefix}${channelId}`, subscription.listener); + }); + + connectedChannels.labels({ type: 'websocket', channel: subscription.channelName }).dec(); + subscription.stopHeartbeat(); + + delete subscriptions[channelIds.join(';')]; + } /** * @param {WebSocketSession} session * @param {string} channelName * @param {StreamParams} params + * @returns {void} */ - const unsubscribeWebsocketFromChannel = ({ socket, request, subscriptions }, channelName, params) => + const unsubscribeWebsocketFromChannel = ({ socket, request, subscriptions }, channelName, params) => { channelNameToIds(request, channelName, params).then(({ channelIds }) => { - log.verbose(request.requestId, `Ending stream from ${channelIds.join(', ')} for ${request.accountId}`); - - const subscription = subscriptions[channelIds.join(';')]; - - if (!subscription) { - return; - } - - const { listener, stopHeartbeat } = subscription; - - channelIds.forEach(channelId => { - unsubscribe(`${redisPrefix}${channelId}`, listener); - }); - - stopHeartbeat(); - - delete subscriptions[channelIds.join(';')]; + removeSubscription(subscriptions, channelIds, request); }).catch(err => { - log.verbose(request.requestId, 'Unsubscription error:', err); - socket.send(JSON.stringify({ error: err.toString() })); + log.verbose(request.requestId, 'Unsubscribe error:', err); + + // If we have a socket that is alive and open still, send the error back to the client: + // FIXME: In other parts of the code ws === socket + if (socket.isAlive && socket.readyState === socket.OPEN) { + socket.send(JSON.stringify({ error: "Error unsubscribing from channel" })); + } }); + } /** * @param {WebSocketSession} session @@ -1212,21 +1363,25 @@ const startWorker = async (workerId) => { subscribe(`${redisPrefix}${systemChannelId}`, listener); subscriptions[accessTokenChannelId] = { + channelName: 'system', listener, stopHeartbeat: () => { }, }; subscriptions[systemChannelId] = { + channelName: 'system', listener, stopHeartbeat: () => { }, }; + + connectedChannels.labels({ type: 'websocket', channel: 'system' }).inc(2); }; /** * @param {string|string[]} arrayOrString - * @return {string} + * @returns {string} */ const firstParam = arrayOrString => { if (Array.isArray(arrayOrString)) { @@ -1237,13 +1392,17 @@ const startWorker = async (workerId) => { }; wss.on('connection', (ws, req) => { - const location = url.parse(req.url, true); + // Note: url.parse could throw, which would terminate the connection, so we + // increment the connected clients metric straight away when we establish + // the connection, without waiting: + connectedClients.labels({ type: 'websocket' }).inc(); + // Setup request properties: req.requestId = uuid.v4(); req.remoteAddress = ws._socket.remoteAddress; + // Setup connection keep-alive state: ws.isAlive = true; - ws.on('pong', () => { ws.isAlive = true; }); @@ -1257,26 +1416,31 @@ const startWorker = async (workerId) => { subscriptions: {}, }; - const onEnd = () => { - const keys = Object.keys(session.subscriptions); + ws.on('close', function onWebsocketClose() { + const subscriptions = Object.keys(session.subscriptions); - keys.forEach(channelIds => { - const { listener, stopHeartbeat } = session.subscriptions[channelIds]; - - channelIds.split(';').forEach(channelId => { - unsubscribe(`${redisPrefix}${channelId}`, listener); - }); - - stopHeartbeat(); + subscriptions.forEach(channelIds => { + removeSubscription(session.subscriptions, channelIds.split(';'), req) }); - }; - ws.on('close', onEnd); - ws.on('error', onEnd); + // Decrement the metrics for connected clients: + connectedClients.labels({ type: 'websocket' }).dec(); + + // ensure garbage collection: + session.socket = null; + session.request = null; + session.subscriptions = {}; + }); + + // Note: immediately after the `error` event is emitted, the `close` event + // is emitted. As such, all we need to do is log the error here. + ws.on('error', (err) => { + log.error('websocket', err.toString()); + }); ws.on('message', (data, isBinary) => { if (isBinary) { - log.warn('socket', 'Received binary data, closing connection'); + log.warn('websocket', 'Received binary data, closing connection'); ws.close(1003, 'The mastodon streaming server does not support binary messages'); return; } @@ -1299,7 +1463,10 @@ const startWorker = async (workerId) => { subscribeWebsocketToSystemChannel(session); - if (location.query.stream) { + // Parse the URL for the connection arguments (if supplied), url.parse can throw: + const location = req.url && url.parse(req.url, true); + + if (location && location.query.stream) { subscribeWebsocketToChannel(session, firstParam(location.query.stream), location.query); } }); @@ -1317,11 +1484,10 @@ const startWorker = async (workerId) => { }, 30000); attachServerWithConfig(server, address => { - log.warn(`Worker ${workerId} now listening on ${address}`); + log.warn(`Streaming API now listening on ${address}`); }); const onExit = () => { - log.warn(`Worker ${workerId} exiting`); server.close(); process.exit(0); }; @@ -1359,34 +1525,4 @@ const attachServerWithConfig = (server, onSuccess) => { } }; -/** - * @param {function(Error=): void} onSuccess - */ -const onPortAvailable = onSuccess => { - const testServer = http.createServer(); - - testServer.once('error', err => { - onSuccess(err); - }); - - testServer.once('listening', () => { - testServer.once('close', () => onSuccess()); - testServer.close(); - }); - - attachServerWithConfig(testServer); -}; - -onPortAvailable(err => { - if (err) { - log.error('Could not start server, the port or socket is in use'); - return; - } - - throng({ - workers: numWorkers, - lifetime: Infinity, - start: startWorker, - master: startMaster, - }); -}); +startServer(); diff --git a/stylelint.config.js b/stylelint.config.js index 0f8267a81..9f9509163 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -2,27 +2,45 @@ module.exports = { extends: ['stylelint-config-standard-scss'], ignoreFiles: [ 'app/javascript/styles/mastodon/reset.scss', + 'coverage/**/*', 'node_modules/**/*', + 'public/assets/**/*', + 'public/packs/**/*', + 'public/packs-test/**/*', 'vendor/**/*', ], + reportDescriptionlessDisables: true, + reportInvalidScopeDisables: true, + reportNeedlessDisables: true, rules: { 'at-rule-empty-line-before': null, 'color-function-notation': null, 'color-hex-length': null, 'declaration-block-no-redundant-longhand-properties': null, - 'max-line-length': null, 'no-descending-specificity': null, 'no-duplicate-selectors': null, 'number-max-precision': 8, - 'property-no-unknown': null, 'property-no-vendor-prefix': null, 'selector-class-pattern': null, 'selector-id-pattern': null, - 'string-quotes': null, 'value-keyword-case': null, 'value-no-vendor-prefix': null, 'scss/dollar-variable-empty-line-before': null, 'scss/no-global-function-names': null, }, + overrides: [ + { + 'files': ['app/javascript/styles/mailer.scss'], + rules: { + 'property-no-unknown': [ + true, + { + ignoreProperties: [ + '/^mso-/', + ] }, + ], + }, + }, + ], }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..ff9da2949 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "target": "esnext", + "module": "CommonJS", + "moduleResolution": "node", + "allowJs": true, + "noEmit": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "baseUrl": "./", + "paths": { + "mastodon": ["app/javascript/mastodon"], + "mastodon/*": ["app/javascript/mastodon/*"] + } + }, + "include": [ + "app/javascript/mastodon", + "app/javascript/packs", + "app/javascript/types" + ] +} diff --git a/yarn.lock b/yarn.lock index 9351abfbc..70b791d8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,538 +2,363 @@ # yarn lockfile v1 -"@adobe/css-tools@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd" - integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g== +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== -"@ampproject/remapping@^2.1.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" - integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== +"@adobe/css-tools@^4.0.1": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.0.tgz#1991d273fb29edbd2f63060f5bdaf0af26aa64e3" + integrity sha512-+RNNcQvw2V1bmnBTPAtOLfW/9mhH2vC67+rUSi5T8EtEWt6lEnGNY2GuhZ1/YwbgikT1TkhvidCDmN5Q5YCo/w== + +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== dependencies: - "@jridgewell/trace-mapping" "^0.3.0" + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" "@apideck/better-ajv-errors@^0.3.1": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.3.tgz#ab0b1e981e1749bf59736cf7ebe25cfc9f949c15" - integrity sha512-9o+HO2MbJhJHjDYZaDxJmSDckvDpiuItEsrIShV0DXeCshXWRHhqYyU/PKHMkuClOmFnZhRd6wzv4vpDu/dRKg== + version "0.3.6" + resolved "https://registry.yarnpkg.com/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz#957d4c28e886a64a8141f7522783be65733ff097" + integrity sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA== dependencies: json-schema "^0.4.0" jsonpointer "^5.0.0" leven "^3.1.0" -"@babel/code-frame@7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== +"@babel/code-frame@^7.0.0": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.10.tgz#1c20e612b768fefa75f6e90d6ecb86329247f0a3" + integrity sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA== dependencies: - "@babel/highlight" "^7.10.4" + "@babel/highlight" "^7.22.10" + chalk "^2.4.2" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== +"@babel/code-frame@^7.10.4": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" + integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== dependencies: - "@babel/highlight" "^7.18.6" + "@babel/highlight" "^7.22.5" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.19.3", "@babel/compat-data@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.4.tgz#95c86de137bf0317f3a570e1b6e996b427299747" - integrity sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw== - -"@babel/core@^7.11.1", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.19.6", "@babel/core@^7.7.2": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.6.tgz#7122ae4f5c5a37c0946c066149abd8e75f81540f" - integrity sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg== +"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.22.5": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.6" - "@babel/helper-compilation-targets" "^7.19.3" - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helpers" "^7.19.4" - "@babel/parser" "^7.19.6" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.6" - "@babel/types" "^7.19.4" + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + +"@babel/compat-data@^7.22.20", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0" + integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw== + +"@babel/core@^7.10.4", "@babel/core@^7.11.1", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.22.1": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.20.tgz#e3d0eed84c049e2a2ae0a64d27b6a37edec385b7" + integrity sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.22.15" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-module-transforms" "^7.22.20" + "@babel/helpers" "^7.22.15" + "@babel/parser" "^7.22.16" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.22.20" + "@babel/types" "^7.22.19" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.1" - semver "^6.3.0" + json5 "^2.2.3" + semver "^6.3.1" -"@babel/eslint-parser@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz#4f68f6b0825489e00a24b41b6a1ae35414ecd2f4" - integrity sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ== +"@babel/generator@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.15.tgz#1564189c7ec94cb8f77b5e8a90c4d200d21b2339" + integrity sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA== dependencies: - "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" - eslint-visitor-keys "^2.1.0" - semver "^6.3.0" - -"@babel/generator@^7.19.6", "@babel/generator@^7.7.2": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.6.tgz#9e481a3fe9ca6261c972645ae3904ec0f9b34a1d" - integrity sha512-oHGRUQeoX1QrKeJIKVe0hwjGqNnVYsM5Nep5zo0uE0m42sLH+Fsd2pStJ5sRM1bNyTUUoz0pe2lTeMJrb/taTA== - dependencies: - "@babel/types" "^7.19.4" + "@babel/types" "^7.22.15" "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" - integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== +"@babel/generator@^7.7.2": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.10.tgz#c92254361f398e160645ac58831069707382b722" + integrity sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.22.10" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.6.tgz#f14d640ed1ee9246fb33b8255f08353acfe70e6a" - integrity sha512-KT10c1oWEpmrIRYnthbzHgoOf6B+Xd6a5yhdbNtdhtG7aO1or5HViuf1TQR36xY/QprXA5nvxO6nAjhJ4y38jw== +"@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== dependencies: - "@babel/helper-explode-assignable-expression" "^7.18.6" - "@babel/types" "^7.18.6" + "@babel/types" "^7.22.5" -"@babel/helper-builder-react-jsx@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.18.6.tgz#b3a302c0eb4949e5356b400cb752a91e93bf9b79" - integrity sha512-2ndBVP5f9zwHWQeBr5EgqTAvFhPDViMW969bbJzRhKUUylnC39CdFZdVmqk+UtkxIpwm/efPgm3SzXUSlJnjAw== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" + integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/types" "^7.18.6" + "@babel/types" "^7.22.15" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz#a10a04588125675d7c7ae299af86fa1b2ee038ca" - integrity sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg== +"@babel/helper-builder-react-jsx@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.22.5.tgz#9b325d4558fb51b0bb51e4837a2bf8f707029e97" + integrity sha512-GYZBuAC9Vl4jnPun18TeNGyqkKWQ+3AtZHbgnrdT//0yCV+qcFyXj0X+9DJyD2jYi0C+55gRcUAhE35sk2Mm9g== dependencies: - "@babel/compat-data" "^7.19.3" - "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.21.3" - semver "^6.3.0" + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/types" "^7.22.5" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b" - integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== +"@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" + integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-member-expression-to-functions" "^7.18.9" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.9" - "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/compat-data" "^7.22.9" + "@babel/helper-validator-option" "^7.22.15" + browserslist "^4.21.9" + lru-cache "^5.1.1" + semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz#3e35f4e04acbbf25f1b3534a657610a000543d3c" - integrity sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A== +"@babel/helper-create-class-features-plugin@^7.22.11", "@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz#97a61b385e57fe458496fad19f8e63b63c867de4" + integrity sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - regexpu-core "^5.1.0" + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-member-expression-to-functions" "^7.22.15" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" - integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" + integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - regexpu-core "^5.1.0" + "@babel/helper-annotate-as-pure" "^7.22.5" + regexpu-core "^5.3.1" + semver "^6.3.1" -"@babel/helper-define-polyfill-provider@^0.3.3": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" - integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== +"@babel/helper-define-polyfill-provider@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz#82c825cadeeeee7aad237618ebbe8fa1710015d7" + integrity sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw== dependencies: - "@babel/helper-compilation-targets" "^7.17.7" - "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" debug "^4.1.1" lodash.debounce "^4.0.8" resolve "^1.14.2" - semver "^6.1.2" -"@babel/helper-environment-visitor@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz#b7eee2b5b9d70602e59d1a6cad7dd24de7ca6cd7" - integrity sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q== +"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== - -"@babel/helper-explode-assignable-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" - integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== +"@babel/helper-function-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" + integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== dependencies: - "@babel/types" "^7.18.6" + "@babel/template" "^7.22.5" + "@babel/types" "^7.22.5" -"@babel/helper-function-name@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz#8334fecb0afba66e6d87a7e8c6bb7fed79926b83" - integrity sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw== +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== dependencies: - "@babel/template" "^7.18.6" - "@babel/types" "^7.18.6" + "@babel/types" "^7.22.5" -"@babel/helper-function-name@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz#940e6084a55dee867d33b4e487da2676365e86b0" - integrity sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A== +"@babel/helper-member-expression-to-functions@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz#b95a144896f6d491ca7863576f820f3628818621" + integrity sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA== dependencies: - "@babel/template" "^7.18.6" - "@babel/types" "^7.18.9" + "@babel/types" "^7.22.15" -"@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== +"@babel/helper-module-imports@^7.0.0-beta.49", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.16.7": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" + integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" + "@babel/types" "^7.22.5" -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== +"@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.22.15" -"@babel/helper-member-expression-to-functions@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" - integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== +"@babel/helper-module-transforms@^7.22.15", "@babel/helper-module-transforms@^7.22.20", "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.20.tgz#da9edc14794babbe7386df438f3768067132f59e" + integrity sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A== dependencies: - "@babel/types" "^7.18.9" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" -"@babel/helper-module-imports@^7.0.0-beta.49", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== +"@babel/helper-optimise-call-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.22.5" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0", "@babel/helper-module-transforms@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz#6c52cc3ac63b70952d33ee987cbee1c9368b533f" - integrity sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + +"@babel/helper-remap-async-to-generator@^7.22.5", "@babel/helper-remap-async-to-generator@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" + integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.19.4" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.6" - "@babel/types" "^7.19.4" + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-wrap-function" "^7.22.20" -"@babel/helper-optimise-call-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" - integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== +"@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" + integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw== dependencies: - "@babel/types" "^7.18.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.22.15" + "@babel/helper-optimise-call-expression" "^7.22.5" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" - integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== - -"@babel/helper-remap-async-to-generator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.6.tgz#fa1f81acd19daee9d73de297c0308783cd3cfc23" - integrity sha512-z5wbmV55TveUPZlCLZvxWHtrjuJd+8inFhk7DG0WW87/oJuGDcjDiu7HIvGcpf5464L6xKCg3vNkmlVVz9hwyQ== +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.6" - "@babel/helper-wrap-function" "^7.18.6" - "@babel/types" "^7.18.6" + "@babel/types" "^7.22.5" -"@babel/helper-remap-async-to-generator@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" - integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== +"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" + integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-wrap-function" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/types" "^7.22.5" -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9", "@babel/helper-replace-supers@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" - integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.18.9" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.19.1" - "@babel/types" "^7.19.0" + "@babel/types" "^7.22.5" -"@babel/helper-simple-access@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" - integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== + +"@babel/helper-validator-identifier@^7.22.19", "@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.22.5": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" + integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA== + +"@babel/helper-wrap-function@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" + integrity sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw== dependencies: - "@babel/types" "^7.18.6" + "@babel/helper-function-name" "^7.22.5" + "@babel/template" "^7.22.15" + "@babel/types" "^7.22.19" -"@babel/helper-simple-access@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz#be553f4951ac6352df2567f7daa19a0ee15668e7" - integrity sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg== +"@babel/helpers@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.15.tgz#f09c3df31e86e3ea0b7ff7556d85cdebd47ea6f1" + integrity sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw== dependencies: - "@babel/types" "^7.19.4" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.22.15" + "@babel/types" "^7.22.15" -"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818" - integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw== +"@babel/highlight@^7.22.10": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.13.tgz#9cda839e5d3be9ca9e8c26b6dd69e7548f0cbf16" + integrity sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ== dependencies: - "@babel/types" "^7.18.9" - -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== - -"@babel/helper-validator-identifier@^7.12.11": - version "7.12.11" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" - integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== - -"@babel/helper-validator-identifier@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" - integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== - -"@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== - -"@babel/helper-validator-option@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" - integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== - -"@babel/helper-wrap-function@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.6.tgz#ec44ea4ad9d8988b90c3e465ba2382f4de81a073" - integrity sha512-I5/LZfozwMNbwr/b1vhhuYD+J/mU+gfGAj5td7l5Rv9WYmH6i3Om69WGKNmlIpsVW/mF6O5bvTKbvDQZVgjqOw== - dependencies: - "@babel/helper-function-name" "^7.18.6" - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.6" - "@babel/types" "^7.18.6" - -"@babel/helper-wrap-function@^7.18.9": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.10.tgz#a7fcd3ab9b1be4c9b52cf7d7fdc1e88c2ce93396" - integrity sha512-95NLBP59VWdfK2lyLKe6eTMq9xg+yWKzxzxbJ1wcYNi1Auz200+83fMDADjRxBvc2QQor5zja2yTQzXGhk2GtQ== - dependencies: - "@babel/helper-function-name" "^7.18.9" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.18.10" - "@babel/types" "^7.18.10" - -"@babel/helpers@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.4.tgz#42154945f87b8148df7203a25c31ba9a73be46c5" - integrity sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw== - dependencies: - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.4" - "@babel/types" "^7.19.4" - -"@babel/highlight@^7.10.4": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c" - integrity sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww== - dependencies: - "@babel/helper-validator-identifier" "^7.12.11" - chalk "^2.0.0" + "@babel/helper-validator-identifier" "^7.22.5" + chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.6.tgz#b923430cb94f58a7eae8facbffa9efd19130e7f8" - integrity sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA== - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" - integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== +"@babel/highlight@^7.22.5": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.10.tgz#02a3f6d8c1cb4521b2fd0ab0da8f4739936137d7" + integrity sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-identifier" "^7.22.5" + chalk "^2.4.2" + js-tokens "^4.0.0" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz#a11af19aa373d68d561f08e0a57242350ed0ec50" - integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" - "@babel/plugin-proposal-optional-chaining" "^7.18.9" +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.16", "@babel/parser@^7.22.5": + version "7.22.16" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.16.tgz#180aead7f247305cce6551bea2720934e2fa2c95" + integrity sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA== -"@babel/plugin-proposal-async-generator-functions@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz#34f6f5174b688529342288cd264f80c9ea9fb4a7" - integrity sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-remap-async-to-generator" "^7.18.9" - "@babel/plugin-syntax-async-generators" "^7.8.4" +"@babel/parser@^7.14.7": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.10.tgz#e37634f9a12a1716136c44624ef54283cabd3f55" + integrity sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ== -"@babel/plugin-proposal-class-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" - integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz#02dc8a03f613ed5fdc29fb2f728397c78146c962" + integrity sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-proposal-class-static-block@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz#8aa81d403ab72d3962fc06c26e222dacfc9b9020" - integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz#2aeb91d337d4e1a1e7ce85b76a37f5301781200f" + integrity sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ== dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.22.15" -"@babel/plugin-proposal-decorators@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.19.6.tgz#0f1af5c21957e9a438cc1d08d2a6a6858af127b7" - integrity sha512-PKWforYpkVkogpOW0RaPuh7eQ7AoFgBJP+d87tQCRY2LVbvyGtfRM7RtrhCBsNgZb+2EY28SeWB6p2xe1Z5oAw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-replace-supers" "^7.19.1" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/plugin-syntax-decorators" "^7.19.0" - -"@babel/plugin-proposal-dynamic-import@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94" - integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - -"@babel/plugin-proposal-export-namespace-from@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203" - integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-proposal-json-strings@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b" - integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-json-strings" "^7.8.3" - -"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz#8148cbb350483bf6220af06fa6db3690e14b2e23" - integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" - integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - -"@babel/plugin-proposal-numeric-separator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75" - integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-proposal-object-rest-spread@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz#a8fc86e8180ff57290c91a75d83fe658189b642d" - integrity sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q== - dependencies: - "@babel/compat-data" "^7.19.4" - "@babel/helper-compilation-targets" "^7.19.3" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.18.8" - -"@babel/plugin-proposal-optional-catch-binding@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" - integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - -"@babel/plugin-proposal-optional-chaining@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993" - integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== - dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - -"@babel/plugin-proposal-private-methods@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" - integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-proposal-private-property-in-object@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz#a64137b232f0aca3733a67eb1a144c192389c503" - integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - -"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" - integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -563,13 +388,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-decorators@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.19.0.tgz#5f13d1d8fce96951bea01a10424463c9a5b3a599" - integrity sha512-xaBZUEDntt4faL1yN8oIFlhfXeQAWJW7CLKYsHTUqriCUbj8xOra8bfxxKGi/UwExPFBuPdH4XfHc9rGQhrVkQ== - dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" @@ -584,14 +402,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-import-assertions@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz#cd6190500a4fa2fe31990a963ffab4b63e4505e4" - integrity sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ== +"@babel/plugin-syntax-import-assertions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz#07d252e2aa0bc6125567f742cd58619cb14dce98" + integrity sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-syntax-import-meta@^7.8.3": +"@babel/plugin-syntax-import-attributes@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz#ab840248d834410b829f569f5262b9e517555ecb" + integrity sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== @@ -605,19 +430,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.12.13": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665" - integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q== +"@babel/plugin-syntax-jsx@7", "@babel/plugin-syntax-jsx@^7.22.5", "@babel/plugin-syntax-jsx@^7.7.2": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" + integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-syntax-jsx@^7.18.6", "@babel/plugin-syntax-jsx@^7.7.2": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" - integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" @@ -675,351 +493,499 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.7.2": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz#b82c6ce471b165b5ce420cf92914d6fb46225716" - integrity sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q== +"@babel/plugin-syntax-typescript@^7.22.5", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" + integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== dependencies: - "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-arrow-functions@^7.18.6": +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" - integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-async-to-generator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz#ccda3d1ab9d5ced5265fdb13f1882d5476c71615" - integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== +"@babel/plugin-transform-arrow-functions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958" + integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw== dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-remap-async-to-generator" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-block-scoped-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" - integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== +"@babel/plugin-transform-async-generator-functions@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.15.tgz#3b153af4a6b779f340d5b80d3f634f55820aefa3" + integrity sha512-jBm1Es25Y+tVoTi5rfd5t1KLmL8ogLKpXszboWOTTtGFGz2RKnQe2yn7HbZ+kb/B8N0FVSGQo874NSlOU1T4+w== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-transform-block-scoping@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.19.4.tgz#315d70f68ce64426db379a3d830e7ac30be02e9b" - integrity sha512-934S2VLLlt2hRJwPf4MczaOr4hYF0z+VKPwqTNxyKX7NthTiPfhuKFWQZHXRM0vh/wo/VyXB3s4bZUNA08l+tQ== +"@babel/plugin-transform-async-to-generator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775" + integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-remap-async-to-generator" "^7.22.5" -"@babel/plugin-transform-classes@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz#0e61ec257fba409c41372175e7c1e606dc79bb20" - integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== +"@babel/plugin-transform-block-scoped-functions@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024" + integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.19.0" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-replace-supers" "^7.18.9" - "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-block-scoping@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.15.tgz#494eb82b87b5f8b1d8f6f28ea74078ec0a10a841" + integrity sha512-G1czpdJBZCtngoK1sJgloLiOHUnkb/bLZwqVZD8kXmq0ZnVfTTWUcs9OWtp0mBtYJ+4LQY1fllqBkOIPhXmFmw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-class-properties@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz#97a56e31ad8c9dc06a0b3710ce7803d5a48cca77" + integrity sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-class-static-block@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz#dc8cc6e498f55692ac6b4b89e56d87cec766c974" + integrity sha512-GMM8gGmqI7guS/llMFk1bJDkKfn3v3C4KHK9Yg1ey5qcHcOlKb0QvcMrgzvxo+T03/4szNh5lghY+fEC98Kq9g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.22.11" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-transform-classes@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz#aaf4753aee262a232bbc95451b4bdf9599c65a0b" + integrity sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.9" + "@babel/helper-split-export-declaration" "^7.22.6" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" - integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== +"@babel/plugin-transform-computed-properties@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869" + integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/template" "^7.22.5" -"@babel/plugin-transform-destructuring@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.19.4.tgz#46890722687b9b89e1369ad0bd8dc6c5a3b4319d" - integrity sha512-t0j0Hgidqf0aM86dF8U+vXYReUgJnlv4bZLsyoPnwZNrGY+7/38o8YjaELrvHeVfTZao15kjR0PVv0nju2iduA== +"@babel/plugin-transform-destructuring@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.15.tgz#e7404ea5bb3387073b9754be654eecb578324694" + integrity sha512-HzG8sFl1ZVGTme74Nw+X01XsUTqERVQ6/RLHo3XjGRzm7XD6QTtfS3NJotVgCGy8BzkDqRjRBD8dAyJn5TuvSQ== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" - integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== +"@babel/plugin-transform-dotall-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165" + integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-duplicate-keys@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" - integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== +"@babel/plugin-transform-duplicate-keys@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285" + integrity sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-exponentiation-operator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" - integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== +"@babel/plugin-transform-dynamic-import@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz#2c7722d2a5c01839eaf31518c6ff96d408e447aa" + integrity sha512-g/21plo58sfteWjaO0ZNVb+uEOkJNjAaHhbejrnBmu011l/eNDScmkbjCC3l4FKb10ViaGU4aOkFznSu2zRHgA== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-transform-for-of@^7.18.8": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" - integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== +"@babel/plugin-transform-exponentiation-operator@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a" + integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-function-name@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" - integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== +"@babel/plugin-transform-export-namespace-from@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz#b3c84c8f19880b6c7440108f8929caf6056db26c" + integrity sha512-xa7aad7q7OiT8oNZ1mU7NrISjlSkVdMbNxn9IuLZyL9AJEhs1Apba3I+u5riX1dIkdptP5EKDG5XDPByWxtehw== dependencies: - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-transform-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" - integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== +"@babel/plugin-transform-for-of@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.15.tgz#f64b4ccc3a4f131a996388fae7680b472b306b29" + integrity sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-member-expression-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" - integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== +"@babel/plugin-transform-function-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143" + integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-compilation-targets" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-modules-amd@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz#8c91f8c5115d2202f277549848874027d7172d21" - integrity sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg== +"@babel/plugin-transform-json-strings@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz#689a34e1eed1928a40954e37f74509f48af67835" + integrity sha512-CxT5tCqpA9/jXFlme9xIBCc5RPtdDq3JpkkhgHQqtDdiTnTI0jtZ0QzXhr5DILeYifDPp2wvY2ad+7+hLMW5Pw== dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-transform-modules-commonjs@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz#afd243afba166cca69892e24a8fd8c9f2ca87883" - integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q== +"@babel/plugin-transform-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920" + integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g== dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-simple-access" "^7.18.6" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-modules-systemjs@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz#5f20b471284430f02d9c5059d9b9a16d4b085a1f" - integrity sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A== +"@babel/plugin-transform-logical-assignment-operators@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz#24c522a61688bde045b7d9bc3c2597a4d948fc9c" + integrity sha512-qQwRTP4+6xFCDV5k7gZBF3C31K34ut0tbEcTKxlX/0KXxm9GLcO14p570aWxFvVzx6QAfPgq7gaeIHXJC8LswQ== dependencies: - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-validator-identifier" "^7.18.6" - babel-plugin-dynamic-import-node "^2.3.3" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-transform-modules-umd@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" - integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== +"@babel/plugin-transform-member-expression-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def" + integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew== dependencies: - "@babel/helper-module-transforms" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz#ec7455bab6cd8fb05c525a94876f435a48128888" - integrity sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw== +"@babel/plugin-transform-modules-amd@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz#4e045f55dcf98afd00f85691a68fc0780704f526" + integrity sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-new-target@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8" - integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== +"@babel/plugin-transform-modules-commonjs@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.15.tgz#b11810117ed4ee7691b29bd29fd9f3f98276034f" + integrity sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-module-transforms" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" -"@babel/plugin-transform-object-super@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" - integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== +"@babel/plugin-transform-modules-systemjs@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz#3386be5875d316493b517207e8f1931d93154bb1" + integrity sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.6" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-module-transforms" "^7.22.9" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" -"@babel/plugin-transform-parameters@^7.18.8": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz#ee9f1a0ce6d78af58d0956a9378ea3427cccb48a" - integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg== +"@babel/plugin-transform-modules-umd@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98" + integrity sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-module-transforms" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-property-literals@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" - integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== +"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" + integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-react-display-name@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz#8b1125f919ef36ebdfff061d664e266c666b9415" - integrity sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA== +"@babel/plugin-transform-new-target@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d" + integrity sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-react-inline-elements@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-inline-elements/-/plugin-transform-react-inline-elements-7.18.6.tgz#d0676948eb5a11d547de6add7e8a2c522ec708f5" - integrity sha512-uo3yD1EXhDxmk1Y/CeFDdHS5t22IOUBooLPFOrrjfpYmDM9Vg61xbIaWeWkbYQ7Aq0zMf30/FfKoQgFwyqw6Bg== +"@babel/plugin-transform-nullish-coalescing-operator@^7.22.11", "@babel/plugin-transform-nullish-coalescing-operator@^7.22.3": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz#debef6c8ba795f5ac67cd861a81b744c5d38d9fc" + integrity sha512-YZWOw4HxXrotb5xsjMJUDlLgcDXSfO9eCmdl1bgW4+/lAGdkjaEvOnQ4p5WKKdUgSzO39dgPl0pTnfxm0OAXcg== dependencies: - "@babel/helper-builder-react-jsx" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-transform-react-jsx-development@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz#dbe5c972811e49c7405b630e4d0d2e1380c0ddc5" - integrity sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA== +"@babel/plugin-transform-numeric-separator@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz#498d77dc45a6c6db74bb829c02a01c1d719cbfbd" + integrity sha512-3dzU4QGPsILdJbASKhF/V2TVP+gJya1PsueQCxIPCEcerqF21oEcrob4mzjsp2Py/1nLfF5m+xYNMDpmA8vffg== dependencies: - "@babel/plugin-transform-react-jsx" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-transform-react-jsx@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.18.6.tgz#2721e96d31df96e3b7ad48ff446995d26bc028ff" - integrity sha512-Mz7xMPxoy9kPS/JScj6fJs03TZ/fZ1dJPlMjRAgTaxaS0fUBk8FV/A2rRgfPsVCZqALNwMexD+0Uaf5zlcKPpw== +"@babel/plugin-transform-object-rest-spread@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz#21a95db166be59b91cde48775310c0df6e1da56f" + integrity sha512-fEB+I1+gAmfAyxZcX1+ZUwLeAuuf8VIg67CTznZE0MqVFumWkh8xWtn58I4dxdVf080wn7gzWoF8vndOViJe9Q== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-syntax-jsx" "^7.18.6" - "@babel/types" "^7.18.6" + "@babel/compat-data" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.22.15" -"@babel/plugin-transform-react-pure-annotations@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz#561af267f19f3e5d59291f9950fd7b9663d0d844" - integrity sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ== +"@babel/plugin-transform-object-super@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c" + integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw== dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.5" -"@babel/plugin-transform-regenerator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73" - integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== +"@babel/plugin-transform-optional-catch-binding@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz#461cc4f578a127bb055527b3e77404cad38c08e0" + integrity sha512-rli0WxesXUeCJnMYhzAglEjLWVDF6ahb45HuprcmQuLidBJFWjNnOzssk2kuc6e33FlLaiZhG/kUIzUMWdBKaQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - regenerator-transform "^0.15.0" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-transform-reserved-words@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" - integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== +"@babel/plugin-transform-optional-chaining@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.15.tgz#d7a5996c2f7ca4ad2ad16dbb74444e5c4385b1ba" + integrity sha512-ngQ2tBhq5vvSJw2Q2Z9i7ealNkpDMU0rGWnHPKqRZO0tzZ5tlaoz4hDvhXioOoaE0X2vfNss1djwg0DXlfu30A== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-transform-runtime@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.6.tgz#9d2a9dbf4e12644d6f46e5e75bfbf02b5d6e9194" - integrity sha512-PRH37lz4JU156lYFW1p8OxE5i7d6Sl/zV58ooyr+q1J1lnQPyg5tIiXlIwNVhJaY4W3TmOtdc8jqdXQcB1v5Yw== +"@babel/plugin-transform-parameters@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz#719ca82a01d177af358df64a514d64c2e3edb114" + integrity sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ== dependencies: - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.19.0" - babel-plugin-polyfill-corejs2 "^0.3.3" - babel-plugin-polyfill-corejs3 "^0.6.0" - babel-plugin-polyfill-regenerator "^0.4.1" - semver "^6.3.0" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-shorthand-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" - integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== +"@babel/plugin-transform-private-methods@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz#21c8af791f76674420a147ae62e9935d790f8722" + integrity sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-spread@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" - integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== +"@babel/plugin-transform-private-property-in-object@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz#ad45c4fc440e9cb84c718ed0906d96cf40f9a4e1" + integrity sha512-sSCbqZDBKHetvjSwpyWzhuHkmW5RummxJBVbYLkGkaiTOWGxml7SXt0iWa03bzxFIx7wOj3g/ILRd0RcJKBeSQ== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.11" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" -"@babel/plugin-transform-sticky-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" - integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== +"@babel/plugin-transform-property-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766" + integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-template-literals@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" - integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== +"@babel/plugin-transform-react-constant-elements@^7.12.1": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.22.5.tgz#6dfa7c1c37f7d7279e417ceddf5a04abb8bb9c29" + integrity sha512-BF5SXoO+nX3h5OhlN78XbbDrBOffv+AxPP2ENaJOVqjWCgBDeOY3WcaUcddutGSfoap+5NEQ/q/4I3WZIvgkXA== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-typeof-symbol@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" - integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== +"@babel/plugin-transform-react-display-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz#3c4326f9fce31c7968d6cb9debcaf32d9e279a2b" + integrity sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-unicode-escapes@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" - integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== +"@babel/plugin-transform-react-inline-elements@^7.21.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-inline-elements/-/plugin-transform-react-inline-elements-7.22.5.tgz#f158486764de87f84706f5e9c98506df3015d9a4" + integrity sha512-m+OHS1E33wsWyv37bQXNzY/AB7vMTR1BYGG/KW+HGHdKeQS03sUAweNdGaDh8wKmAqh6ZbRRtFjPbhyYFToSbQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-builder-react-jsx" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-unicode-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" - integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== +"@babel/plugin-transform-react-jsx-development@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz#e716b6edbef972a92165cd69d92f1255f7e73e87" + integrity sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-react-jsx" "^7.22.5" -"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.4.tgz#4c91ce2e1f994f717efb4237891c3ad2d808c94b" - integrity sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg== +"@babel/plugin-transform-react-jsx@^7.22.15", "@babel/plugin-transform-react-jsx@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz#7e6266d88705d7c49f11c98db8b9464531289cd6" + integrity sha512-oKckg2eZFa8771O/5vi7XeTvmM6+O9cxZu+kanTU7tD4sin5nO/G8jGJhq8Hvt2Z0kUoEDRayuZLaUlYl8QuGA== dependencies: - "@babel/compat-data" "^7.19.4" - "@babel/helper-compilation-targets" "^7.19.3" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-async-generator-functions" "^7.19.1" - "@babel/plugin-proposal-class-properties" "^7.18.6" - "@babel/plugin-proposal-class-static-block" "^7.18.6" - "@babel/plugin-proposal-dynamic-import" "^7.18.6" - "@babel/plugin-proposal-export-namespace-from" "^7.18.9" - "@babel/plugin-proposal-json-strings" "^7.18.6" - "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" - "@babel/plugin-proposal-numeric-separator" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.19.4" - "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" - "@babel/plugin-proposal-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-private-methods" "^7.18.6" - "@babel/plugin-proposal-private-property-in-object" "^7.18.6" - "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-jsx" "^7.22.5" + "@babel/types" "^7.22.15" + +"@babel/plugin-transform-react-pure-annotations@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz#1f58363eef6626d6fa517b95ac66fe94685e32c0" + integrity sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-regenerator@^7.22.10": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz#8ceef3bd7375c4db7652878b0241b2be5d0c3cca" + integrity sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + regenerator-transform "^0.15.2" + +"@babel/plugin-transform-reserved-words@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb" + integrity sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-runtime@^7.22.4": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.15.tgz#3a625c4c05a39e932d7d34f5d4895cdd0172fdc9" + integrity sha512-tEVLhk8NRZSmwQ0DJtxxhTrCht1HVo8VaMzYT4w6lwyKBuHsgoioAUA7/6eT2fRfc5/23fuGdlwIxXhRVgWr4g== + dependencies: + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + babel-plugin-polyfill-corejs2 "^0.4.5" + babel-plugin-polyfill-corejs3 "^0.8.3" + babel-plugin-polyfill-regenerator "^0.5.2" + semver "^6.3.1" + +"@babel/plugin-transform-shorthand-properties@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624" + integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-spread@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b" + integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + +"@babel/plugin-transform-sticky-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa" + integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-template-literals@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff" + integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-typeof-symbol@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34" + integrity sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-typescript@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz#15adef906451d86349eb4b8764865c960eb54127" + integrity sha512-1uirS0TnijxvQLnlv5wQBwOX3E1wCFX7ITv+9pBV2wKEk4K+M5tqDaoNXnTH8tjEIYHLO98MwiTWO04Ggz4XuA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-typescript" "^7.22.5" + +"@babel/plugin-transform-unicode-escapes@^7.22.10": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz#c723f380f40a2b2f57a62df24c9005834c8616d9" + integrity sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-property-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz#098898f74d5c1e86660dc112057b2d11227f1c81" + integrity sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183" + integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-unicode-sets-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz#77788060e511b708ffc7d42fdfbc5b37c3004e91" + integrity sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.22.4": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.20.tgz#de9e9b57e1127ce0a2f580831717f7fb677ceedb" + integrity sha512-11MY04gGC4kSzlPHRfvVkNAZhUxOvm7DCJ37hPDnUENwe06npjIRAfInEMTGSb4LZK5ZgDFkv5hw0lGebHeTyg== + dependencies: + "@babel/compat-data" "^7.22.20" + "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.15" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.15" + "@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.18.6" + "@babel/plugin-syntax-import-assertions" "^7.22.5" + "@babel/plugin-syntax-import-attributes" "^7.22.5" + "@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" @@ -1029,76 +995,99 @@ "@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-transform-arrow-functions" "^7.18.6" - "@babel/plugin-transform-async-to-generator" "^7.18.6" - "@babel/plugin-transform-block-scoped-functions" "^7.18.6" - "@babel/plugin-transform-block-scoping" "^7.19.4" - "@babel/plugin-transform-classes" "^7.19.0" - "@babel/plugin-transform-computed-properties" "^7.18.9" - "@babel/plugin-transform-destructuring" "^7.19.4" - "@babel/plugin-transform-dotall-regex" "^7.18.6" - "@babel/plugin-transform-duplicate-keys" "^7.18.9" - "@babel/plugin-transform-exponentiation-operator" "^7.18.6" - "@babel/plugin-transform-for-of" "^7.18.8" - "@babel/plugin-transform-function-name" "^7.18.9" - "@babel/plugin-transform-literals" "^7.18.9" - "@babel/plugin-transform-member-expression-literals" "^7.18.6" - "@babel/plugin-transform-modules-amd" "^7.18.6" - "@babel/plugin-transform-modules-commonjs" "^7.18.6" - "@babel/plugin-transform-modules-systemjs" "^7.19.0" - "@babel/plugin-transform-modules-umd" "^7.18.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" - "@babel/plugin-transform-new-target" "^7.18.6" - "@babel/plugin-transform-object-super" "^7.18.6" - "@babel/plugin-transform-parameters" "^7.18.8" - "@babel/plugin-transform-property-literals" "^7.18.6" - "@babel/plugin-transform-regenerator" "^7.18.6" - "@babel/plugin-transform-reserved-words" "^7.18.6" - "@babel/plugin-transform-shorthand-properties" "^7.18.6" - "@babel/plugin-transform-spread" "^7.19.0" - "@babel/plugin-transform-sticky-regex" "^7.18.6" - "@babel/plugin-transform-template-literals" "^7.18.9" - "@babel/plugin-transform-typeof-symbol" "^7.18.9" - "@babel/plugin-transform-unicode-escapes" "^7.18.10" - "@babel/plugin-transform-unicode-regex" "^7.18.6" - "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.19.4" - babel-plugin-polyfill-corejs2 "^0.3.3" - babel-plugin-polyfill-corejs3 "^0.6.0" - babel-plugin-polyfill-regenerator "^0.4.1" - core-js-compat "^3.25.1" - semver "^6.3.0" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.22.5" + "@babel/plugin-transform-async-generator-functions" "^7.22.15" + "@babel/plugin-transform-async-to-generator" "^7.22.5" + "@babel/plugin-transform-block-scoped-functions" "^7.22.5" + "@babel/plugin-transform-block-scoping" "^7.22.15" + "@babel/plugin-transform-class-properties" "^7.22.5" + "@babel/plugin-transform-class-static-block" "^7.22.11" + "@babel/plugin-transform-classes" "^7.22.15" + "@babel/plugin-transform-computed-properties" "^7.22.5" + "@babel/plugin-transform-destructuring" "^7.22.15" + "@babel/plugin-transform-dotall-regex" "^7.22.5" + "@babel/plugin-transform-duplicate-keys" "^7.22.5" + "@babel/plugin-transform-dynamic-import" "^7.22.11" + "@babel/plugin-transform-exponentiation-operator" "^7.22.5" + "@babel/plugin-transform-export-namespace-from" "^7.22.11" + "@babel/plugin-transform-for-of" "^7.22.15" + "@babel/plugin-transform-function-name" "^7.22.5" + "@babel/plugin-transform-json-strings" "^7.22.11" + "@babel/plugin-transform-literals" "^7.22.5" + "@babel/plugin-transform-logical-assignment-operators" "^7.22.11" + "@babel/plugin-transform-member-expression-literals" "^7.22.5" + "@babel/plugin-transform-modules-amd" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.22.15" + "@babel/plugin-transform-modules-systemjs" "^7.22.11" + "@babel/plugin-transform-modules-umd" "^7.22.5" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" + "@babel/plugin-transform-new-target" "^7.22.5" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.11" + "@babel/plugin-transform-numeric-separator" "^7.22.11" + "@babel/plugin-transform-object-rest-spread" "^7.22.15" + "@babel/plugin-transform-object-super" "^7.22.5" + "@babel/plugin-transform-optional-catch-binding" "^7.22.11" + "@babel/plugin-transform-optional-chaining" "^7.22.15" + "@babel/plugin-transform-parameters" "^7.22.15" + "@babel/plugin-transform-private-methods" "^7.22.5" + "@babel/plugin-transform-private-property-in-object" "^7.22.11" + "@babel/plugin-transform-property-literals" "^7.22.5" + "@babel/plugin-transform-regenerator" "^7.22.10" + "@babel/plugin-transform-reserved-words" "^7.22.5" + "@babel/plugin-transform-shorthand-properties" "^7.22.5" + "@babel/plugin-transform-spread" "^7.22.5" + "@babel/plugin-transform-sticky-regex" "^7.22.5" + "@babel/plugin-transform-template-literals" "^7.22.5" + "@babel/plugin-transform-typeof-symbol" "^7.22.5" + "@babel/plugin-transform-unicode-escapes" "^7.22.10" + "@babel/plugin-transform-unicode-property-regex" "^7.22.5" + "@babel/plugin-transform-unicode-regex" "^7.22.5" + "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" + "@babel/preset-modules" "0.1.6-no-external-plugins" + "@babel/types" "^7.22.19" + babel-plugin-polyfill-corejs2 "^0.4.5" + babel-plugin-polyfill-corejs3 "^0.8.3" + babel-plugin-polyfill-regenerator "^0.5.2" + core-js-compat "^3.31.0" + semver "^6.3.1" -"@babel/preset-modules@^0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" - integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-react@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d" - integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg== +"@babel/preset-react@^7.12.5", "@babel/preset-react@^7.22.3": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.22.15.tgz#9a776892b648e13cc8ca2edf5ed1264eea6b6afc" + integrity sha512-Csy1IJ2uEh/PecCBXXoZGAZBeCATTuePzCSB7dLYWS0vOEj6CNpjxIhW4duWwZodBNueH7QO14WbGn8YyeuN9w== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-validator-option" "^7.18.6" - "@babel/plugin-transform-react-display-name" "^7.18.6" - "@babel/plugin-transform-react-jsx" "^7.18.6" - "@babel/plugin-transform-react-jsx-development" "^7.18.6" - "@babel/plugin-transform-react-pure-annotations" "^7.18.6" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" + "@babel/plugin-transform-react-display-name" "^7.22.5" + "@babel/plugin-transform-react-jsx" "^7.22.15" + "@babel/plugin-transform-react-jsx-development" "^7.22.5" + "@babel/plugin-transform-react-pure-annotations" "^7.22.5" -"@babel/runtime-corejs3@^7.10.2": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.3.tgz#931ed6941d3954924a7aa967ee440e60c507b91a" - integrity sha512-HA7RPj5xvJxQl429r5Cxr2trJwOfPjKiqhCXcdQPSqO2G0RHPZpXu4fkYmBaTKCp2c/jRaMK9GB/lN+7zvvFPw== +"@babel/preset-typescript@^7.21.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.22.15.tgz#43db30516fae1d417d748105a0bc95f637239d48" + integrity sha512-HblhNmh6yM+cU4VwbBRpxFhxsTdfS1zsvH9W+gEjD0ARV9+8B4sNfpI6GuhePti84nuvhiwKS539jKPFHskA9A== dependencies: - core-js-pure "^3.0.0" - regenerator-runtime "^0.13.4" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-validator-option" "^7.22.15" + "@babel/plugin-syntax-jsx" "^7.22.5" + "@babel/plugin-transform-modules-commonjs" "^7.22.15" + "@babel/plugin-transform-typescript" "^7.22.15" + +"@babel/regjsgen@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" + integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@7.0.0": version "7.0.0" @@ -1107,45 +1096,72 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.9", "@babel/runtime@^7.19.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78" - integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" + integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== dependencies: - regenerator-runtime "^0.13.4" + regenerator-runtime "^0.14.0" -"@babel/template@^7.18.10", "@babel/template@^7.18.6", "@babel/template@^7.3.3": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" - integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== +"@babel/template@^7.22.15", "@babel/template@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.10" - "@babel/types" "^7.18.10" + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" -"@babel/traverse@^7.18.10", "@babel/traverse@^7.18.6", "@babel/traverse@^7.19.1", "@babel/traverse@^7.19.4", "@babel/traverse@^7.19.6", "@babel/traverse@^7.7.2": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.6.tgz#7b4c865611df6d99cb131eec2e8ac71656a490dc" - integrity sha512-6l5HrUCzFM04mfbG09AagtYyR2P0B71B1wN7PfSPiksDPz2k5H9CBC1tcZpz2M8OxbKTPccByoOJ22rUKbpmQQ== +"@babel/template@^7.3.3": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" + integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.6" - "@babel/types" "^7.19.4" + "@babel/code-frame" "^7.22.5" + "@babel/parser" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/traverse@7", "@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.20.tgz#db572d9cb5c79e02d83e5618b82f6991c07584c9" + integrity sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/generator" "^7.22.15" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.22.16" + "@babel/types" "^7.22.19" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.19.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" - integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== +"@babel/types@^7.0.0", "@babel/types@^7.12.11", "@babel/types@^7.20.7", "@babel/types@^7.22.10", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.4.4": + version "7.22.19" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.19.tgz#7425343253556916e440e662bb221a93ddb75684" + integrity sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg== dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.19" + to-fast-properties "^2.0.0" + +"@babel/types@^7.0.0-beta.49", "@babel/types@^7.12.6": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" + integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" + to-fast-properties "^2.0.0" + +"@babel/types@^7.3.3": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.10.tgz#4a9e76446048f2c66982d1a989dd12b8a2d2dc03" + integrity sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -1153,161 +1169,324 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@csstools/selector-specificity@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36" - integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg== +"@csstools/css-parser-algorithms@^2.3.0": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.1.tgz#ec4fc764ba45d2bb7ee2774667e056aa95003f3a" + integrity sha512-xrvsmVUtefWMWQsGgFffqWSK03pZ1vfDki4IVIIUxxDKnGBzqNgv0A7SB1oXtVNEkcVO8xi1ZrTL29HhSu5kGA== -"@emotion/babel-plugin@^11.7.1": - version "11.9.2" - resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz#723b6d394c89fb2ef782229d92ba95a740576e95" - integrity sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw== +"@csstools/css-tokenizer@^2.1.1": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.2.0.tgz#9d70e6dcbe94e44c7400a2929928db35c4de32b5" + integrity sha512-wErmsWCbsmig8sQKkM6pFhr/oPha1bHfvxsUY5CYSQxwyhA9Ulrs8EqCgClhg4Tgg2XapVstGqSVcz0xOYizZA== + +"@csstools/media-query-list-parser@^2.1.2": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.4.tgz#0017f99945f6c16dd81a7aacf6821770933c3a5c" + integrity sha512-V/OUXYX91tAC1CDsiY+HotIcJR+vPtzrX8pCplCpT++i8ThZZsq5F5dzZh/bDM3WUOjrvC1ljed1oSJxMfjqhw== + +"@csstools/selector-specificity@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz#798622546b63847e82389e473fd67f2707d82247" + integrity sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g== + +"@discoveryjs/json-ext@0.5.7": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@emotion/babel-plugin@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" + integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== dependencies: - "@babel/helper-module-imports" "^7.12.13" - "@babel/plugin-syntax-jsx" "^7.12.13" - "@babel/runtime" "^7.13.10" - "@emotion/hash" "^0.8.0" - "@emotion/memoize" "^0.7.5" - "@emotion/serialize" "^1.0.2" - babel-plugin-macros "^2.6.1" + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/serialize" "^1.1.2" + babel-plugin-macros "^3.1.0" convert-source-map "^1.5.0" escape-string-regexp "^4.0.0" find-root "^1.1.0" source-map "^0.5.7" - stylis "4.0.13" + stylis "4.2.0" -"@emotion/cache@^11.4.0", "@emotion/cache@^11.7.1": - version "11.7.1" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.7.1.tgz#08d080e396a42e0037848214e8aa7bf879065539" - integrity sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A== +"@emotion/cache@^11.11.0", "@emotion/cache@^11.4.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" + integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== dependencies: - "@emotion/memoize" "^0.7.4" - "@emotion/sheet" "^1.1.0" - "@emotion/utils" "^1.0.0" - "@emotion/weak-memoize" "^0.2.5" - stylis "4.0.13" + "@emotion/memoize" "^0.8.1" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + stylis "4.2.0" -"@emotion/hash@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" - integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== +"@emotion/hash@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" + integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== -"@emotion/memoize@^0.7.4", "@emotion/memoize@^0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50" - integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ== +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== "@emotion/react@^11.8.1": - version "11.9.0" - resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.9.0.tgz#b6d42b1db3bd7511e7a7c4151dc8bc82e14593b8" - integrity sha512-lBVSF5d0ceKtfKCDQJveNAtkC7ayxpVlgOohLgXqRwqWr9bOf4TZAFFyIcNngnV6xK6X4x2ZeXq7vliHkoVkxQ== + version "11.11.1" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.1.tgz#b2c36afac95b184f73b08da8c214fdf861fa4157" + integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA== dependencies: - "@babel/runtime" "^7.13.10" - "@emotion/babel-plugin" "^11.7.1" - "@emotion/cache" "^11.7.1" - "@emotion/serialize" "^1.0.3" - "@emotion/utils" "^1.1.0" - "@emotion/weak-memoize" "^0.2.5" + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/cache" "^11.11.0" + "@emotion/serialize" "^1.1.2" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" hoist-non-react-statics "^3.3.1" -"@emotion/serialize@^1.0.2", "@emotion/serialize@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.3.tgz#99e2060c26c6292469fb30db41f4690e1c8fea63" - integrity sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA== +"@emotion/serialize@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51" + integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA== dependencies: - "@emotion/hash" "^0.8.0" - "@emotion/memoize" "^0.7.4" - "@emotion/unitless" "^0.7.5" - "@emotion/utils" "^1.0.0" + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/unitless" "^0.8.1" + "@emotion/utils" "^1.2.1" csstype "^3.0.2" -"@emotion/sheet@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.1.0.tgz#56d99c41f0a1cda2726a05aa6a20afd4c63e58d2" - integrity sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g== +"@emotion/sheet@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" + integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== -"@emotion/unitless@^0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" - integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== +"@emotion/unitless@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== -"@emotion/utils@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af" - integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA== +"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" + integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== -"@emotion/utils@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.1.0.tgz#86b0b297f3f1a0f2bdb08eeac9a2f49afd40d0cf" - integrity sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ== +"@emotion/utils@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" + integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== -"@emotion/weak-memoize@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" - integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== +"@emotion/weak-memoize@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" + integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== -"@eslint/eslintrc@^0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" - integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== +"@es-joy/jsdoccomment@~0.40.1": + version "0.40.1" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.40.1.tgz#13acd77fb372ed1c83b7355edd865a3b370c9ec4" + integrity sha512-YORCdZSusAlBrFpZ77pJjc5r1bQs5caPWtAu+WWmiSo+8XaUzseapVrfAtiRFbQWnrBxxLLEwF6f6ZG/UgCQCg== + dependencies: + comment-parser "1.4.0" + esquery "^1.5.0" + jsdoc-type-pratt-parser "~4.0.0" + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.5.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.8.1.tgz#8c4bb756cc2aa7eaf13cfa5e69c83afb3260c20c" + integrity sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ== + +"@eslint-community/regexpp@^4.6.1": + version "4.8.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.8.0.tgz#11195513186f68d42fbf449f9a7136b2c0c92005" + integrity sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg== + +"@eslint/eslintrc@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" + integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== dependencies: ajv "^6.12.4" - debug "^4.1.1" - espree "^7.3.0" - globals "^13.9.0" - ignore "^4.0.6" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" import-fresh "^3.2.1" - js-yaml "^3.13.1" - minimatch "^3.0.4" + js-yaml "^4.1.0" + minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@floating-ui/core@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.0.1.tgz#00e64d74e911602c8533957af0cce5af6b2e93c8" - integrity sha512-bO37brCPfteXQfFY0DyNDGB3+IMe4j150KFQcgJ5aBP295p9nBGeHEs/p0czrRbtlHq4Px/yoPXO/+dOCcF4uA== +"@eslint/js@8.49.0": + version "8.49.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.49.0.tgz#86f79756004a97fa4df866835093f1df3d03c333" + integrity sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w== + +"@floating-ui/core@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.3.1.tgz#4d795b649cc3b1cbb760d191c80dcb4353c9a366" + integrity sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g== "@floating-ui/dom@^1.0.1": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.0.2.tgz#c5184c52c6f50abd11052d71204f4be2d9245237" - integrity sha512-5X9WSvZ8/fjy3gDu8yx9HAA4KG1lazUN2P4/VnaXLxTO9Dz53HI1oYoh1OlhqFNlHgGDiwFX5WhFCc2ljbW3yA== + version "1.4.5" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.4.5.tgz#336dfb9870c98b471ff5802002982e489b8bd1c5" + integrity sha512-96KnRWkRnuBSSFbj0sFGwwOUd8EkiecINVl0O9wiZlZ64EkpyAOG3Xc2vKKNJmru0Z7RqWNymA+6b8OZqjgyyw== dependencies: - "@floating-ui/core" "^1.0.1" + "@floating-ui/core" "^1.3.1" -"@formatjs/intl-unified-numberformat@^3.3.3": - version "3.3.6" - resolved "https://registry.yarnpkg.com/@formatjs/intl-unified-numberformat/-/intl-unified-numberformat-3.3.6.tgz#ab69818f7568894023cb31fdb5b5c7eed62c6537" - integrity sha512-VQYswh9Pxf4kN6FQvKprAQwSJrF93eJstCDPM1HIt3c3O6NqPFWNWhZ91PLTppOV11rLYsFK11ZxiGbnLNiPTg== +"@formatjs/cli@^6.1.1": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.1.3.tgz#b4b95259398e222ec6c06cf5f23f76d987f53e96" + integrity sha512-PdTXZTY8LqxwmvFqdifn89gjXnPUpGtGyFs0BnoeLuOuxZFSnBfIs5WQCVMaJnr1+0vNNlXyT0VAIAwjRpf6BA== + +"@formatjs/ecma402-abstract@1.17.2": + version "1.17.2" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.17.2.tgz#d197c6e26b9fd96ff7ba3b3a0cc2f25f1f2dcac3" + integrity sha512-k2mTh0m+IV1HRdU0xXM617tSQTi53tVR2muvYOsBeYcUgEAyxV1FOC7Qj279th3fBVQ+Dj6muvNJZcHSPNdbKg== dependencies: - "@formatjs/intl-utils" "^2.2.5" + "@formatjs/intl-localematcher" "0.4.2" + tslib "^2.4.0" -"@formatjs/intl-utils@^2.2.5": - version "2.2.5" - resolved "https://registry.yarnpkg.com/@formatjs/intl-utils/-/intl-utils-2.2.5.tgz#eaafd94df3d102ee13e54e80f992a33868a6b1e8" - integrity sha512-p7gcmazKROteL4IECCp03Qrs790fZ8tbemUAjQu0+K0AaAlK49rI1SIFFq3LzDUAqXIshV95JJhRe/yXxkal5g== +"@formatjs/fast-memoize@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz#33bd616d2e486c3e8ef4e68c99648c196887802b" + integrity sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA== + dependencies: + tslib "^2.4.0" + +"@formatjs/icu-messageformat-parser@2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.6.2.tgz#9bbb29099416e4ce2c7df50029c48985d4f901b3" + integrity sha512-nF/Iww7sc5h+1MBCDRm68qpHTCG4xvGzYs/x9HFcDETSGScaJ1Fcadk5U/NXjXeCtzD+DhN4BAwKFVclHfKMdA== + dependencies: + "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/icu-skeleton-parser" "1.6.2" + tslib "^2.4.0" + +"@formatjs/icu-skeleton-parser@1.6.2": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.6.2.tgz#00303034dc08583973c8aa67b96534c49c0bad8d" + integrity sha512-VtB9Slo4ZL6QgtDFJ8Injvscf0xiDd4bIV93SOJTBjUF4xe2nAWOoSjLEtqIG+hlIs1sNrVKAaFo3nuTI4r5ZA== + dependencies: + "@formatjs/ecma402-abstract" "1.17.2" + tslib "^2.4.0" + +"@formatjs/intl-displaynames@6.5.2": + version "6.5.2" + resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.5.2.tgz#b14ffd0962d5b5cfd71457efc389f0bca83a00db" + integrity sha512-uC2VBlz+WydGTDDpJwMTQuPH3CUpTricr91WH1QMfz5oEHg2sB7mUERcZONE/lu8MOe1jREIx4vBciZEVTqkmA== + dependencies: + "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/intl-localematcher" "0.4.2" + tslib "^2.4.0" + +"@formatjs/intl-listformat@7.4.2": + version "7.4.2" + resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.4.2.tgz#c8d86d3b15eead41f74748d1c79d6450fd1bad82" + integrity sha512-+6bSVudEQkf12Hh7kuKt8Xv/MyFlqdwA4V4NLnTZW8uYdF9RxlOELDD0rPaOc2++TMKIzI5o6XXwHPvpL6VrPA== + dependencies: + "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/intl-localematcher" "0.4.2" + tslib "^2.4.0" + +"@formatjs/intl-localematcher@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.4.2.tgz#7e6e596dbaf2f0c5a7c22da5a01d5c55f4c37e9a" + integrity sha512-BGdtJFmaNJy5An/Zan4OId/yR9Ih1OojFjcduX/xOvq798OgWSyDtd6Qd5jqJXwJs1ipe4Fxu9+cshic5Ox2tA== + dependencies: + tslib "^2.4.0" + +"@formatjs/intl-pluralrules@^5.2.2": + version "5.2.6" + resolved "https://registry.yarnpkg.com/@formatjs/intl-pluralrules/-/intl-pluralrules-5.2.6.tgz#c08daf9f4feb8a858e8dcb90e50f1e33fd6dc469" + integrity sha512-64i4JvWS1ibw774xnJaNdyIxPzyaO+uGgOtkuS0EbK076f3znEhu1GXh4vmnaCEGb9R3uLwOWU0GjOEbavFzbA== + dependencies: + "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/intl-localematcher" "0.4.2" + tslib "^2.4.0" + +"@formatjs/intl@2.9.3": + version "2.9.3" + resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.9.3.tgz#e570c4b1afb173dfb1f80a42624425dde9841329" + integrity sha512-hclPdyCF1zk2XmhgdXfl5Sd30QEdRBnIijH7Vc1AWz2K0/saVRrxuL3UYn+m3xEyfOa4yDbTWVbmXDL0XEzlsQ== + dependencies: + "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/fast-memoize" "2.2.0" + "@formatjs/icu-messageformat-parser" "2.6.2" + "@formatjs/intl-displaynames" "6.5.2" + "@formatjs/intl-listformat" "7.4.2" + intl-messageformat "10.5.3" + tslib "^2.4.0" + +"@formatjs/ts-transformer@3.13.5": + version "3.13.5" + resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-3.13.5.tgz#8b4f5dcb02940e3f12b9f6c95b47f52777a9969d" + integrity sha512-dh2mmZqkId0UeM+FQtmwugpMGvyzTBmXj5LjwD4M5OeSm62tcgkScjqeO/1EetaNS/JkTUBbsFBnHzaDzh3yOw== + dependencies: + "@formatjs/icu-messageformat-parser" "2.6.2" + "@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 "^4.7 || 5" "@gamestdio/websocket@^0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@gamestdio/websocket/-/websocket-0.3.2.tgz#321ba0976ee30fd14e51dbf8faa85ce7b325f76a" integrity sha512-J3n5SKim+ZoLbe44hRGI/VYAwSMCeIJuBy+FfP6EZaujEpNchPRFcIsVQLWAwpU1bP2Ji63rC+rEUOd1vjUB6Q== -"@github/webauthn-json@^0.5.7": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@github/webauthn-json/-/webauthn-json-0.5.7.tgz#143bc67f6e0f75f8d188e565741507bb08c31214" - integrity sha512-SUYsttDxFSvWvvJssJpwzjmRCqYfdfqC9VCmAHQYfdKCVelyJteCHo9/lK1CB72mx/jrl6cFNY08aua4J2jIyg== +"@gar/promisify@^1.0.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@humanwhocodes/config-array@^0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" - integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== +"@github/webauthn-json@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@github/webauthn-json/-/webauthn-json-2.1.1.tgz#648e63fc28050917d2882cc2b27817a88cb420fc" + integrity sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ== + +"@humanwhocodes/config-array@^0.11.11": + version "0.11.11" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" + integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA== dependencies: - "@humanwhocodes/object-schema" "^1.2.0" + "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" - minimatch "^3.0.4" + minimatch "^3.0.5" -"@humanwhocodes/object-schema@^1.2.0": +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@ioredis/commands@^1.1.1": version "1.2.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" - integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" + integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + 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" "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" @@ -1321,136 +1500,121 @@ resolve-from "^5.0.0" "@istanbuljs/schema@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" - integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.2.1": - version "29.2.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.2.1.tgz#5f2c62dcdd5ce66e94b6d6729e021758bceea090" - integrity sha512-MF8Adcw+WPLZGBiNxn76DOuczG3BhODTcMlDCA4+cFi41OkaY/lyI0XUUhi73F88Y+7IHoGmD80pN5CtxQUdSw== +"@jest/console@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.6.2.tgz#bf1d4101347c23e07c029a1b1ae07d550f5cc541" + integrity sha512-0N0yZof5hi44HAR2pPS+ikJ3nzKNoZdVu8FffRf3wy47I7Dm7etk/3KetMdRUqzVd16V4O2m2ISpNTbnIuqy1w== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.2.1" - jest-util "^29.2.1" + jest-message-util "^29.6.2" + jest-util "^29.6.2" slash "^3.0.0" -"@jest/core@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.2.2.tgz#207aa8973d9de8769f9518732bc5f781efc3ffa7" - integrity sha512-susVl8o2KYLcZhhkvSB+b7xX575CX3TmSvxfeDjpRko7KmT89rHkXj6XkDkNpSeFMBzIENw5qIchO9HC9Sem+A== +"@jest/core@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.6.2.tgz#6f2d1dbe8aa0265fcd4fb8082ae1952f148209c8" + integrity sha512-Oj+5B+sDMiMWLhPFF+4/DvHOf+U10rgvCLGPHP8Xlsy/7QxS51aU/eBngudHlJXnaWD5EohAgJ4js+T6pa+zOg== dependencies: - "@jest/console" "^29.2.1" - "@jest/reporters" "^29.2.2" - "@jest/test-result" "^29.2.1" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/console" "^29.6.2" + "@jest/reporters" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" "@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.2.0" - jest-config "^29.2.2" - jest-haste-map "^29.2.1" - jest-message-util "^29.2.1" - jest-regex-util "^29.2.0" - jest-resolve "^29.2.2" - jest-resolve-dependencies "^29.2.2" - jest-runner "^29.2.2" - jest-runtime "^29.2.2" - jest-snapshot "^29.2.2" - jest-util "^29.2.1" - jest-validate "^29.2.2" - jest-watcher "^29.2.2" + jest-changed-files "^29.5.0" + jest-config "^29.6.2" + jest-haste-map "^29.6.2" + jest-message-util "^29.6.2" + jest-regex-util "^29.4.3" + jest-resolve "^29.6.2" + jest-resolve-dependencies "^29.6.2" + jest-runner "^29.6.2" + jest-runtime "^29.6.2" + jest-snapshot "^29.6.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" + jest-watcher "^29.6.2" micromatch "^4.0.4" - pretty-format "^29.2.1" + pretty-format "^29.6.2" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.2.1": - version "29.2.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.2.1.tgz#acb1994fbd5ad02819a1a34a923c531e6923b665" - integrity sha512-EutqA7T/X6zFjw6mAWRHND+ZkTPklmIEWCNbmwX6uCmOrFrWaLbDZjA+gePHJx6fFMMRvNfjXcvzXEtz54KPlg== +"@jest/environment@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.2.tgz#794c0f769d85e7553439d107d3f43186dc6874a9" + integrity sha512-AEcW43C7huGd/vogTddNNTDRpO6vQ2zaQNrttvWV18ArBx9Z56h7BIsXkNFJVOO4/kblWEQz30ckw0+L3izc+Q== dependencies: - "@jest/fake-timers" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/fake-timers" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" - jest-mock "^29.2.1" + jest-mock "^29.6.2" -"@jest/environment@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.2.2.tgz#481e729048d42e87d04842c38aa4d09c507f53b0" - integrity sha512-OWn+Vhu0I1yxuGBJEFFekMYc8aGBGrY4rt47SOh/IFaI+D7ZHCk7pKRiSoZ2/Ml7b0Ony3ydmEHRx/tEOC7H1A== +"@jest/expect-utils@^29.6.2": + version "29.6.4" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.4.tgz#17c7dfe6cec106441f218b0aff4b295f98346679" + integrity sha512-FEhkJhqtvBwgSpiTrocquJCdXPsyvNKcl/n7A3u7X4pVoF4bswm11c9d4AV+kfq2Gpv/mM8x7E7DsRvH+djkrg== dependencies: - "@jest/fake-timers" "^29.2.2" - "@jest/types" "^29.2.1" + jest-get-type "^29.6.3" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.6.2.tgz#5a2ad58bb345165d9ce0a1845bbf873c480a4b28" + integrity sha512-m6DrEJxVKjkELTVAztTLyS/7C92Y2b0VYqmDROYKLLALHn8T/04yPs70NADUYPrV3ruI+H3J0iUIuhkjp7vkfg== + dependencies: + expect "^29.6.2" + jest-snapshot "^29.6.2" + +"@jest/fake-timers@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.2.tgz#fe9d43c5e4b1b901168fe6f46f861b3e652a2df4" + integrity sha512-euZDmIlWjm1Z0lJ1D0f7a0/y5Kh/koLFMUBE5SUYWrmy8oNhJpbTBDAP6CxKnadcMLDoDf4waRYCe35cH6G6PA== + dependencies: + "@jest/types" "^29.6.1" + "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-mock "^29.2.2" + jest-message-util "^29.6.2" + jest-mock "^29.6.2" + jest-util "^29.6.2" -"@jest/expect-utils@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.2.2.tgz#460a5b5a3caf84d4feb2668677393dd66ff98665" - integrity sha512-vwnVmrVhTmGgQzyvcpze08br91OL61t9O0lJMDyb6Y/D8EKQ9V7rGUb/p7PDt0GPzK0zFYqXWFo4EO2legXmkg== +"@jest/globals@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.6.2.tgz#74af81b9249122cc46f1eb25793617eec69bf21a" + integrity sha512-cjuJmNDjs6aMijCmSa1g2TNG4Lby/AeU7/02VtpW+SLcZXzOLK2GpN2nLqcFjmhy3B3AoPeQVx7BnyOf681bAw== dependencies: - jest-get-type "^29.2.0" + "@jest/environment" "^29.6.2" + "@jest/expect" "^29.6.2" + "@jest/types" "^29.6.1" + jest-mock "^29.6.2" -"@jest/expect@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.2.2.tgz#81edbd33afbde7795ca07ff6b4753d15205032e4" - integrity sha512-zwblIZnrIVt8z/SiEeJ7Q9wKKuB+/GS4yZe9zw7gMqfGf4C5hBLGrVyxu1SzDbVSqyMSlprKl3WL1r80cBNkgg== - dependencies: - expect "^29.2.2" - jest-snapshot "^29.2.2" - -"@jest/fake-timers@^29.2.1": - version "29.2.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.2.1.tgz#786d60e8cb60ca70c9f913cb49fcc77610c072bb" - integrity sha512-KWil+8fef7Uj/P/PTZlPKk1Pw117wAmr71VWFV8ZDtRtkwmTG8oY4IRf0Ss44J2y5CYRy8d/zLOhxyoGRENjvA== - dependencies: - "@jest/types" "^29.2.1" - "@sinonjs/fake-timers" "^9.1.2" - "@types/node" "*" - jest-message-util "^29.2.1" - jest-mock "^29.2.1" - jest-util "^29.2.1" - -"@jest/fake-timers@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.2.2.tgz#d8332e6e3cfa99cde4bc87d04a17d6b699deb340" - integrity sha512-nqaW3y2aSyZDl7zQ7t1XogsxeavNpH6kkdq+EpXncIDvAkjvFD7hmhcIs1nWloengEWUoWqkqSA6MSbf9w6DgA== - dependencies: - "@jest/types" "^29.2.1" - "@sinonjs/fake-timers" "^9.1.2" - "@types/node" "*" - jest-message-util "^29.2.1" - jest-mock "^29.2.2" - jest-util "^29.2.1" - -"@jest/globals@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.2.2.tgz#205ff1e795aa774301c2c0ba0be182558471b845" - integrity sha512-/nt+5YMh65kYcfBhj38B3Hm0Trk4IsuMXNDGKE/swp36yydBWfz3OXkLqkSvoAtPW8IJMSJDFCbTM2oj5SNprw== - dependencies: - "@jest/environment" "^29.2.2" - "@jest/expect" "^29.2.2" - "@jest/types" "^29.2.1" - jest-mock "^29.2.2" - -"@jest/reporters@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.2.2.tgz#69b395f79c3a97ce969ce05ccf1a482e5d6de290" - integrity sha512-AzjL2rl2zJC0njIzcooBvjA4sJjvdoq98sDuuNs4aNugtLPSQ+91nysGKRF0uY1to5k0MdGMdOBggUsPqvBcpA== +"@jest/reporters@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.6.2.tgz#524afe1d76da33d31309c2c4a2c8062d0c48780a" + integrity sha512-sWtijrvIav8LgfJZlrGCdN0nP2EWbakglJY49J1Y5QihcQLfy7ovyxxjJBRXMNltgt4uPtEcFmIMbVshEDfFWw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.2.1" - "@jest/test-result" "^29.2.1" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" - "@jridgewell/trace-mapping" "^0.3.15" + "@jest/console" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" + "@jridgewell/trace-mapping" "^0.3.18" "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" @@ -1462,222 +1626,127 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.2.1" - jest-util "^29.2.1" - jest-worker "^29.2.1" + jest-message-util "^29.6.2" + jest-util "^29.6.2" + jest-worker "^29.6.2" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" -"@jest/schemas@^29.0.0": - version "29.0.0" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.0.0.tgz#5f47f5994dd4ef067fb7b4188ceac45f77fe952a" - integrity sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA== +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== dependencies: - "@sinclair/typebox" "^0.24.1" + "@sinclair/typebox" "^0.27.8" -"@jest/source-map@^29.2.0": - version "29.2.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.2.0.tgz#ab3420c46d42508dcc3dc1c6deee0b613c235744" - integrity sha512-1NX9/7zzI0nqa6+kgpSdKPK+WU1p+SJk3TloWZf5MzPbxri9UEeXX5bWZAPCzbQcyuAzubcdUHA7hcNznmRqWQ== +"@jest/source-map@^29.6.0": + version "29.6.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.0.tgz#bd34a05b5737cb1a99d43e1957020ac8e5b9ddb1" + integrity sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA== dependencies: - "@jridgewell/trace-mapping" "^0.3.15" + "@jridgewell/trace-mapping" "^0.3.18" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.2.1": - version "29.2.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.2.1.tgz#f42dbf7b9ae465d0a93eee6131473b8bb3bd2edb" - integrity sha512-lS4+H+VkhbX6z64tZP7PAUwPqhwj3kbuEHcaLuaBuB+riyaX7oa1txe0tXgrFj5hRWvZKvqO7LZDlNWeJ7VTPA== +"@jest/test-result@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.6.2.tgz#fdd11583cd1608e4db3114e8f0cce277bf7a32ed" + integrity sha512-3VKFXzcV42EYhMCsJQURptSqnyjqCGbtLuX5Xxb6Pm6gUf1wIRIl+mandIRGJyWKgNKYF9cnstti6Ls5ekduqw== dependencies: - "@jest/console" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/console" "^29.6.2" + "@jest/types" "^29.6.1" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.2.2.tgz#4ac7487b237e517a1f55e7866fb5553f6e0168b9" - integrity sha512-Cuc1znc1pl4v9REgmmLf0jBd3Y65UXJpioGYtMr/JNpQEIGEzkmHhy6W6DLbSsXeUA13TDzymPv0ZGZ9jH3eIw== +"@jest/test-sequencer@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.6.2.tgz#585eff07a68dd75225a7eacf319780cb9f6b9bf4" + integrity sha512-GVYi6PfPwVejO7slw6IDO0qKVum5jtrJ3KoLGbgBWyr2qr4GaxFV6su+ZAjdTX75Sr1DkMFRk09r2ZVa+wtCGw== dependencies: - "@jest/test-result" "^29.2.1" + "@jest/test-result" "^29.6.2" graceful-fs "^4.2.9" - jest-haste-map "^29.2.1" + jest-haste-map "^29.6.2" slash "^3.0.0" -"@jest/transform@^29.2.1": - version "29.2.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.2.1.tgz#f3d8154edd19cdbcaf1d6646bd8f4ff7812318a2" - integrity sha512-xup+iEuaIRSQabQaeqxaQyN0vg1Dctrp9oTObQsNf3sZEowTIa5cANYuoyi8Tqhg4GCqEVLTf18KW7ii0UeFVA== +"@jest/transform@^29.6.2": + version "29.6.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.6.2.tgz#522901ebbb211af08835bc3bcdf765ab778094e3" + integrity sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.2.1" - "@jridgewell/trace-mapping" "^0.3.15" + "@jest/types" "^29.6.1" + "@jridgewell/trace-mapping" "^0.3.18" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" - convert-source-map "^1.4.0" + convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.2.1" - jest-regex-util "^29.2.0" - jest-util "^29.2.1" + jest-haste-map "^29.6.2" + jest-regex-util "^29.4.3" + jest-util "^29.6.2" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" - write-file-atomic "^4.0.1" + write-file-atomic "^4.0.2" -"@jest/transform@^29.2.2": - version "29.2.2" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.2.2.tgz#dfc03fc092b31ffea0c55917728e75bfcf8b5de6" - integrity sha512-aPe6rrletyuEIt2axxgdtxljmzH8O/nrov4byy6pDw9S8inIrTV+2PnjyP/oFHMSynzGxJ2s6OHowBNMXp/Jzg== +"@jest/types@^29.6.1", "@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== dependencies: - "@babel/core" "^7.11.6" - "@jest/types" "^29.2.1" - "@jridgewell/trace-mapping" "^0.3.15" - babel-plugin-istanbul "^6.1.1" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.1.0" - graceful-fs "^4.2.9" - jest-haste-map "^29.2.1" - jest-regex-util "^29.2.0" - jest-util "^29.2.1" - micromatch "^4.0.4" - pirates "^4.0.4" - slash "^3.0.0" - write-file-atomic "^4.0.1" - -"@jest/types@^25.5.0": - version "25.5.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" - integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^15.0.0" - chalk "^3.0.0" - -"@jest/types@^27.0.2": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" - integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^16.0.0" - chalk "^4.0.0" - -"@jest/types@^29.2.1": - version "29.2.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.2.1.tgz#ec9c683094d4eb754e41e2119d8bdaef01cf6da0" - integrity sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw== - dependencies: - "@jest/schemas" "^29.0.0" + "@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" -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== dependencies: "@jridgewell/set-array" "^1.0.1" "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@^3.0.3": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.4.tgz#b876e3feefb9c8d3aa84014da28b5e52a0640d72" - integrity sha512-cz8HFjOFfUBtvN+NXYSFMHYRdxZMaEl0XypVrhzxBgadKIXhIkRd8aMeHhmF56Sl7SuS8OnUpQ73/k9LE4VnLg== +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== "@jridgewell/set-array@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.10" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.10.tgz#baf57b4e2a690d4f38560171f91783656b7f8186" - integrity sha512-Ht8wIW5v165atIX1p+JvKR5ONzUyF4Ac8DZIQ5kZs9zrb6M8SJNXpx1zn04rn65VjBMygRoMXcyYwNK0fT7bEg== - -"@jridgewell/trace-mapping@^0.3.0": - version "0.3.4" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" - integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== +"@jridgewell/source-map@^0.3.3": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" + integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ== dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/trace-mapping@^0.3.12": - version "0.3.14" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" - integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.19" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" + integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" -"@jridgewell/trace-mapping@^0.3.15": - version "0.3.15" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" - integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" - integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": - version "5.1.1-v1" - resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" - integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg== - dependencies: - eslint-scope "5.1.1" - -"@node-redis/bloom@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@node-redis/bloom/-/bloom-1.0.1.tgz#144474a0b7dc4a4b91badea2cfa9538ce0a1854e" - integrity sha512-mXEBvEIgF4tUzdIN89LiYsbi6//EdpFA7L8M+DHCvePXg+bfHWi+ct5VI6nHUFQE5+ohm/9wmgihCH3HSkeKsw== - -"@node-redis/client@1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@node-redis/client/-/client-1.0.5.tgz#ebac5e2bbf12214042a37621604973a954ede755" - integrity sha512-ESZ3bd1f+od62h4MaBLKum+klVJfA4wAeLHcVQBkoXa1l0viFesOWnakLQqKg+UyrlJhZmXJWtu0Y9v7iTMrig== - dependencies: - cluster-key-slot "1.1.0" - generic-pool "3.8.2" - redis-parser "3.0.0" - yallist "4.0.0" - -"@node-redis/graph@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@node-redis/graph/-/graph-1.0.0.tgz#baf8eaac4a400f86ea04d65ec3d65715fd7951ab" - integrity sha512-mRSo8jEGC0cf+Rm7q8mWMKKKqkn6EAnA9IA2S3JvUv/gaWW/73vil7GLNwion2ihTptAm05I9LkepzfIXUKX5g== - -"@node-redis/json@1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@node-redis/json/-/json-1.0.2.tgz#8ad2d0f026698dc1a4238cc3d1eb099a3bee5ab8" - integrity sha512-qVRgn8WfG46QQ08CghSbY4VhHFgaTY71WjpwRBGEuqGPfWwfRcIf3OqSpR7Q/45X+v3xd8mvYjywqh0wqJ8T+g== - -"@node-redis/search@1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@node-redis/search/-/search-1.0.5.tgz#96050007eb7c50a7e47080320b4f12aca8cf94c4" - integrity sha512-MCOL8iCKq4v+3HgEQv8zGlSkZyXSXtERgrAJ4TSryIG/eLFy84b57KmNNa/V7M1Q2Wd2hgn2nPCGNcQtk1R1OQ== - -"@node-redis/time-series@1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@node-redis/time-series/-/time-series-1.0.2.tgz#5dd3638374edd85ebe0aa6b0e87addc88fb9df69" - integrity sha512-HGQ8YooJ8Mx7l28tD7XjtB3ImLEjlUxG1wC1PAjxu6hPJqjPshUZxAICzDqDjtIbhDTf48WXXUcx8TQJB1XTKA== +"@material-design-icons/svg@^0.14.10": + version "0.14.12" + resolved "https://registry.yarnpkg.com/@material-design-icons/svg/-/svg-0.14.12.tgz#b3dd27b4c2a93e0310f51acfb311846b0212f987" + integrity sha512-hVEMICFvG26SKDXatPmz+vY5BAqLPCDiyXnw+KN46FXOtY4PcpeAfzFZvwt6D9ywNnVJd4EvmLdlWgLmtOWxbA== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -1692,7 +1761,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -1700,22 +1769,81 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + "@npmcli/move-file@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.0.1.tgz#de103070dac0f48ce49cf6693c23af59c0f70464" - integrity sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw== + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== dependencies: mkdirp "^1.0.4" + rimraf "^3.0.2" -"@polka/url@^1.0.0-next.9": - version "1.0.0-next.11" - resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71" - integrity sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA== +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@rails/ujs@^6.1.7": - version "6.1.7" - resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.7.tgz#b09dc5b2105dd267e8374c47e4490240451dc7f6" - integrity sha512-0e7WQ4LE/+LEfW2zfAw9ppsB6A8RmxbdAUPAF++UT80epY+7emuQDkKXmaK0a9lp6An50RvzezI0cIQjp1A58w== +"@pkgr/utils@^2.3.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.1.tgz#adf291d0357834c410ce80af16e711b56c7b1cd3" + integrity sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w== + dependencies: + cross-spawn "^7.0.3" + fast-glob "^3.2.12" + is-glob "^4.0.3" + open "^9.1.0" + picocolors "^1.0.0" + tslib "^2.5.0" + +"@polka/url@^1.0.0-next.20": + version "1.0.0-next.21" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" + integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== + +"@popperjs/core@^2.11.6": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + +"@rails/ujs@^7.0.6": + version "7.0.8" + resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-7.0.8.tgz#59853367d0827b3955d2c4bedfd5eba4a79d3422" + integrity sha512-tOQQBVH8LsUpGXqDnk+kaOGVsgZ8maHAhEiw3Git3p88q+c0Slgu47HuDnL6sVxeCfz24zbq7dOjsVYDiTpDIA== + +"@reduxjs/toolkit@^1.9.5": + version "1.9.5" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.9.5.tgz#d3987849c24189ca483baa7aa59386c8e52077c4" + integrity sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ== + dependencies: + immer "^9.0.21" + redux "^4.2.1" + redux-thunk "^2.4.2" + reselect "^4.1.8" + +"@renchap/compression-webpack-plugin@^6.1.4": + version "6.1.4" + resolved "https://registry.yarnpkg.com/@renchap/compression-webpack-plugin/-/compression-webpack-plugin-6.1.4.tgz#5ff528ae9edf83de7447b72f5b52a05f860bb899" + integrity sha512-Ij43bj/jhKiMKOZVT9b3DJvr4R+dNs9ZbH7QV3kLfloavt4GhNo4Jw86tVwmP5d+seZtSwTL1NG8/c6dM1V0vw== + dependencies: + cacache "^15.0.5" + find-cache-dir "^3.3.1" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + webpack-sources "^1.4.3" + +"@restart/hooks@^0.4.7": + version "0.4.9" + resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.9.tgz#ad858fb39d99e252cccce19416adc18fc3f18fcb" + integrity sha512-3BekqcwB6Umeya+16XPooARn4qEPW6vNvwYnlofIYe6h9qG1/VeD7UvShCWx11eFz5ELYmwIEshz+MkPX3wjcQ== + dependencies: + dequal "^2.0.2" "@rollup/plugin-babel@^5.2.0": version "5.3.1" @@ -1754,24 +1882,24 @@ estree-walker "^1.0.1" picomatch "^2.2.2" -"@sinclair/typebox@^0.24.1": - version "0.24.20" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.20.tgz#11a657875de6008622d53f56e063a6347c51a6dd" - integrity sha512-kVaO5aEFZb33nPMTZBxiPEkY+slxiPtqC7QX8f9B3eGOMBvEfuMfxp9DSTTCsRJPumPKjrge4yagyssO4q6qzQ== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== -"@sinonjs/commons@^1.7.0": - version "1.8.1" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" - integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw== +"@sinonjs/commons@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" + integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^9.1.2": - version "9.1.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" - integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== dependencies: - "@sinonjs/commons" "^1.7.0" + "@sinonjs/commons" "^3.0.0" "@surma/rollup-plugin-off-main-thread@^2.2.3": version "2.2.3" @@ -1783,28 +1911,130 @@ magic-string "^0.25.0" string.prototype.matchall "^4.0.6" -"@testing-library/dom@^8.0.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.1.0.tgz#f8358b1883844ea569ba76b7e94582168df5370d" - integrity sha512-kmW9alndr19qd6DABzQ978zKQ+J65gU2Rzkl8hriIetPnwpesRaK4//jEQyYh8fEALmGhomD/LBQqt+o+DL95Q== +"@svgr/babel-plugin-add-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" + integrity sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg== + +"@svgr/babel-plugin-remove-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz#6b2c770c95c874654fd5e1d5ef475b78a0a962ef" + integrity sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg== + +"@svgr/babel-plugin-remove-jsx-empty-expression@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz#25621a8915ed7ad70da6cea3d0a6dbc2ea933efd" + integrity sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz#0b221fc57f9fcd10e91fe219e2cd0dd03145a897" + integrity sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ== + +"@svgr/babel-plugin-svg-dynamic-title@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz#139b546dd0c3186b6e5db4fefc26cb0baea729d7" + integrity sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg== + +"@svgr/babel-plugin-svg-em-dimensions@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz#6543f69526632a133ce5cabab965deeaea2234a0" + integrity sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw== + +"@svgr/babel-plugin-transform-react-native-svg@^5.4.0": + version "5.4.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz#00bf9a7a73f1cad3948cdab1f8dfb774750f8c80" + integrity sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q== + +"@svgr/babel-plugin-transform-svg-component@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz#583a5e2a193e214da2f3afeb0b9e8d3250126b4a" + integrity sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ== + +"@svgr/babel-preset@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-5.5.0.tgz#8af54f3e0a8add7b1e2b0fcd5a882c55393df327" + integrity sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "^5.0.1" + "@svgr/babel-plugin-replace-jsx-attribute-value" "^5.0.1" + "@svgr/babel-plugin-svg-dynamic-title" "^5.4.0" + "@svgr/babel-plugin-svg-em-dimensions" "^5.4.0" + "@svgr/babel-plugin-transform-react-native-svg" "^5.4.0" + "@svgr/babel-plugin-transform-svg-component" "^5.5.0" + +"@svgr/core@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-5.5.0.tgz#82e826b8715d71083120fe8f2492ec7d7874a579" + integrity sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ== + dependencies: + "@svgr/plugin-jsx" "^5.5.0" + camelcase "^6.2.0" + cosmiconfig "^7.0.0" + +"@svgr/hast-util-to-babel-ast@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz#5ee52a9c2533f73e63f8f22b779f93cd432a5461" + integrity sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ== + dependencies: + "@babel/types" "^7.12.6" + +"@svgr/plugin-jsx@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz#1aa8cd798a1db7173ac043466d7b52236b369000" + integrity sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA== + dependencies: + "@babel/core" "^7.12.3" + "@svgr/babel-preset" "^5.5.0" + "@svgr/hast-util-to-babel-ast" "^5.5.0" + svg-parser "^2.0.2" + +"@svgr/plugin-svgo@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz#02da55d85320549324e201c7b2e53bf431fcc246" + integrity sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ== + dependencies: + cosmiconfig "^7.0.0" + deepmerge "^4.2.2" + svgo "^1.2.2" + +"@svgr/webpack@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-5.5.0.tgz#aae858ee579f5fa8ce6c3166ef56c6a1b381b640" + integrity sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g== + dependencies: + "@babel/core" "^7.12.3" + "@babel/plugin-transform-react-constant-elements" "^7.12.1" + "@babel/preset-env" "^7.12.1" + "@babel/preset-react" "^7.12.5" + "@svgr/core" "^5.5.0" + "@svgr/plugin-jsx" "^5.5.0" + "@svgr/plugin-svgo" "^5.5.0" + loader-utils "^2.0.0" + +"@testing-library/dom@^9.0.0": + version "9.3.1" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.1.tgz#8094f560e9389fb973fe957af41bf766937a9ee9" + integrity sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" - "@types/aria-query" "^4.2.0" - aria-query "^4.2.2" + "@types/aria-query" "^5.0.1" + aria-query "5.1.3" chalk "^4.1.0" - dom-accessibility-api "^0.5.6" - lz-string "^1.4.4" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" pretty-format "^27.0.2" -"@testing-library/jest-dom@^5.16.5": - version "5.16.5" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" - integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA== +"@testing-library/jest-dom@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.0.0.tgz#d2ba5a3fd13724d5966b3f8cd24d2cedcab4fa76" + integrity sha512-Ye2R3+/oM27jir8CzYPmuWdavTaKwNZcu0d22L9pO/vnOYE0wmrtpw79TQJa8H6gV8/i7yd+pLaqeLlA0rTMfg== dependencies: "@adobe/css-tools" "^4.0.1" "@babel/runtime" "^7.9.2" - "@types/testing-library__jest-dom" "^5.9.1" aria-query "^5.0.0" chalk "^3.0.0" css.escape "^1.5.1" @@ -1812,101 +2042,186 @@ lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react@^12.1.5": - version "12.1.5" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" - integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg== +"@testing-library/react@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.0.0.tgz#59030392a6792450b9ab8e67aea5f3cc18d6347c" + integrity sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg== dependencies: "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^8.0.0" - "@types/react-dom" "<18.0.0" + "@testing-library/dom" "^9.0.0" + "@types/react-dom" "^18.0.0" "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== -"@types/aria-query@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0" - integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A== +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== -"@types/babel__core@^7.1.12", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.3": - version "7.1.18" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.18.tgz#1a29abcc411a9c05e2094c98f9a1b7da6cdf49f8" - integrity sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ== +"@types/aria-query@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc" + integrity sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q== + +"@types/babel__core@*", "@types/babel__core@^7.1.12", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.7", "@types/babel__core@^7.20.1": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.2.tgz#215db4f4a35d710256579784a548907237728756" + integrity sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA== dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" "@types/babel__generator" "*" "@types/babel__template" "*" "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.1" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04" - integrity sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew== + version "7.6.5" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.5.tgz#281f4764bcbbbc51fdded0f25aa587b4ce14da95" + integrity sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w== dependencies: "@babel/types" "^7.0.0" +"@types/babel__helper-plugin-utils@^7.10.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@types/babel__helper-plugin-utils/-/babel__helper-plugin-utils-7.10.1.tgz#d24424d9cc7785a64cb1c1b41fac5c2d37c92aac" + integrity sha512-6RaT7i6r2rT6ouIDZ2Cd6dPkq4wn1F8pLyDO+7wPVsL1dodvORiZORImaD6j9FBcHjPGuERE0hhtwkuPNXsO0A== + dependencies: + "@types/babel__core" "*" + "@types/babel__template@*": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307" - integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg== + version "7.4.2" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.2.tgz#843e9f1f47c957553b0c374481dc4772921d6a6b" + integrity sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.0.13" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.13.tgz#1874914be974a492e1b4cb00585cabb274e8ba18" - integrity sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ== +"@types/babel__traverse@*", "@types/babel__traverse@^7.1.7": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.2.tgz#4ddf99d95cfdd946ff35d2b65c978d9c9bf2645d" + integrity sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw== dependencies: - "@babel/types" "^7.3.0" + "@babel/types" "^7.20.7" -"@types/color-name@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@types/babel__traverse@^7.0.6": + version "7.20.1" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.1.tgz#dd6f1d2411ae677dcb2db008c962598be31d6acf" + integrity sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg== + dependencies: + "@babel/types" "^7.20.7" + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/emoji-mart@^3.0.9": + version "3.0.9" + resolved "https://registry.yarnpkg.com/@types/emoji-mart/-/emoji-mart-3.0.9.tgz#2f7ef5d9ec194f28029c46c81a5fc1e5b0efa73c" + integrity sha512-qdBo/2Y8MXaJ/2spKjDZocuq79GpnOhkwMHnK2GnVFa8WYFgfA+ei6sil3aeWQPCreOKIx9ogPpR5+7MaOqYAA== + dependencies: + "@types/react" "*" + +"@types/escape-html@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-1.0.2.tgz#072b7b13784fb3cee9c2450c22f36405983f5e3c" + integrity sha512-gaBLT8pdcexFztLSPRtriHeXY/Kn4907uOCZ4Q3lncFBkheAWOuNt53ypsF8szgxbEJ513UeBzcf4utN0EzEwA== + +"@types/eslint@7 || 8": + version "8.44.2" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.2.tgz#0d21c505f98a89b8dd4d37fa162b09da6089199a" + integrity sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" + integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== -"@types/events@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" - integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/express-serve-static-core@^4.17.33": + version "4.17.35" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz#c95dd4424f0d32e525d23812aa8ab8e4d3906c4f" + integrity sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^4.17.17": + version "4.17.17" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" + integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" "@types/glob@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" - integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== + version "7.2.0" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== dependencies: - "@types/events" "*" "@types/minimatch" "*" "@types/node" "*" "@types/graceful-fs@^4.1.3": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" - integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" + integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== dependencies: "@types/node" "*" -"@types/hoist-non-react-statics@^3.3.0": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" - integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== +"@types/history@^4.7.11": + version "4.7.11" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" + integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== + +"@types/hoist-non-react-statics@^3.3.1": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#dc1e9ded53375d37603c479cc12c693b0878aa2a" + integrity sha512-YIQtIg4PKr7ZyqNPZObpxfHsHEmuB8dXCxd6qVcGuQVDK2bpsF7bYNnBJ4Nn7giuACZg+WewExgrtAJ3XnA4Xw== dependencies: "@types/react" "*" hoist-non-react-statics "^3.3.0" +"@types/http-link-header@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/http-link-header/-/http-link-header-1.0.3.tgz#899adf1d8d2036074514f3dbd148fb901ceff920" + integrity sha512-y8HkoD/vyid+5MrJ3aas0FvU3/BVBGcyG9kgxL0Zn4JwstA8CglFPnrR0RuzOjRCXwqzL5uxWC2IO7Ub0rMU2A== + dependencies: + "@types/node" "*" + +"@types/intl@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/intl/-/intl-1.2.0.tgz#1245511f13064402087979f498764611a3c758fc" + integrity sha512-BP+KwmOvD9AR5aoxnbyyPr3fAtpjEI/bVImHsotmpuC43+z0NAmjJ9cQbX7vPCq8XcvCeAVc8E3KSQPYNaPsUQ== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" - integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== "@types/istanbul-lib-report@*": version "3.0.0" @@ -1915,129 +2230,272 @@ dependencies: "@types/istanbul-lib-coverage" "*" -"@types/istanbul-reports@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2" - integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw== - dependencies: - "@types/istanbul-lib-coverage" "*" - "@types/istanbul-lib-report" "*" - "@types/istanbul-reports@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" - integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@*": - version "26.0.3" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.3.tgz#79534e0e94857171c0edc596db0ebe7cb7863251" - integrity sha512-v89ga1clpVL/Y1+YI0eIu1VMW+KU7Xl8PhylVtDKVWaSUHBHYPLXMQGBdrpHewaKoTvlXkksbYqPgz8b4cmRZg== +"@types/jest@^29.5.2": + version "29.5.5" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.5.tgz#727204e06228fe24373df9bae76b90f3e8236a2a" + integrity sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg== dependencies: - jest-diff "^25.2.1" - pretty-format "^25.2.1" + expect "^29.0.0" + pretty-format "^29.0.0" + +"@types/js-yaml@^4.0.5": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.6.tgz#4b3afd5158b8749095b1f096967b6d0f838d862f" + integrity sha512-ACTuifTSIIbyksx2HTon3aFtCKWcID7/h3XEmRpDYdMCXxPbl+m9GteOJeaAkiAta/NJaSFuA7ahZ0NkwajDSw== "@types/jsdom@^20.0.0": - version "20.0.0" - resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.0.tgz#4414fb629465167f8b7b3804b9e067bdd99f1791" - integrity sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA== + version "20.0.1" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808" + integrity sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ== dependencies: "@types/node" "*" "@types/tough-cookie" "*" parse5 "^7.0.0" -"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": - version "7.0.6" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" - integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== +"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8": + version "7.0.12" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" + integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== + +"@types/json-schema@^7.0.12": + version "7.0.13" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" + integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== + +"@types/json-stable-stringify@^1.0.32": + version "1.0.34" + resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.34.tgz#c0fb25e4d957e0ee2e497c1f553d7f8bb668fd75" + integrity sha512-s2cfwagOQAS8o06TcwKfr9Wx11dNGbH2E9vJz1cqV+a/LOyhWNLUNd6JSRYNzvB4d29UuJX2M0Dj9vE1T8fRXw== "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/lodash@^4.14.195": + version "4.14.198" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.198.tgz#4d27465257011aedc741a809f1269941fa2c5d4c" + integrity sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg== + +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== "@types/minimatch@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" + integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== -"@types/minimist@^1.2.0": +"@types/minimist@^1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== "@types/node@*": - version "14.11.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.1.tgz#56af902ad157e763f9ba63d671c39cda3193c835" - integrity sha512-oTQgnd0hblfLsJ6BvJzzSL+Inogp3lq9fGgqRkMB/ziKMgEUaFl801OncOzUmalfzt14N0oPHMK47ipl+wbTIw== + version "20.6.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.6.2.tgz#a065925409f59657022e9063275cd0b9bd7e1b12" + integrity sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw== + +"@types/node@14 || 16 || 17": + version "17.0.45" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" + integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== +"@types/npmlog@^4.1.4": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@types/npmlog/-/npmlog-4.1.4.tgz#30eb872153c7ead3e8688c476054ddca004115f6" + integrity sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ== + +"@types/object-assign@^4.0.30": + version "4.0.31" + resolved "https://registry.yarnpkg.com/@types/object-assign/-/object-assign-4.0.31.tgz#9493903157310e28d97b8775b3ce0a7c6373ae46" + integrity sha512-Xw3AoamIJzBgAV2/Rd/TnnpIpkVKeI1IkpOaxy6eIFR55AVnWd5WKqiIkkozamoN/+mSukK/4VgN9zcT7hbOIA== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/prettier@^2.1.5": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0" - integrity sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA== +"@types/pg@^8.6.6": + version "8.10.2" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.10.2.tgz#7814d1ca02c8071f4d0864c1b17c589b061dba43" + integrity sha512-MKFs9P6nJ+LAeHLU3V0cODEOgyThJ3OAnmOlsZsxux6sfQs3HRXR5bBn7xG5DjckEFhTAxsXi7k7cd0pCMxpJw== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^4.0.1" -"@types/prop-types@*": - version "15.7.3" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" - integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== +"@types/picomatch@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@types/picomatch/-/picomatch-2.3.0.tgz#75db5e75a713c5a83d5b76780c3da84a82806003" + integrity sha512-O397rnSS9iQI4OirieAtsDqvCj4+3eY1J+EPdNTKuHuRWIfUoGyzX294o8C4KJYaLqgSrd2o60c5EqCU8Zv02g== + +"@types/prop-types@*", "@types/prop-types@^15.7.5": + version "15.7.6" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.6.tgz#bbf819813d6be21011b8f5801058498bec555572" + integrity sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg== + +"@types/punycode@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/punycode/-/punycode-2.1.0.tgz#89e4f3d09b3f92e87a80505af19be7e0c31d4e83" + integrity sha512-PG5aLpW6PJOeV2fHRslP4IOMWn+G+Uq8CfnyJ+PDS8ndCbU+soO+fB3NKCKo0p/Jh2Y4aPaiQZsrOXFdzpcA6g== "@types/q@^1.5.1": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" - integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== + version "1.5.6" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.6.tgz#a6edffe8283910e46dc7a573621f928e6b47fa56" + integrity sha512-IKjZ8RjTSwD4/YG+2gtj7BPFRB/lNbWKTiSj3M7U/TD2B7HfYCxvp2Zz6xA2WIY7pAuL1QOUPw8gQRbUrrq4fQ== -"@types/react-dom@<18.0.0": - version "17.0.15" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.15.tgz#f2c8efde11521a4b7991e076cb9c70ba3bb0d156" - integrity sha512-Tr9VU9DvNoHDWlmecmcsE5ZZiUkYx+nKBzum4Oxe1K0yJVyBlfbq7H3eXjxXqJczBKqPGq3EgfTru4MgKb9+Yw== - dependencies: - "@types/react" "^17" +"@types/qs@*": + version "6.9.8" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.8.tgz#f2a7de3c107b89b441e071d5472e6b726b4adf45" + integrity sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg== -"@types/react-redux@^7.1.20": - version "7.1.20" - resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.20.tgz#42f0e61ababb621e12c66c96dda94c58423bd7df" - integrity sha512-q42es4c8iIeTgcnB+yJgRTTzftv3eYYvCZOh1Ckn2eX/3o5TdsQYKUWpLoLuGlcY/p+VAhV9IOEZJcWk/vfkXw== +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/react-dom@^18.0.0", "@types/react-dom@^18.2.4": + version "18.2.7" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63" + integrity sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA== + dependencies: + "@types/react" "*" + +"@types/react-helmet@^6.1.6": + version "6.1.6" + resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.6.tgz#7d1afd8cbf099616894e8240e9ef70e3c6d7506d" + integrity sha512-ZKcoOdW/Tg+kiUbkFCBtvDw0k3nD4HJ/h/B9yWxN4uDO8OkRksWTO+EL+z/Qu3aHTeTll3Ro0Cc/8UhwBCMG5A== + dependencies: + "@types/react" "*" + +"@types/react-immutable-proptypes@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#c045fb48ba28c34c9d759abc3a51a04b5321b77a" + integrity sha512-NRH4W4mgymzyM2gnAG+i2VoOdWIBOQlJlSyAgnFiBTdJ0l8IVeyCtdWP8g6Lra59sUBj2XUO/+DkfmrRAxj6UA== + dependencies: + "@types/prop-types" "*" + immutable "^3.8.2" + +"@types/react-motion@^0.0.34": + version "0.0.34" + resolved "https://registry.yarnpkg.com/@types/react-motion/-/react-motion-0.0.34.tgz#789ff2063e2f7fbb6085d291135c442e8b35291a" + integrity sha512-/rFI22Vg4Xzb47hXtS06WkzUGRu+Vb3yDleuxiqzGj0JbXYXQUCgwSa2ZU12K7ubKi4C8xsdIN3xt4Z4fjSdPw== + dependencies: + "@types/react" "*" + +"@types/react-overlays@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/react-overlays/-/react-overlays-3.1.0.tgz#2efefa5407562c5aeb34a03336d070a0f74c6274" + integrity sha512-NzZZHFLj7M7+I+p5rDdVHtm6AeVYQPShVxALiLYhR9leJSX8XujPsYuY+vh7/mzFjv6XR7PxHBAdlFGNaN6QDQ== + dependencies: + react-overlays "*" + +"@types/react-router-dom@^5.3.3": + version "5.3.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" + integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.20" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c" + integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + +"@types/react-select@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-5.0.1.tgz#04fc85edd34a72675a0ab56ad4c30428aab0e444" + integrity sha512-h5Im0AP0dr4AVeHtrcvQrLV+gmPa7SA0AGdxl2jOhtwiE6KgXBFSogWw8az32/nusE6AQHlCOHQWjP1S/+oMWA== + dependencies: + react-select "*" + +"@types/react-sparklines@^1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@types/react-sparklines/-/react-sparklines-1.7.2.tgz#c14e80623abd3669a10f18d13f6fb9fbdc322f70" + integrity sha512-N1GwO7Ri5C5fE8+CxhiDntuSw1qYdGytBuedKrCxWpaojXm4WnfygbdBdc5sXGX7feMxDXBy9MNhxoUTwrMl4A== + dependencies: + "@types/react" "*" + +"@types/react-swipeable-views@^0.13.1": + version "0.13.2" + resolved "https://registry.yarnpkg.com/@types/react-swipeable-views/-/react-swipeable-views-0.13.2.tgz#c37cc8978ae60ab0dff209ef3eb1f77185aef330" + integrity sha512-FiszBm9M0JicAgzO/IwDqpfHQRUEjPZA88UexYsVD6qHJBf5LrbGjR5Mw4+yZbf8ZxJneNqOsZbe4WGjOYG7iQ== + dependencies: + "@types/react" "*" + +"@types/react-test-renderer@^18.0.0": + version "18.0.2" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-18.0.2.tgz#44243977eec18ab8cda88d8977437f47a0d3fdbe" + integrity sha512-tJzMn+9GHDrdrLe0O4rwJELDfTrmdJbCn/UdYyzjlnPiXYXDl5FBNzdw4PVk2R3hJvSHKFjZcRgvZc12lV0p5Q== + dependencies: + "@types/react" "*" + +"@types/react-textarea-autosize@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@types/react-textarea-autosize/-/react-textarea-autosize-8.0.0.tgz#f68f388552aaa608328b3e352d9a23ad7e0f72e4" + integrity sha512-KVqk+/+RMQB3ZDpk7ZTpYHauU3Ue+Y0f09POvGaEpaGb+izzbpoM47tkDGlbF37iT7JYZ8QFwLzqiOPYbQaztA== + dependencies: + react-textarea-autosize "*" + +"@types/react-toggle@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/react-toggle/-/react-toggle-4.0.3.tgz#8db98ac8d2c5e8c03c2d3a42027555c1cd2289da" + integrity sha512-57QdMWeeQdRjM2/p+udgYerxUbSkmeUIW18kwUttcci6GHkgxoqCsDZfRtsCsAHcvvM5VBQdtDUEgLWo2e87mA== dependencies: - "@types/hoist-non-react-statics" "^3.3.0" "@types/react" "*" - hoist-non-react-statics "^3.3.0" - redux "^4.0.0" "@types/react-transition-group@^4.4.0": - version "4.4.3" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.3.tgz#b0994da0a7023d67dbb4a8910a62112bc00d5688" - integrity sha512-fUx5muOWSYP8Bw2BUQ9M9RK9+W1XBK/7FLJ8PTQpnpTEkn0ccyMffyEQvan4C3h53gHdx7KE5Qrxi/LnUGQtdg== + version "4.4.6" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.6.tgz#18187bcda5281f8e10dfc48f0943e2fdf4f75e2e" + integrity sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew== dependencies: "@types/react" "*" -"@types/react@*": - version "17.0.3" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79" - integrity sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg== +"@types/react@*", "@types/react@16 || 17 || 18", "@types/react@>=16.9.11", "@types/react@^18.0.26", "@types/react@^18.2.7": + version "18.2.22" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.22.tgz#abe778a1c95a07fa70df40a52d7300a40b949ccb" + integrity sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" csstype "^3.0.2" -"@types/react@^17": - version "17.0.44" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.44.tgz#c3714bd34dd551ab20b8015d9d0dbec812a51ec7" - integrity sha512-Ye0nlw09GeMp2Suh8qoOv0odfgCoowfM/9MG6WeRD60Gq9wS90bdkdRtYbRkNhXOpG4H+YXGvj4wOWhAC0LJ1g== +"@types/redux-immutable@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/redux-immutable/-/redux-immutable-4.0.3.tgz#db92a281aa9a55a7b63bc1f20a233790305a1f06" + integrity sha512-wXUApt9ib9MGUqoHUMbQmQhqCkvykMHBW3z/P7DISMigFGpGRQ0kkbv7we0XNiv5sYEtEiZzNCEDm+W6ei04DA== dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" + immutable "^4.0.0-rc.1" + redux "^4.0.0" + +"@types/requestidlecallback@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@types/requestidlecallback/-/requestidlecallback-0.3.5.tgz#132529751a4717fe7dc55fef5e930336f229543c" + integrity sha512-Uh49VrVTPfU0y/qIvXXYuRmd/sKLfVgQWZU1t8FWH22AIJyQbCei1aSmXdMDAijwGUFhBDpJmksiHEDsfiE/cg== "@types/resolve@1.17.1": version "1.17.1" @@ -2047,26 +2505,45 @@ "@types/node" "*" "@types/scheduler@*": - version "0.16.1" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" - integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== + version "0.16.3" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" + integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== -"@types/schema-utils@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/schema-utils/-/schema-utils-1.0.0.tgz#295d36f01e2cb8bc3207ca1d9a68e210db6b40cb" - integrity sha512-YesPanU1+WCigC/Aj1Mga8UCOjHIfMNHZ3zzDsUY7lI8GlKnh/Kv2QwJOQ+jNQ36Ru7IfzSedlG14hppYaN13A== +"@types/semver@^7.5.0": + version "7.5.2" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.2.tgz#31f6eec1ed7ec23f4f05608d3a2d381df041f564" + integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw== + +"@types/send@*": + version "0.17.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" + integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.1" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.1.tgz#86b1753f0be4f9a1bee68d459fcda5be4ea52b5d" + integrity sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ== + dependencies: + "@types/mime" "*" + "@types/node" "*" + +"@types/source-list-map@*": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" + integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== "@types/stack-utils@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" - integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== -"@types/testing-library__jest-dom@^5.9.1": - version "5.9.1" - resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.1.tgz#aba5ee062b7880f69c212ef769389f30752806e5" - integrity sha512-yYn5EKHO3MPEMSOrcAb1dLWY+68CG29LiXKsWmmpVHqoP5+ZRiAVLyUHvPNrO2dABDdUGZvavMsaGpWNjM6N2g== - dependencies: - "@types/jest" "*" +"@types/tapable@^1": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" + integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== "@types/tough-cookie@*": version "4.0.2" @@ -2074,35 +2551,175 @@ integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== "@types/trusted-types@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" - integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.3.tgz#a136f83b0758698df454e328759dbd3d44555311" + integrity sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g== + +"@types/uglify-js@*": + version "3.17.1" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.17.1.tgz#e0ffcef756476410e5bce2cb01384ed878a195b5" + integrity sha512-GkewRA4i5oXacU/n4MA9+bLgt5/L3F1mKrYvFGm7r2ouLXhRKjuWwo9XHNnbx6WF3vlGW21S3fCvgqxvxXXc5g== + dependencies: + source-map "^0.6.1" + +"@types/use-sync-external-store@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" + integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== + +"@types/uuid@^9.0.0": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.4.tgz#e884a59338da907bda8d2ed03e01c5c49d036f1c" + integrity sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA== + +"@types/warning@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52" + integrity sha512-t/Tvs5qR47OLOr+4E9ckN8AmP2Tf16gWq+/qA4iUGS/OOyHVO8wv2vjJuX8SNOUTJyWb+2t7wJm6cXILFnOROA== + +"@types/webpack-sources@*": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b" + integrity sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg== + dependencies: + "@types/node" "*" + "@types/source-list-map" "*" + source-map "^0.7.3" + +"@types/webpack@^4.41.33": + version "4.41.33" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.33.tgz#16164845a5be6a306bcbe554a8e67f9cac215ffc" + integrity sha512-PPajH64Ft2vWevkerISMtnZ8rTs4YmRbs+23c402J0INmxDKCrhZNvwZYtzx96gY2wAtXdrK1BS2fiC8MlLr3g== + dependencies: + "@types/node" "*" + "@types/tapable" "^1" + "@types/uglify-js" "*" + "@types/webpack-sources" "*" + anymatch "^3.0.0" + source-map "^0.6.0" "@types/yargs-parser@*": - version "15.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" - integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== -"@types/yargs@^15.0.0": - version "15.0.5" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79" - integrity sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w== +"@types/yargs@^17.0.24", "@types/yargs@^17.0.8": + version "17.0.24" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" + integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== dependencies: "@types/yargs-parser" "*" -"@types/yargs@^16.0.0": - version "16.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.3.tgz#4b6d35bb8e680510a7dc2308518a80ee1ef27e01" - integrity sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ== +"@typescript-eslint/eslint-plugin@^6.0.0": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.2.tgz#f18cc75c9cceac8080a9dc2e7d166008c5207b9f" + integrity sha512-ooaHxlmSgZTM6CHYAFRlifqh1OAr3PAQEwi7lhYhaegbnXrnh7CDcHmc3+ihhbQC7H0i4JF0psI5ehzkF6Yl6Q== dependencies: - "@types/yargs-parser" "*" + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.7.2" + "@typescript-eslint/type-utils" "6.7.2" + "@typescript-eslint/utils" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" -"@types/yargs@^17.0.8": - version "17.0.10" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.10.tgz#591522fce85d8739bca7b8bb90d048e4478d186a" - integrity sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA== +"@typescript-eslint/parser@^6.0.0": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.2.tgz#e0ae93771441b9518e67d0660c79e3a105497af4" + integrity sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw== dependencies: - "@types/yargs-parser" "*" + "@typescript-eslint/scope-manager" "6.7.2" + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/typescript-estree" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.2.tgz#cf59a2095d2f894770c94be489648ad1c78dc689" + integrity sha512-bgi6plgyZjEqapr7u2mhxGR6E8WCzKNUFWNh6fkpVe9+yzRZeYtDTbsIBzKbcxI+r1qVWt6VIoMSNZ4r2A+6Yw== + dependencies: + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" + +"@typescript-eslint/type-utils@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.2.tgz#ed921c9db87d72fa2939fee242d700561454f367" + integrity sha512-36F4fOYIROYRl0qj95dYKx6kybddLtsbmPIYNK0OBeXv2j9L5nZ17j9jmfy+bIDHKQgn2EZX+cofsqi8NPATBQ== + dependencies: + "@typescript-eslint/typescript-estree" "6.7.2" + "@typescript-eslint/utils" "6.7.2" + debug "^4.3.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== + +"@typescript-eslint/types@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.2.tgz#75a615a6dbeca09cafd102fe7f465da1d8a3c066" + integrity sha512-flJYwMYgnUNDAN9/GAI3l8+wTmvTYdv64fcH8aoJK76Y+1FCZ08RtI5zDerM/FYT5DMkAc+19E4aLmd5KqdFyg== + +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== + 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" + +"@typescript-eslint/typescript-estree@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.2.tgz#ce5883c23b581a5caf878af641e49dd0349238c7" + integrity sha512-kiJKVMLkoSciGyFU0TOY0fRxnp9qq1AzVOHNeN1+B9erKFCJ4Z8WdjAkKQPP+b1pWStGFqezMLltxO+308dJTQ== + dependencies: + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.2.tgz#b9ef0da6f04932167a9222cb4ac59cb187165ebf" + integrity sha512-ZCcBJug/TS6fXRTsoTkgnsvyWSiXwMNiPzBUani7hDidBdj1779qwM1FIAmpH4lvlOZNF3EScsxxuGifjpLSWQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.7.2" + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/typescript-estree" "6.7.2" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== + dependencies: + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" + +"@typescript-eslint/visitor-keys@6.7.2": + version "6.7.2" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.2.tgz#4cb2bd786f1f459731b0ad1584c9f73e1c7a4d5c" + integrity sha512-uVw9VIMFBUTz8rIeaUT3fFe8xIUx8r4ywAdlQv1ifH+6acn/XF8Y6rwJ7XNmkNMDrTW+7+vxFFPIF40nJCVsMQ== + dependencies: + "@typescript-eslint/types" "6.7.2" + eslint-visitor-keys "^3.4.1" "@webassemblyjs/ast@1.9.0": version "1.9.0" @@ -2292,35 +2909,25 @@ acorn-globals@^7.0.0: acorn "^8.1.0" acorn-walk "^8.0.2" -acorn-jsx@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" - integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.0.0.tgz#56ae4c0f434a45fff4a125e7ea95fa9c98f67a16" - integrity sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA== - -acorn-walk@^8.0.2: +acorn-walk@^8.0.0, acorn-walk@^8.0.2: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== acorn@^6.4.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" - integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== + version "6.4.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" + integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== -acorn@^7.4.0: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -acorn@^8.0.4, acorn@^8.1.0, acorn@^8.5.0, acorn@^8.8.0: - version "8.8.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" - integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== +acorn@^8.0.4, acorn@^8.1.0, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== agent-base@6: version "6.0.2" @@ -2347,7 +2954,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -2357,47 +2964,27 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.1: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.5.0.tgz#695528274bcb5afc865446aa275484049a18ae4b" - integrity sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ== +ajv@^8.0.1, ajv@^8.6.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -ajv@^8.6.0: - version "8.11.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" - integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - -alphanum-sort@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= - ansi-colors@^3.0.0: version "3.2.4" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== -ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - -ansi-escapes@^4.2.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" - integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: - type-fest "^0.11.0" + type-fest "^0.21.3" ansi-html-community@0.0.8: version "0.0.8" @@ -2407,27 +2994,22 @@ ansi-html-community@0.0.8: ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" @@ -2437,11 +3019,10 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" - integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: - "@types/color-name" "^1.1.1" color-convert "^2.0.1" ansi-styles@^5.0.0: @@ -2449,6 +3030,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +ansi-styles@^6.0.0, ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -2457,10 +3043,10 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@^3.0.3, anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== +anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" @@ -2470,10 +3056,10 @@ anymatch@^3.0.3, anymatch@~3.1.1: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== -aproba@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +are-docs-informative@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/are-docs-informative/-/are-docs-informative-0.0.2.tgz#387f0e93f5d45280373d387a59d34c96db321963" + integrity sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig== are-we-there-yet@^4.0.0: version "4.0.0" @@ -2495,23 +3081,31 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" - integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== +aria-query@5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" + integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== dependencies: - "@babel/runtime" "^7.10.2" - "@babel/runtime-corejs3" "^7.10.2" + deep-equal "^2.0.5" aria-query@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c" - integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg== + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + dependencies: + dequal "^2.0.3" + +aria-query@^5.1.3: + version "5.2.1" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.2.1.tgz#bc285d9d654d1df121bcd0c134880d415ca67c15" + integrity sha512-7uFg4b+lETFgdaJyETnILsXgnnzVnkHcgRbwbPwevm5x/LmUlt3MjczMRe1zg824iBgXZNRPTBftNYyRSKLp2g== + dependencies: + dequal "^2.0.3" arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== arr-flatten@^1.1.0: version "1.1.0" @@ -2521,33 +3115,41 @@ arr-flatten@^1.1.0: arr-union@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== + +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== array-flatten@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== -array-includes@^3.1.4, array-includes@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" - integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== +array-includes@^3.1.5, array-includes@^3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" + integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" - get-intrinsic "^1.1.1" + es-abstract "^1.20.4" + get-intrinsic "^1.1.3" is-string "^1.0.7" array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= + integrity sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng== dependencies: array-uniq "^1.0.1" @@ -2559,31 +3161,77 @@ array-union@^2.1.0: array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= + integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== -array.prototype.flat@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" - integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg== +array.prototype.findlastindex@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz#bc229aef98f6bd0533a2bc61ff95209875526c9b" + integrity sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - -array.prototype.flatmap@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz#a7e8ed4225f4788a70cd910abcf0791e76a5534f" - integrity sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" es-shim-unscopables "^1.0.0" + get-intrinsic "^1.1.3" + +array.prototype.flat@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" + integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" + integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" + +array.prototype.reduce@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz#6b20b0daa9d9734dd6bc7ea66b5bbce395471eac" + integrity sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.7" + +array.prototype.tosorted@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz#ccf44738aa2b5ac56578ffda97c03fd3e23dd532" + integrity sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" + get-intrinsic "^1.1.3" + +arraybuffer.prototype.slice@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz#9b5ea3868a6eebc30273da577eb888381c0044bb" + integrity sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" arrify@^1.0.1: version "1.0.1" @@ -2616,12 +3264,12 @@ assert@^1.1.1: assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== ast-types-flow@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" - integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= + integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== astral-regex@^2.0.0: version "2.0.0" @@ -2629,16 +3277,23 @@ astral-regex@^2.0.0: integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + version "1.0.6" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.6.tgz#52f1d9403818c179b7561e11a5d1b77eb2160e77" + integrity sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg== async-limiter@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async@^2.6.2: +async-mutex@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.0.tgz#ae8048cd4d04ace94347507504b3cf15e631c25f" + integrity sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA== + dependencies: + tslib "^2.4.0" + +async@^2.6.4: version "2.6.4" resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== @@ -2646,14 +3301,21 @@ async@^2.6.2: lodash "^4.17.14" async@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" - integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== + +asynciterator.prototype@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz#8c5df0514936cdd133604dfcc9d3fb93f09b2b62" + integrity sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg== + dependencies: + has-symbols "^1.0.3" asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== at-least-node@^1.0.0: version "1.0.0" @@ -2665,80 +3327,83 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -autoprefixer@^9.8.8: - version "9.8.8" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a" - integrity sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA== +autoprefixer@^10.4.14: + version "10.4.16" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.16.tgz#fad1411024d8670880bdece3970aa72e3572feb8" + integrity sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ== dependencies: - browserslist "^4.12.0" - caniuse-lite "^1.0.30001109" + browserslist "^4.21.10" + caniuse-lite "^1.0.30001538" + fraction.js "^4.3.6" normalize-range "^0.1.2" - num2fraction "^1.2.2" - picocolors "^0.2.1" - postcss "^7.0.32" - postcss-value-parser "^4.1.0" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" -axe-core@^4.4.3: - version "4.4.3" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.3.tgz#11c74d23d5013c0fa5d183796729bc3482bd2f6f" - integrity sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w== +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -axios@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.1.3.tgz#8274250dada2edf53814ed7db644b9c2866c1e35" - integrity sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA== +axe-core@^4.6.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.2.tgz#040a7342b20765cb18bb50b628394c21bccc17a0" + integrity sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g== + +axios@^1.4.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.5.0.tgz#f02e4af823e2e46a9768cfc74691fdd0517ea267" + integrity sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0" proxy-from-env "^1.1.0" -axobject-query@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" - integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== - -babel-jest@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.2.1.tgz#213c47e28072de11bdb98c9d29b89f2ab99664f1" - integrity sha512-gQJwArok0mqoREiCYhXKWOgUhElJj9DpnssW6GL8dG7ARYqHEhrM9fmPHTjdqEGRVXZAd6+imo3/Vwa8TjLcsw== +axobject-query@^3.1.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" + integrity sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg== dependencies: - "@jest/transform" "^29.2.1" + dequal "^2.0.3" + +babel-jest@^29.5.0, babel-jest@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.6.2.tgz#cada0a59e07f5acaeb11cbae7e3ba92aec9c1126" + integrity sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A== + dependencies: + "@jest/transform" "^29.6.2" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.2.0" + babel-preset-jest "^29.5.0" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" -babel-jest@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.2.2.tgz#2c15abd8c2081293c9c3f4f80a4ed1d51542fee5" - integrity sha512-kkq2QSDIuvpgfoac3WZ1OOcHsQQDU5xYk2Ql7tLdJ8BVAYbefEXal+NfS45Y5LVZA7cxC8KYcQMObpCt1J025w== - dependencies: - "@jest/transform" "^29.2.2" - "@types/babel__core" "^7.1.14" - babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.2.0" - chalk "^4.0.0" - graceful-fs "^4.2.9" - slash "^3.0.0" - -babel-loader@^8.2.5: - version "8.2.5" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e" - integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ== +babel-loader@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8" + integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q== dependencies: find-cache-dir "^3.3.1" loader-utils "^2.0.0" make-dir "^3.1.0" schema-utils "^2.6.5" -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" - integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== +babel-plugin-formatjs@^10.5.1: + version "10.5.6" + resolved "https://registry.yarnpkg.com/babel-plugin-formatjs/-/babel-plugin-formatjs-10.5.6.tgz#394908db02594ec34d4341fb38342302cd304a45" + integrity sha512-XlE8WHF/ZstS5K3ZCWb5nQ6e9u6KpNquTpHpjteGaMSguSjvbfNb7CsF4YHq1fTPBdHWNspA3qfAqMGgHBO4mw== dependencies: - object.assign "^4.1.0" + "@babel/core" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-jsx" "7" + "@babel/traverse" "7" + "@babel/types" "^7.12.11" + "@formatjs/icu-messageformat-parser" "2.6.2" + "@formatjs/ts-transformer" "3.13.5" + "@types/babel__core" "^7.1.7" + "@types/babel__helper-plugin-utils" "^7.10.0" + "@types/babel__traverse" "^7.1.7" + tslib "^2.4.0" babel-plugin-istanbul@^6.1.1: version "6.1.1" @@ -2751,10 +3416,10 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.2.0.tgz#23ee99c37390a98cfddf3ef4a78674180d823094" - integrity sha512-TnspP2WNiR3GLfCsUNHqeXw0RoQ2f9U5hQ5L3XFpwuO8htQmSrhh8qsB6vi5Yi8+kuynN1yjDjQsPfkebmB6ZA== +babel-plugin-jest-hoist@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a" + integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -2772,16 +3437,7 @@ babel-plugin-lodash@^3.3.4: lodash "^4.17.10" require-package-name "^2.0.1" -babel-plugin-macros@^2.6.1: - version "2.8.0" - resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" - integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== - dependencies: - "@babel/runtime" "^7.7.2" - cosmiconfig "^6.0.0" - resolve "^1.12.0" - -babel-plugin-macros@^3.0.1: +babel-plugin-macros@^3.0.1, babel-plugin-macros@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== @@ -2790,29 +3446,29 @@ babel-plugin-macros@^3.0.1: cosmiconfig "^7.0.0" resolve "^1.19.0" -babel-plugin-polyfill-corejs2@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" - integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== +babel-plugin-polyfill-corejs2@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz#8097b4cb4af5b64a1d11332b6fb72ef5e64a054c" + integrity sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg== dependencies: - "@babel/compat-data" "^7.17.7" - "@babel/helper-define-polyfill-provider" "^0.3.3" - semver "^6.1.1" + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.4.2" + semver "^6.3.1" -babel-plugin-polyfill-corejs3@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a" - integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== +babel-plugin-polyfill-corejs3@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz#b4f719d0ad9bb8e0c23e3e630c0c8ec6dd7a1c52" + integrity sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.3" - core-js-compat "^3.25.1" + "@babel/helper-define-polyfill-provider" "^0.4.2" + core-js-compat "^3.31.0" -babel-plugin-polyfill-regenerator@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" - integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== +babel-plugin-polyfill-regenerator@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz#80d0f3e1098c080c8b5a65f41e9427af692dc326" + integrity sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.3" + "@babel/helper-define-polyfill-provider" "^0.4.2" babel-plugin-preval@^5.1.0: version "5.1.0" @@ -2824,28 +3480,15 @@ babel-plugin-preval@^5.1.0: babel-plugin-macros "^3.0.1" require-from-string "^2.0.2" -babel-plugin-react-intl@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/babel-plugin-react-intl/-/babel-plugin-react-intl-6.2.0.tgz#ac51ca757f318938792fc91e1747515e9225386a" - integrity sha512-ajGpa14mLzyDgdOS75DRlQ0aEL+q7iSCB77613YUPOZbxnAvfB0wg+gLngbd/43eKRw7a4y+IzO3P8kDHl40nA== - dependencies: - "@babel/core" "^7.7.2" - "@babel/helper-plugin-utils" "^7.0.0" - "@types/babel__core" "^7.1.3" - "@types/schema-utils" "^1.0.0" - fs-extra "^8.1.0" - intl-messageformat-parser "^4.1.1" - schema-utils "^2.2.0" - babel-plugin-transform-react-remove-prop-types@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a" integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA== babel-preset-current-node-syntax@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.0.tgz#cf5feef29551253471cfa82fc8e0f5063df07a77" - integrity sha512-mGkvkpocWJes1CmMKtgGUwCeeq0pOhALyymozzDWYomHTbDLwueDYG6p4TK1YOeYHCzBzYPsWkgTto10JubI1Q== + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-bigint" "^7.8.3" @@ -2860,30 +3503,25 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.2.0.tgz#3048bea3a1af222e3505e4a767a974c95a7620dc" - integrity sha512-z9JmMJppMxNv8N7fNRHvhMg9cvIkMxQBXgFkane3yKVEvEOP+kB50lk8DFRvF9PGqbyXxlmebKWhuDORO8RgdA== +babel-preset-jest@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2" + integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== dependencies: - babel-plugin-jest-hoist "^29.2.0" + babel-plugin-jest-hoist "^29.5.0" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== balanced-match@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9" integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA== -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -base64-js@^1.3.1: +base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -2904,7 +3542,12 @@ base@^0.11.1: batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== + +big-integer@^1.6.44: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== big.js@^5.2.2: version "5.2.2" @@ -2917,9 +3560,9 @@ binary-extensions@^1.0.0: integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== binary-extensions@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" - integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== bindings@^1.5.0: version "1.5.0" @@ -2928,30 +3571,35 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bluebird@^3.5.5: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +bintrees@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" + integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw== -blurhash@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-2.0.3.tgz#5c1166bf5b65e09e337fe5b8c6b53e1218085b0b" - integrity sha512-nTnJTOheiaV3b189f7rH5AbbrnQB2r3CcOZBg47GUDaE9DrxyBPD2w0HYp4ME2UBlTP7LMIa6nMWqg/58oyIzA== +blueimp-load-image@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/blueimp-load-image/-/blueimp-load-image-3.0.0.tgz#d71c39440a7d2f1a83e3e86a625e329116a51705" + integrity sha512-Q9rFbd4ZUNvzSFmRXx9MoG0RwWwJeMjjEUbG7WIOJgUg22Jgkow0wL5b35B6qwiBscxACW9OHdrP5s2vQ3x8DQ== + +blurhash@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-2.0.5.tgz#efde729fc14a2f03571a6aa91b49cba80d1abe4b" + integrity sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w== bmp-js@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233" - integrity sha1-4Fpj95amwf8l9Hcex62twUjAcjM= + integrity sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw== bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.1.1: - version "5.1.3" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b" - integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ== +bn.js@^5.0.0, bn.js@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== body-parser@1.20.1: version "1.20.1" @@ -2974,7 +3622,7 @@ body-parser@1.20.1: bonjour@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" - integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= + integrity sha512-RaVTblr+OnEli0r/ud8InrU7D+G0y6aJhlxaLa6Pwty4+xoxboF1BsUI45tujvRpbj9dQVoglChqonGAsjEBYg== dependencies: array-flatten "^2.1.0" deep-equal "^1.0.1" @@ -2986,7 +3634,14 @@ bonjour@^3.5.0: boolbase@^1.0.0, boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +bplist-parser@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" + integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== + dependencies: + big-integer "^1.6.44" brace-expansion@^1.1.7: version "1.1.11" @@ -3029,7 +3684,7 @@ braces@^3.0.2, braces@~3.0.2: brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" @@ -3063,11 +3718,11 @@ browserify-des@^1.0.0: safe-buffer "^5.1.2" browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== dependencies: - bn.js "^4.1.0" + bn.js "^5.0.0" randombytes "^2.0.1" browserify-sign@^4.0.0: @@ -3092,26 +3747,25 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@^4.0.0, browserslist@^4.12.0: - version "4.16.6" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" - integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== +browserslist@^4.0.0, browserslist@^4.21.4: + version "4.21.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.8.tgz#db2498e1f4b80ed199c076248a094935860b6017" + integrity sha512-j+7xYe+v+q2Id9qbBeCI8WX5NmZSRe8es1+0xntD/+gaWXznP8tFEkv5IgSaHf5dS1YwVMbX/4W6m937mj+wQw== dependencies: - caniuse-lite "^1.0.30001219" - colorette "^1.2.2" - electron-to-chromium "^1.3.723" - escalade "^3.1.1" - node-releases "^1.1.71" + caniuse-lite "^1.0.30001502" + electron-to-chromium "^1.4.428" + node-releases "^2.0.12" + update-browserslist-db "^1.0.11" -browserslist@^4.21.3, browserslist@^4.21.4: - version "4.21.4" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" - integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== +browserslist@^4.21.10, browserslist@^4.21.9: + version "4.21.10" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" + integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== dependencies: - caniuse-lite "^1.0.30001400" - electron-to-chromium "^1.4.251" - node-releases "^2.0.6" - update-browserslist-db "^1.0.9" + caniuse-lite "^1.0.30001517" + electron-to-chromium "^1.4.477" + node-releases "^2.0.13" + update-browserslist-db "^1.0.11" bser@2.1.1: version "2.1.1" @@ -3138,7 +3792,7 @@ buffer-writer@2.0.0: buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= + integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== buffer@^4.3.0: version "4.9.2" @@ -3164,7 +3818,7 @@ bufferutil@^4.0.7: dependencies: node-gyp-build "^4.3.0" -builtin-modules@^3.1.0: +builtin-modules@^3.1.0, builtin-modules@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== @@ -3172,44 +3826,31 @@ builtin-modules@^3.1.0: builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= + integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ== + +bundle-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" + integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== + dependencies: + run-applescript "^5.0.0" bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cacache@^12.0.2: - version "12.0.4" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" - integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - cacache@^15.0.5: - version "15.0.5" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.0.5.tgz#69162833da29170d6732334643c60e005f5f17d0" - integrity sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A== + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== dependencies: + "@npmcli/fs" "^1.0.0" "@npmcli/move-file" "^1.0.1" chownr "^2.0.0" fs-minipass "^2.0.0" @@ -3224,7 +3865,7 @@ cacache@^15.0.5: p-map "^4.0.0" promise-inflight "^1.0.1" rimraf "^3.0.2" - ssri "^8.0.0" + ssri "^8.0.1" tar "^6.0.2" unique-filename "^1.1.1" @@ -3251,48 +3892,30 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" -caller-callsite@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" - integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= - dependencies: - callsites "^2.0.0" - -caller-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" - integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= - dependencies: - caller-callsite "^2.0.0" - -callsites@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase-keys@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" - integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== +camelcase-keys@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-7.0.2.tgz#d048d8c69448745bb0de6fc4c1c52a30dfbe7252" + integrity sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg== dependencies: - camelcase "^5.3.1" - map-obj "^4.0.0" - quick-lru "^4.0.1" + camelcase "^6.3.0" + map-obj "^4.1.0" + quick-lru "^5.1.1" + type-fest "^1.2.1" camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" - integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== +camelcase@^6.2.0, camelcase@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-api@^3.0.0: version "3.0.0" @@ -3304,23 +3927,27 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001400: - version "1.0.30001414" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001414.tgz" - integrity sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg== +caniuse-lite@^1.0.0: + version "1.0.30001503" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001503.tgz#88b6ff1b2cf735f1f3361dc1a15b59f0561aa398" + integrity sha512-Sf9NiF+wZxPfzv8Z3iS0rXM1Do+iOy2Lxvib38glFX+08TCYYYGR5fRJXk4d77C4AYwhUjgYgMsMudbh2TqCKw== -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" +caniuse-lite@^1.0.30001502: + version "1.0.30001515" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001515.tgz#418aefeed9d024cd3129bfae0ccc782d4cb8f12b" + integrity sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA== -chalk@^2.0.0, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: +caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001538: + version "1.0.30001538" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001538.tgz#9dbc6b9af1ff06b5eb12350c2012b3af56744f3f" + integrity sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw== + +chalk@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3" + integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA== + +chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -3351,19 +3978,19 @@ char-regex@^1.0.2: integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== "chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: - anymatch "~3.1.1" + anymatch "~3.1.2" braces "~3.0.2" - glob-parent "~5.1.0" + glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.5.0" + readdirp "~3.6.0" optionalDependencies: - fsevents "~2.3.1" + fsevents "~2.3.2" chokidar@^2.1.8: version "2.1.8" @@ -3384,27 +4011,20 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== - dependencies: - tslib "^1.9.0" + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== ci-info@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" - integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== + version "3.8.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" + integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" @@ -3414,10 +4034,15 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +circular-dependency-plugin@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz#39e836079db1d3cf2f988dc48c5188a44058b600" + integrity sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ== + cjs-module-lexer@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.1.tgz#2fd46d9906a126965aa541345c499aaa18e8cd73" - integrity sha512-jVamGdJPDeuQilKhvVn1h3knuMOZzr8QDnpk+M9aMlCaMkTDd6fBWPhiDqFvFZ07pL0liqabAiuy8SY4jGHeaw== + version "1.2.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== class-utils@^0.3.5: version "0.3.6" @@ -3439,6 +4064,29 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cli-truncate@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" + integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== + dependencies: + slice-ansi "^5.0.0" + string-width "^5.0.0" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -3466,15 +4114,15 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -cluster-key-slot@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" - integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== +cluster-key-slot@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" + integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== coa@^2.0.2: version "2.0.2" @@ -3491,24 +4139,24 @@ cocoon-js-vanilla@^1.3.0: integrity sha512-rMnbfW6oFhvELUg141vfqZKzsowfLJRxs5FksfmDr1ZBs6LTNVYE63NQyvgRqyYUOK54cKKbI+V83dQKeeRuPg== collect-v8-coverage@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" - integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== dependencies: map-visit "^1.0.0" object-visit "^1.0.0" -color-blend@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/color-blend/-/color-blend-3.0.1.tgz#3882ed1190ca18760ffe11570d8537960171172b" - integrity sha512-KueDvNiKHAvVeApic0SxHZLyy4x3NELfTLzMHRpRRLi+9e2kWhpeWvtuH3Sjb92mOJYEUhRjb8z7lr4OqDv17Q== +color-blend@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/color-blend/-/color-blend-4.0.0.tgz#e9950e9fa5d6e552ff8bb107c39f7e83a0c1a3bb" + integrity sha512-fYODTHhI/NG+B5GnzvuL3kiFrK/UnkUezWFTgEPBTY5V+kpyfAn95Vn9sJeeCX6omrCOdxnqCL3CvH+6sXtIbw== -color-convert@^1.9.0, color-convert@^1.9.1: +color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -3525,43 +4173,32 @@ color-convert@^2.0.1: color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312" - integrity sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -color@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" - integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== - dependencies: - color-convert "^1.9.1" - color-string "^1.5.2" - -colord@^2.9.3: +colord@^2.9.1, colord@^2.9.3: version "2.9.3" resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== -colorette@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" - integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== +colorette@^2.0.19: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +colors@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== combined-stream@^1.0.8: version "1.0.8" @@ -3570,6 +4207,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -3580,6 +4222,11 @@ commander@^7.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== +comment-parser@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.0.tgz#0f8c560f59698193854f12884c20c0e39a26d32c" + integrity sha512-QLyTNiZ2KDOibvFPlZ6ZngVsZ/0gYnE6uTXi5aoDg8ed3AkJAz4sEje3Y8a29hQ1s6A99MZXe47fLAXQ1rTqaw== + common-tags@^1.8.0: version "1.8.2" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" @@ -3588,7 +4235,7 @@ common-tags@^1.8.0: commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== component-emitter@^1.2.1: version "1.3.0" @@ -3602,17 +4249,6 @@ compressible@~2.0.16: dependencies: mime-db ">= 1.43.0 < 2" -compression-webpack-plugin@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-6.1.1.tgz#ae8e4b2ffdb7396bb776e66918d751a20d8ccf0e" - integrity sha512-BEHft9M6lwOqVIQFMS/YJGmeCYXVOakC5KzQk05TFpMBlODByh1qNsZCWjUBxCQhUP9x0WfGidxTbGkjbWO/TQ== - dependencies: - cacache "^15.0.5" - find-cache-dir "^3.3.1" - schema-utils "^3.0.0" - serialize-javascript "^5.0.1" - webpack-sources "^1.4.3" - compression@^1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" @@ -3629,17 +4265,7 @@ compression@^1.7.4: concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -concat-stream@^1.5.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== connect-history-api-fallback@^1.6.0: version "1.6.0" @@ -3654,12 +4280,12 @@ console-browserify@^1.1.0: console-control-strings@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= + integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ== content-disposition@0.5.4: version "0.5.4" @@ -3669,91 +4295,61 @@ content-disposition@0.5.4: safe-buffer "5.2.1" content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== -convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" +convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== cookie@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== -copy-concurrently@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" - integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== - dependencies: - aproba "^1.1.1" - fs-write-stream-atomic "^1.0.8" - iferr "^0.1.5" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.0" - copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== -core-js-compat@^3.25.1: - version "3.25.2" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.2.tgz#7875573586809909c69e03ef310810c1969ee138" - integrity sha512-TxfyECD4smdn3/CjWxczVtJqVLEEC2up7/82t7vC0AzNogr+4nQ8vyF7abxAuTXWvjTClSbvGhU0RgqA4ToQaQ== +core-js-compat@^3.31.0: + version "3.32.2" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.32.2.tgz#8047d1a8b3ac4e639f0d4f66d4431aa3b16e004c" + integrity sha512-+GjlguTDINOijtVRUxrQOv3kfu9rl+qPNdX2LTbJ/ZyVTuxK+ksVSAGX1nHstu4hrv1En/uPTtWgq2gI5wt4AQ== dependencies: - browserslist "^4.21.4" - -core-js-pure@^3.0.0: - version "3.6.5" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" - integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== + browserslist "^4.21.10" core-js@^2.5.0: version "2.6.12" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== +core-js@^3.30.2: + version "3.32.2" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.2.tgz#172fb5949ef468f93b4be7841af6ab1f21992db7" + integrity sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ== + core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cosmiconfig@^5.0.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" - integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== - dependencies: - import-fresh "^2.0.0" - is-directory "^0.3.1" - js-yaml "^3.13.1" - parse-json "^4.0.0" - -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== - 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" - -cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" - integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== dependencies: "@types/parse-json" "^4.0.0" import-fresh "^3.2.1" @@ -3761,6 +4357,16 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" +cosmiconfig@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.2.0.tgz#f7d17c56a590856cd1e7cee98734dca272b0d8fd" + integrity sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ== + dependencies: + import-fresh "^3.2.1" + js-yaml "^4.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -3810,7 +4416,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -3841,55 +4447,15 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -css-color-names@0.0.4, css-color-names@^0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" - integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= +css-declaration-sorter@^6.3.1: + version "6.4.0" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz#630618adc21724484b3e9505bce812def44000ad" + integrity sha512-jDfsatwWMWN0MODAFuHszfjphEXfNw9JUAhmY4pLu3TyTU+ohUpsbVtbU+1MZn4a47D9kqh03i4eyOm+74+zew== -css-declaration-sorter@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" - integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== - dependencies: - postcss "^7.0.1" - timsort "^0.3.0" - -css-font-size-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-font-size-keywords/-/css-font-size-keywords-1.0.0.tgz#854875ace9aca6a8d2ee0d345a44aae9bb6db6cb" - integrity sha1-hUh1rOmspqjS7g00WkSq6btttss= - -css-font-stretch-keywords@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-font-stretch-keywords/-/css-font-stretch-keywords-1.0.1.tgz#50cee9b9ba031fb5c952d4723139f1e107b54b10" - integrity sha1-UM7puboDH7XJUtRyMTnx4Qe1SxA= - -css-font-style-keywords@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-font-style-keywords/-/css-font-style-keywords-1.0.1.tgz#5c3532813f63b4a1de954d13cea86ab4333409e4" - integrity sha1-XDUygT9jtKHelU0TzqhqtDM0CeQ= - -css-font-weight-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-font-weight-keywords/-/css-font-weight-keywords-1.0.0.tgz#9bc04671ac85bc724b574ef5d3ac96b0d604fd97" - integrity sha1-m8BGcayFvHJLV07106yWsNYE/Zc= - -css-functions-list@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.1.0.tgz#cf5b09f835ad91a00e5959bcfc627cd498e1321b" - integrity sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w== - -css-global-keywords@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-global-keywords/-/css-global-keywords-1.0.1.tgz#72a9aea72796d019b1d2a3252de4e5aaa37e4a69" - integrity sha1-cqmupyeW0Bmx0qMlLeTlqqN+Smk= - -css-list-helpers@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-list-helpers/-/css-list-helpers-1.0.1.tgz#fff57192202db83240c41686f919e449a7024f7d" - integrity sha1-//VxkiAtuDJAxBaG+RnkSacCT30= - dependencies: - tcomb "^2.5.0" +css-functions-list@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.2.0.tgz#8290b7d064bf483f48d6559c10e98dc4d1ad19ee" + integrity sha512-d/jBMPyYybkkLVypgtGv12R+pIFw4/f/IHtCTxWpZc8ofTYOPigIgmA6vu5rMHartZC+WuXhBUHfnyNUIQSYrg== css-loader@^5.2.7: version "5.2.7" @@ -3922,10 +4488,16 @@ css-select@^2.0.0: domutils "^1.7.0" nth-check "^1.0.2" -css-system-font-keywords@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz#85c6f086aba4eb32c571a3086affc434b84823ed" - integrity sha1-hcbwhquk6zLFcaMIav/ENLhII+0= +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" @@ -3935,103 +4507,111 @@ css-tree@1.0.0-alpha.37: mdn-data "2.0.4" source-map "^0.6.1" -css-tree@1.0.0-alpha.39: - version "1.0.0-alpha.39" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" - integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== +css-tree@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== dependencies: - mdn-data "2.0.6" + mdn-data "2.0.14" source-map "^0.6.1" +css-tree@^2.2.1, css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" + css-what@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.3.0.tgz#10fec696a9ece2e591ac772d759aacabac38cd39" - integrity sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg== + version "3.4.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" + integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== css.escape@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" - integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-default@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz#920622b1fc1e95a34e8838203f1397a504f2d3ff" - integrity sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ== +cssnano-preset-default@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-6.0.1.tgz#2a93247140d214ddb9f46bc6a3562fa9177fe301" + integrity sha512-7VzyFZ5zEB1+l1nToKyrRkuaJIx0zi/1npjvZfbBwbtNTzhLtlvYraK/7/uqmX2Wb2aQtd983uuGw79jAjLSuQ== dependencies: - css-declaration-sorter "^4.0.1" - cssnano-util-raw-cache "^4.0.1" - postcss "^7.0.0" - postcss-calc "^7.0.1" - postcss-colormin "^4.0.3" - postcss-convert-values "^4.0.1" - postcss-discard-comments "^4.0.2" - postcss-discard-duplicates "^4.0.2" - postcss-discard-empty "^4.0.1" - postcss-discard-overridden "^4.0.1" - postcss-merge-longhand "^4.0.11" - postcss-merge-rules "^4.0.3" - postcss-minify-font-values "^4.0.2" - postcss-minify-gradients "^4.0.2" - postcss-minify-params "^4.0.2" - postcss-minify-selectors "^4.0.2" - postcss-normalize-charset "^4.0.1" - postcss-normalize-display-values "^4.0.2" - postcss-normalize-positions "^4.0.2" - postcss-normalize-repeat-style "^4.0.2" - postcss-normalize-string "^4.0.2" - postcss-normalize-timing-functions "^4.0.2" - postcss-normalize-unicode "^4.0.1" - postcss-normalize-url "^4.0.1" - postcss-normalize-whitespace "^4.0.2" - postcss-ordered-values "^4.1.2" - postcss-reduce-initial "^4.0.3" - postcss-reduce-transforms "^4.0.2" - postcss-svgo "^4.0.3" - postcss-unique-selectors "^4.0.1" + css-declaration-sorter "^6.3.1" + cssnano-utils "^4.0.0" + postcss-calc "^9.0.0" + postcss-colormin "^6.0.0" + postcss-convert-values "^6.0.0" + postcss-discard-comments "^6.0.0" + postcss-discard-duplicates "^6.0.0" + postcss-discard-empty "^6.0.0" + postcss-discard-overridden "^6.0.0" + postcss-merge-longhand "^6.0.0" + postcss-merge-rules "^6.0.1" + postcss-minify-font-values "^6.0.0" + postcss-minify-gradients "^6.0.0" + postcss-minify-params "^6.0.0" + postcss-minify-selectors "^6.0.0" + postcss-normalize-charset "^6.0.0" + postcss-normalize-display-values "^6.0.0" + postcss-normalize-positions "^6.0.0" + postcss-normalize-repeat-style "^6.0.0" + postcss-normalize-string "^6.0.0" + postcss-normalize-timing-functions "^6.0.0" + postcss-normalize-unicode "^6.0.0" + postcss-normalize-url "^6.0.0" + postcss-normalize-whitespace "^6.0.0" + postcss-ordered-values "^6.0.0" + postcss-reduce-initial "^6.0.0" + postcss-reduce-transforms "^6.0.0" + postcss-svgo "^6.0.0" + postcss-unique-selectors "^6.0.0" -cssnano-util-get-arguments@^4.0.0: +cssnano-utils@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" - integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-4.0.0.tgz#d1da885ec04003ab19505ff0e62e029708d36b08" + integrity sha512-Z39TLP+1E0KUcd7LGyF4qMfu8ZufI0rDzhdyAMsa/8UyNUU8wpS0fhdBxbQbv32r64ea00h4878gommRVg2BHw== -cssnano-util-get-match@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" - integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= - -cssnano-util-raw-cache@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" - integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== +cssnano@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-6.0.1.tgz#87c38c4cd47049c735ab756d7e77ac3ca855c008" + integrity sha512-fVO1JdJ0LSdIGJq68eIxOqFpIJrZqXUsBt8fkrBcztCQqAjQD51OhZp7tc0ImcbwXD4k7ny84QTV90nZhmqbkg== dependencies: - postcss "^7.0.0" - -cssnano-util-same-parent@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" - integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== - -cssnano@^4.1.11: - version "4.1.11" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.11.tgz#c7b5f5b81da269cb1fd982cb960c1200910c9a99" - integrity sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g== - dependencies: - cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.8" - is-resolvable "^1.0.0" - postcss "^7.0.0" + cssnano-preset-default "^6.0.1" + lilconfig "^2.1.0" csso@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" - integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== dependencies: - css-tree "1.0.0-alpha.39" + css-tree "^1.1.2" + +csso@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== + dependencies: + css-tree "~2.2.0" cssom@^0.5.0: version "0.5.0" @@ -4050,28 +4630,17 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csstype@^2.6.7: - version "2.6.13" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f" - integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A== +cssstyle@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-3.0.0.tgz#17ca9c87d26eac764bb8cfd00583cff21ce0277a" + integrity sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg== + dependencies: + rrweb-cssom "^0.6.0" csstype@^3.0.2: - version "3.0.6" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.6.tgz#865d0b5833d7d8d40f4e5b8a6d76aea3de4725ef" - integrity sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw== - -cyclist@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" - integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= - -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== damerau-levenshtein@^1.0.8: version "1.0.8" @@ -4087,21 +4656,30 @@ data-urls@^3.0.2: whatwg-mimetype "^3.0.0" whatwg-url "^11.0.0" -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: +data-urls@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-4.0.0.tgz#333a454eca6f9a5b7b0f1013ff89074c3f522dd4" + integrity sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^12.0.0" + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -debug@^3.1.1, debug@^3.2.6, debug@^3.2.7: +debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -4109,9 +4687,9 @@ debug@^3.1.1, debug@^3.2.6, debug@^3.2.7: ms "^2.1.1" decamelize-keys@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" - integrity sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg== + version "1.1.1" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" + integrity sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg== dependencies: decamelize "^1.1.0" map-obj "^1.0.0" @@ -4119,22 +4697,27 @@ decamelize-keys@^1.1.0: decamelize@^1.1.0, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -decimal.js@^10.4.1: - version "10.4.1" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.1.tgz#be75eeac4a2281aace80c1a8753587c27ef053e7" - integrity sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw== +decamelize@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-5.0.1.tgz#db11a92e58c741ef339fb0a2868d8a06a9a7b1e9" + integrity sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA== + +decimal.js@^10.4.2, decimal.js@^10.4.3: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== -dedent@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +dedent@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" + integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== deep-equal@^1.0.1: version "1.1.1" @@ -4148,15 +4731,57 @@ deep-equal@^1.0.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" -deep-is@^0.1.3, deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deep-equal@^2.0.5: + version "2.2.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.1.tgz#c72ab22f3a7d3503a4ca87dde976fe9978816739" + integrity sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + es-get-iterator "^1.1.3" + get-intrinsic "^1.2.0" + is-arguments "^1.1.1" + is-array-buffer "^3.0.2" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.0" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== deepmerge@^4.0, deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + +default-browser-id@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" + integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== + dependencies: + bplist-parser "^0.2.0" + untildify "^4.0.0" + +default-browser@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" + integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== + dependencies: + bundle-name "^3.0.0" + default-browser-id "^3.0.0" + execa "^7.1.1" + titleize "^3.0.0" default-gateway@^4.2.0: version "4.2.0" @@ -4166,10 +4791,15 @@ default-gateway@^4.2.0: execa "^1.0.0" ip-regex "^2.1.0" -define-properties@^1.1.3, define-properties@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" - integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== +define-lazy-prop@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" + integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== + +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" + integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== dependencies: has-property-descriptors "^1.0.0" object-keys "^1.1.1" @@ -4177,14 +4807,14 @@ define-properties@^1.1.3, define-properties@^1.1.4: define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== dependencies: is-descriptor "^0.1.0" define-property@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== dependencies: is-descriptor "^1.0.0" @@ -4212,12 +4842,17 @@ del@^4.1.1: delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== depd@2.0.0: version "2.0.0" @@ -4227,12 +4862,17 @@ depd@2.0.0: depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +dequal@^2.0.2, dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== des.js@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" - integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + version "1.1.0" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.1.0.tgz#1d37f5766f3bbff4ee9638e871a8768c173b81da" + integrity sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg== dependencies: inherits "^2.0.1" minimalistic-assert "^1.0.0" @@ -4245,7 +4885,7 @@ destroy@1.2.0: detect-file@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= + integrity sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q== detect-it@^4.0.1: version "4.0.1" @@ -4258,9 +4898,9 @@ detect-newline@^3.0.0: integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" - integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== detect-passive-events@^2.0.3: version "2.0.3" @@ -4269,15 +4909,10 @@ detect-passive-events@^2.0.3: dependencies: detect-it "^4.0.1" -diff-sequences@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" - integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== - -diff-sequences@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.2.0.tgz#4c55b5b40706c7b5d2c5c75999a50c56d214e8f6" - integrity sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== diffie-hellman@^5.0.0: version "5.0.3" @@ -4298,7 +4933,7 @@ dir-glob@^3.0.1: dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" - integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= + integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== dns-packet@^1.3.1: version "1.3.4" @@ -4311,7 +4946,7 @@ dns-packet@^1.3.1: dns-txt@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" - integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= + integrity sha512-Ix5PrWjphuSoUXV/Zv5gaFHjnaJtb02F2+Si3Ht9dyJ87+Z/lMmy+dpNHtTGraNK958ndXq2i+GLkWsWHcKaBQ== dependencies: buffer-indexof "^1.0.0" @@ -4329,25 +4964,25 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.6: - version "0.5.6" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.6.tgz#3f5d43b52c7a3bd68b5fb63fa47b4e4c1fdf65a9" - integrity sha512-DplGLZd8L1lN64jlT27N9TVSESFR5STaEJvX+thCby7fuCHonfPpAlodYc3vuUYbDuDec5w8AMP7oCM5TWFsqw== +dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== -dom-helpers@^3.2.1, dom-helpers@^3.4.0: +dom-helpers@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== dependencies: "@babel/runtime" "^7.1.2" -dom-helpers@^5.0.1: - version "5.1.3" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.3.tgz#7233248eb3a2d1f74aafca31e52c5299cc8ce821" - integrity sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw== +dom-helpers@^5.0.1, dom-helpers@^5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== dependencies: - "@babel/runtime" "^7.6.3" - csstype "^2.6.7" + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" dom-serializer@0: version "0.2.2" @@ -4357,6 +4992,15 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" @@ -4367,10 +5011,10 @@ domelementtype@1: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== -domelementtype@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== +domelementtype@^2.0.1, domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== domexception@^4.0.0: version "4.0.0" @@ -4379,6 +5023,13 @@ domexception@^4.0.0: dependencies: webidl-conversions "^7.0.0" +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" @@ -4387,54 +5038,51 @@ domutils@^1.7.0: dom-serializer "0" domelementtype "1" -dot-prop@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== dependencies: - is-obj "^2.0.0" + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" dotenv@^16.0.3: - version "16.0.3" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" - integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== + version "16.3.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" + integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== -duplexify@^3.4.2, duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== ejs@^3.1.6: - version "3.1.8" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" - integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== + version "3.1.9" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361" + integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ== dependencies: jake "^10.8.5" -electron-to-chromium@^1.3.723: - version "1.3.736" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.736.tgz#f632d900a1f788dab22fec9c62ec5c9c8f0c4052" - integrity sha512-DY8dA7gR51MSo66DqitEQoUMQ0Z+A2DSXFi7tK304bdTVqczCAfUuyQw6Wdg8hIoo5zIxkU1L24RQtUce1Ioig== +electron-to-chromium@^1.4.428: + version "1.4.457" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.457.tgz#3fdc7b4f97d628ac6b51e8b4b385befb362fe343" + integrity sha512-/g3UyNDmDd6ebeWapmAoiyy+Sy2HyJ+/X8KyvNeHfKRFfHaA2W8oF5fxD5F3tjBDcjpwo0iek6YNgxNXDBoEtA== -electron-to-chromium@^1.4.251: - version "1.4.254" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.254.tgz#c6203583890abf88dfc0be046cd72d3b48f8beb6" - integrity sha512-Sh/7YsHqQYkA6ZHuHMy24e6TE4eX6KZVsZb9E/DvU1nQRIrH4BflO/4k+83tfdYvDl+MObvlqHPRICzEdC9c6Q== +electron-to-chromium@^1.4.477: + version "1.4.526" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.526.tgz#1bcda5f2b8238e497c20fcdb41af5da907a770e2" + integrity sha512-tjjTMjmZAx1g6COrintLTa2/jcafYKxKoiEkdQOrVdbLaHh2wCt2nsAF8ZHweezkrP+dl/VG9T5nabcYoo0U5Q== elliptic@^6.5.3: version "6.5.4" @@ -4463,6 +5111,11 @@ emittery@^0.13.1: intersection-observer "^0.12.0" prop-types "^15.6.0" +emoji-regex@10.2.1, emoji-regex@^10.2.1: + version "10.2.1" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.2.1.tgz#a41c330d957191efd3d9dfe6e1e8e1e9ab048b3f" + integrity sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA== + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -4486,9 +5139,9 @@ emojis-list@^3.0.0: encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -end-of-stream@^1.0.0, end-of-stream@^1.1.0: +end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -4504,27 +5157,28 @@ enhanced-resolve@^4.1.1, enhanced-resolve@^4.5.0: memory-fs "^0.5.0" tapable "^1.0.0" -enquirer@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== +enhanced-resolve@^5.12.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== dependencies: - ansi-colors "^4.1.1" + graceful-fs "^4.2.4" + tapable "^2.2.0" entities@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" - integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" - integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA== +entities@^4.2.0, entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== -errno@^0.1.3, errno@~0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== +errno@^0.1.3: + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== dependencies: prr "~1.0.1" @@ -4536,69 +5190,105 @@ error-ex@^1.3.1: is-arrayish "^0.2.1" error-stack-parser@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8" - integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ== + version "2.1.4" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" + integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== dependencies: - stackframe "^1.1.1" + stackframe "^1.3.4" -es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1, es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.5: - version "1.20.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.0.tgz#b2d526489cceca004588296334726329e0a6bfb6" - integrity sha512-URbD8tgRthKD3YcC39vbvSDrX23upXnPcnGAjQfgxXF5ID75YcENawc9ZX/9iTP9ptUyfCLIxTTuMYoRfiOVKA== +es-abstract@^1.17.2, es-abstract@^1.19.0, es-abstract@^1.20.4, es-abstract@^1.21.2, es-abstract@^1.21.3: + version "1.22.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" + integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw== dependencies: + array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.1" + available-typed-arrays "^1.0.5" call-bind "^1.0.2" + es-set-tostringtag "^2.0.1" es-to-primitive "^1.2.1" - function-bind "^1.1.1" function.prototype.name "^1.1.5" - get-intrinsic "^1.1.1" + get-intrinsic "^1.2.1" get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" has "^1.0.3" has-property-descriptors "^1.0.0" + has-proto "^1.0.1" has-symbols "^1.0.3" - internal-slot "^1.0.3" - is-callable "^1.2.4" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" + is-callable "^1.2.7" is-negative-zero "^2.0.2" is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" is-string "^1.0.7" + is-typed-array "^1.1.10" is-weakref "^1.0.2" - object-inspect "^1.12.0" + object-inspect "^1.12.3" object-keys "^1.1.1" - object.assign "^4.1.2" - regexp.prototype.flags "^1.4.1" - string.prototype.trimend "^1.0.5" - string.prototype.trimstart "^1.0.5" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.0" + safe-array-concat "^1.0.0" + safe-regex-test "^1.0.0" + string.prototype.trim "^1.2.7" + string.prototype.trimend "^1.0.6" + string.prototype.trimstart "^1.0.6" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" + typed-array-length "^1.0.4" unbox-primitive "^1.0.2" + which-typed-array "^1.1.10" -es-abstract@^1.19.2: - version "1.20.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814" - integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== +es-array-method-boxes-properly@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" + integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== + +es-get-iterator@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== dependencies: call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.1.1" - get-symbol-description "^1.0.0" - has "^1.0.3" - has-property-descriptors "^1.0.0" + get-intrinsic "^1.1.3" has-symbols "^1.0.3" - internal-slot "^1.0.3" - is-callable "^1.2.4" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" is-string "^1.0.7" - is-weakref "^1.0.2" - object-inspect "^1.12.0" - object-keys "^1.1.1" - object.assign "^4.1.2" - regexp.prototype.flags "^1.4.3" - string.prototype.trimend "^1.0.5" - string.prototype.trimstart "^1.0.5" - unbox-primitive "^1.0.2" + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" + +es-iterator-helpers@^1.0.12: + version "1.0.13" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.13.tgz#72101046ffc19baf9996adc70e6177a26e6e8084" + integrity sha512-LK3VGwzvaPWobO8xzXXGRUOGw8Dcjyfk62CsY/wfHN75CwsJPbuypOYJxK6g5RyEL8YDjIWcl6jgd8foO6mmrA== + dependencies: + asynciterator.prototype "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.21.3" + es-set-tostringtag "^2.0.1" + function-bind "^1.1.1" + get-intrinsic "^1.2.1" + globalthis "^1.0.3" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.5" + iterator.prototype "^1.1.0" + safe-array-concat "^1.0.0" + +es-set-tostringtag@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" + integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== + dependencies: + get-intrinsic "^1.1.3" + has "^1.0.3" + has-tostringtag "^1.0.0" es-shim-unscopables@^1.0.0: version "1.0.0" @@ -4616,32 +5306,6 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.50: - version "0.10.53" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" - integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== - dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.3" - next-tick "~1.0.0" - -es6-iterator@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-symbol@^3.1.1, es6-symbol@^3.1.3, es6-symbol@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" - escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -4650,12 +5314,12 @@ escalade@^3.1.1: escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== escape-string-regexp@^2.0.0: version "2.0.0" @@ -4668,103 +5332,166 @@ escape-string-regexp@^4.0.0: integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== escodegen@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" - integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + version "2.1.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" + integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== dependencies: esprima "^4.0.1" estraverse "^5.2.0" esutils "^2.0.2" - optionator "^0.8.1" optionalDependencies: source-map "~0.6.1" -eslint-import-resolver-node@^0.3.6: - version "0.3.6" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" - integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== +eslint-config-prettier@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f" + integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw== + +eslint-import-resolver-node@^0.3.7: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== dependencies: debug "^3.2.7" - resolve "^1.20.0" + is-core-module "^2.13.0" + resolve "^1.22.4" -eslint-module-utils@^2.7.3: - version "2.7.3" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" - integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ== +eslint-import-resolver-typescript@^3.5.5: + version "3.6.0" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.0.tgz#36f93e1eb65a635e688e16cae4bead54552e3bbd" + integrity sha512-QTHR9ddNnn35RTxlaEnx2gCxqFlF2SEN0SE2d17SqwyM7YOSI2GHWRYp5BiRkObTUNYPupC/3Fq2a0PpT+EKpg== + dependencies: + debug "^4.3.4" + enhanced-resolve "^5.12.0" + eslint-module-utils "^2.7.4" + fast-glob "^3.3.1" + get-tsconfig "^4.5.0" + is-core-module "^2.11.0" + is-glob "^4.0.3" + +eslint-module-utils@^2.7.4, eslint-module-utils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" + integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== dependencies: debug "^3.2.7" - find-up "^2.1.0" -eslint-plugin-import@~2.26.0: - version "2.26.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" - integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== +eslint-plugin-formatjs@^4.10.1: + version "4.10.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-formatjs/-/eslint-plugin-formatjs-4.10.5.tgz#3b38ce5da1f8e9d135298df777cf87115e4d0818" + integrity sha512-pBPA4idiYHXPQMrIb9/Le+D0snlNa7MFQsw12yIzyva/z9uz0u/4NOK3NkfyENMBNMeTX2tZXtugk9FyqM5SRw== dependencies: - array-includes "^3.1.4" - array.prototype.flat "^1.2.5" - debug "^2.6.9" + "@formatjs/icu-messageformat-parser" "2.6.2" + "@formatjs/ts-transformer" "3.13.5" + "@types/eslint" "7 || 8" + "@types/picomatch" "^2.3.0" + "@typescript-eslint/typescript-estree" "5.62.0" + emoji-regex "^10.2.1" + magic-string "^0.30.0" + picomatch "^2.3.1" + tslib "2.6.0" + typescript "^4.7 || 5" + unicode-emoji-utils "^1.1.1" + +eslint-plugin-import@~2.28.0: + version "2.28.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz#63b8b5b3c409bfc75ebaf8fb206b07ab435482c4" + integrity sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A== + dependencies: + array-includes "^3.1.6" + array.prototype.findlastindex "^1.2.2" + array.prototype.flat "^1.3.1" + array.prototype.flatmap "^1.3.1" + debug "^3.2.7" doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.3" + eslint-import-resolver-node "^0.3.7" + eslint-module-utils "^2.8.0" has "^1.0.3" - is-core-module "^2.8.1" + is-core-module "^2.13.0" is-glob "^4.0.3" minimatch "^3.1.2" - object.values "^1.1.5" - resolve "^1.22.0" - tsconfig-paths "^3.14.1" + object.fromentries "^2.0.6" + object.groupby "^1.0.0" + object.values "^1.1.6" + semver "^6.3.1" + tsconfig-paths "^3.14.2" -eslint-plugin-jsx-a11y@~6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz#93736fc91b83fdc38cc8d115deedfc3091aef1ff" - integrity sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q== +eslint-plugin-jsdoc@^46.1.0: + version "46.8.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.8.1.tgz#cfc649c15d460903fe8e86eda582023bb97f283a" + integrity sha512-uTce7IBluPKXIQMWJkIwFsI1gv7sZRmLjctca2K5DIxPi8fSBj9f4iru42XmGwuiMyH2f3nfc60sFmnSGv4Z/A== dependencies: - "@babel/runtime" "^7.18.9" - aria-query "^4.2.2" - array-includes "^3.1.5" + "@es-joy/jsdoccomment" "~0.40.1" + are-docs-informative "^0.0.2" + comment-parser "1.4.0" + debug "^4.3.4" + escape-string-regexp "^4.0.0" + esquery "^1.5.0" + is-builtin-module "^3.2.1" + semver "^7.5.4" + spdx-expression-parse "^3.0.1" + +eslint-plugin-jsx-a11y@~6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz#fca5e02d115f48c9a597a6894d5bcec2f7a76976" + integrity sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA== + 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.4.3" - axobject-query "^2.2.0" + 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.2" - language-tags "^1.0.5" + 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" +eslint-plugin-prettier@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz#6887780ed95f7708340ec79acfdf60c35b9be57a" + integrity sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.8.5" + eslint-plugin-promise@~6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz#269a3e2772f62875661220631bd4dafcb4083816" integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig== -eslint-plugin-react@~7.31.10: - version "7.31.10" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz#6782c2c7fe91c09e715d536067644bbb9491419a" - integrity sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA== +eslint-plugin-react-hooks@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" + integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== + +eslint-plugin-react@~7.33.0: + version "7.33.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz#69ee09443ffc583927eafe86ffebb470ee737608" + integrity sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw== dependencies: - array-includes "^3.1.5" - array.prototype.flatmap "^1.3.0" + array-includes "^3.1.6" + array.prototype.flatmap "^1.3.1" + array.prototype.tosorted "^1.1.1" doctrine "^2.1.0" + es-iterator-helpers "^1.0.12" estraverse "^5.3.0" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" - object.entries "^1.1.5" - object.fromentries "^2.0.5" - object.hasown "^1.1.1" - object.values "^1.1.5" + object.entries "^1.1.6" + object.fromentries "^2.0.6" + object.hasown "^1.1.2" + object.values "^1.1.6" prop-types "^15.8.1" - resolve "^2.0.0-next.3" - semver "^6.3.0" - string.prototype.matchall "^4.0.7" - -eslint-scope@5.1.1, eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" + resolve "^2.0.0-next.4" + semver "^6.3.1" + string.prototype.matchall "^4.0.8" eslint-scope@^4.0.3: version "4.0.3" @@ -4774,87 +5501,80 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: - eslint-visitor-keys "^1.1.0" + esrecurse "^4.3.0" + estraverse "^5.2.0" -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint@^7.32.0: - version "7.32.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" - integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== +eslint@^8.41.0: + version "8.49.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.49.0.tgz#09d80a89bdb4edee2efcf6964623af1054bf6d42" + integrity sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ== dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.3" - "@humanwhocodes/config-array" "^0.5.0" - ajv "^6.10.0" + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.2" + "@eslint/js" "8.49.0" + "@humanwhocodes/config-array" "^0.11.11" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" - debug "^4.0.1" + debug "^4.3.2" doctrine "^3.0.0" - enquirer "^2.3.5" escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" - esquery "^1.4.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" - globals "^13.6.0" - ignore "^4.0.6" - import-fresh "^3.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" imurmurhash "^0.1.4" is-glob "^4.0.0" - js-yaml "^3.13.1" + 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.0.4" + minimatch "^3.1.2" natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^6.0.9" + optionator "^0.9.3" + strip-ansi "^6.0.1" text-table "^0.2.0" - v8-compile-cache "^2.0.3" -espree@^7.3.0, espree@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" - integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: - acorn "^7.4.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^1.3.0" + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== +esquery@^1.4.2, esquery@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== dependencies: estraverse "^5.1.0" @@ -4888,7 +5608,7 @@ esutils@^2.0.2: etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== event-target-shim@^5.0.0: version "5.0.1" @@ -4900,22 +5620,15 @@ eventemitter3@^4.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" - integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== - -events@^3.3.0: +events@^3.0.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -eventsource@^1.0.7: - version "1.1.1" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.1.tgz#4544a35a57d7120fba4fa4c86cb4023b2c09df2f" - integrity sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA== - dependencies: - original "^1.0.0" +eventsource@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-2.0.2.tgz#76dfcc02930fb2ff339520b6d290da573a9e8508" + integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA== evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" @@ -4953,20 +5666,45 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -exif-js@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/exif-js/-/exif-js-2.3.0.tgz#9d10819bf571f873813e7640241255ab9ce1a814" - integrity sha1-nRCBm/Vx+HOBPnZAJBJVq5zhqBQ= +execa@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" + integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^4.3.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" + +execa@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-7.1.1.tgz#3eb3c83d239488e7b409d48e8813b76bb55c9c43" + integrity sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^4.3.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== dependencies: debug "^2.3.3" define-property "^0.2.5" @@ -4979,20 +5717,32 @@ expand-brackets@^2.1.4: expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= + integrity sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw== dependencies: homedir-polyfill "^1.0.1" -expect@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.2.2.tgz#ba2dd0d7e818727710324a6e7f13dd0e6d086106" - integrity sha512-hE09QerxZ5wXiOhqkXy5d2G9ar+EqOyifnCXCpMNu+vZ6DG9TJ6CO2c2kPDSLqERTTWrO7OZj8EkYHQqSd78Yw== +expect@^29.0.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== dependencies: - "@jest/expect-utils" "^29.2.2" - jest-get-type "^29.2.0" - jest-matcher-utils "^29.2.2" - jest-message-util "^29.2.1" - jest-util "^29.2.1" + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +expect@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.2.tgz#7b08e83eba18ddc4a2cf62b5f2d1918f5cd84521" + integrity sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA== + dependencies: + "@jest/expect-utils" "^29.6.2" + "@types/node" "*" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.6.2" + jest-message-util "^29.6.2" + jest-util "^29.6.2" express@^4.17.1, express@^4.18.2: version "4.18.2" @@ -5031,24 +5781,17 @@ express@^4.17.1, express@^4.18.2: utils-merge "1.0.1" vary "~1.1.2" -ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== - dependencies: - type "^2.0.0" - extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== dependencies: is-extendable "^0.1.0" extend-shallow@^3.0.0, extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== dependencies: assign-symbols "^1.0.0" is-extendable "^1.0.1" @@ -5072,7 +5815,12 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.12, fast-glob@^3.2.9: +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@^3.2.12: version "3.2.12" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== @@ -5083,15 +5831,26 @@ fast-glob@^3.2.12, fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" + integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastest-levenshtein@^1.0.16: version "1.0.16" @@ -5099,31 +5858,26 @@ fastest-levenshtein@^1.0.16: integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== fastq@^1.6.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== dependencies: reusify "^1.0.4" -faye-websocket@^0.11.3: - version "0.11.3" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" - integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== +faye-websocket@^0.11.3, faye-websocket@^0.11.4: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== dependencies: websocket-driver ">=0.5.1" fb-watchman@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" - integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== dependencies: bser "2.1.1" -figgy-pudding@^3.5.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" - integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -5149,7 +5903,7 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== -filelist@^1.0.1: +filelist@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== @@ -5159,7 +5913,7 @@ filelist@^1.0.1: fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== dependencies: extend-shallow "^2.0.1" is-number "^3.0.0" @@ -5186,19 +5940,10 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -find-cache-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" - find-cache-dir@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" - integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== dependencies: commondir "^1.0.1" make-dir "^3.0.2" @@ -5209,13 +5954,6 @@ find-root@^1.1.0: resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== -find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= - dependencies: - locate-path "^2.0.0" - find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -5231,6 +5969,14 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + findup-sync@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" @@ -5242,25 +5988,18 @@ findup-sync@^3.0.0: resolve-dir "^1.0.1" flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + version "3.1.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.1.0.tgz#0e54ab4a1a60fe87e2946b6b00657f1c99e1af3f" + integrity sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew== dependencies: - flatted "^3.1.0" + flatted "^3.2.7" + keyv "^4.5.3" rimraf "^3.0.2" -flatted@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.0.tgz#a5d06b4a8b01e3a63771daa5cb7a1903e2e57067" - integrity sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA== - -flush-write-stream@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" - integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== - dependencies: - inherits "^2.0.3" - readable-stream "^2.3.6" +flatted@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== follow-redirects@^1.0.0, follow-redirects@^1.15.0: version "1.15.2" @@ -5270,12 +6009,27 @@ follow-redirects@^1.0.0, follow-redirects@^1.15.0: font-awesome@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133" - integrity sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM= + integrity sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== + +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" form-data@^4.0.0: version "4.0.0" @@ -5291,34 +6045,22 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== +fraction.js@^4.3.6: + version "4.3.6" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.6.tgz#e9e3acec6c9a28cf7bc36cbe35eea4ceb2c5c92d" + integrity sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg== + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== dependencies: map-cache "^0.2.2" fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - -from2@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== fs-extra@^9.0.1: version "9.1.0" @@ -5337,20 +6079,10 @@ fs-minipass@^2.0.0: dependencies: minipass "^3.0.0" -fs-write-stream-atomic@^1.0.8: - version "1.0.10" - resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" - integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= - dependencies: - graceful-fs "^4.1.2" - iferr "^0.1.5" - imurmurhash "^0.1.4" - readable-stream "1 || 2" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@^1.2.7: version "1.2.13" @@ -5360,11 +6092,16 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@^2.3.2, fsevents@~2.3.1, fsevents@~2.3.2: +fsevents@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -5380,40 +6117,30 @@ function.prototype.name@^1.1.5: es-abstract "^1.19.0" functions-have-names "^1.2.2" -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= - -functions-have-names@^1.2.2: +functions-have-names@^1.2.2, functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== -fuzzysort@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/fuzzysort/-/fuzzysort-1.9.0.tgz#d36d27949eae22340bb6f7ba30ea6751b92a181c" - integrity sha512-MOxCT0qLTwLqmEwc7UtU045RKef7mc8Qz8eR4r2bLNEq9dy/c3ZKMEFp6IEst69otkQdFZ4FfgH2dmZD+ddX1g== +fuzzysort@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/fuzzysort/-/fuzzysort-2.0.4.tgz#a21d1ce8947eaf2797dc3b7c28c36db9d1165f84" + integrity sha512-Api1mJL+Ad7W7vnDZnWq5pGaXJjyencT+iKGia2PlHUcSsSzWwIQ3S1isiMpwpavjYtGd2FzhUIhnnhOULZgDw== gauge@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-5.0.0.tgz#e270ca9d97dae84abf64e5277ef1ebddc7dd1e2f" - integrity sha512-0s5T5eciEG7Q3ugkxAkFtaDhrrhXsCRivA5y8C9WMHWuI8UlMOJg7+Iwf7Mccii+Dfs3H5jHepU0joPVyQU0Lw== + version "5.0.1" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-5.0.1.tgz#1efc801b8ff076b86ef3e9a7a280a975df572112" + integrity sha512-CmykPMJGuNan/3S4kZOpvvPYSNqSHANiWnh9XcMU2pSjtBfF0XzZ2p1bFAxTbnFxyBuPxQYHhzwaoOmUdqzvxQ== dependencies: aproba "^1.0.3 || ^2.0.0" color-support "^1.1.3" console-control-strings "^1.1.0" has-unicode "^2.0.1" - signal-exit "^3.0.7" + signal-exit "^4.0.1" string-width "^4.2.3" strip-ansi "^6.0.1" wide-align "^1.1.5" -generic-pool@3.8.2: - version "3.8.2" - resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.8.2.tgz#aab4f280adb522fdfbdc5e5b64d718d3683f04e9" - integrity sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg== - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -5424,14 +6151,15 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== dependencies: function-bind "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" + has-proto "^1.0.1" + has-symbols "^1.0.3" get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" @@ -5450,7 +6178,7 @@ get-stream@^4.0.0: dependencies: pump "^3.0.0" -get-stream@^6.0.0: +get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -5463,49 +6191,63 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" +get-tsconfig@^4.5.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.0.tgz#06ce112a1463e93196aa90320c35df5039147e34" + integrity sha512-pmjiZ7xtB8URYm74PlGJozDNyhvsVLUcpBa8DZBG3bWHwaHa9bPiRpiSfovw+fjhwONSCWKRyk+JQHEGZmMrzw== + dependencies: + resolve-pkg-maps "^1.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + integrity sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA== dependencies: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.1.2, glob-parent@~5.1.0: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" -glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^10.2.5, glob@^10.2.6: + version "10.3.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.5.tgz#4c0e46b5bccd78ac42b06a7eaaeb9ee34062968e" + integrity sha512-bYUpUD7XDEHI4Q2O5a7PXGvyw4deKR70kHiDxzQbe925wbZknhOzUt2xBgTkYL6RBcVeXYuD9iNYeqoWbBZQnA== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.0.3" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" + +glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" - integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -5525,7 +6267,7 @@ global-modules@^2.0.0: global-prefix@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= + integrity sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg== dependencies: expand-tilde "^2.0.2" homedir-polyfill "^1.0.1" @@ -5547,13 +6289,20 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.6.0, globals@^13.9.0: - version "13.9.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb" - integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA== +globals@^13.19.0: + version "13.21.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.21.0.tgz#163aae12f34ef502f5153cfbdd3600f36c63c571" + integrity sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg== dependencies: type-fest "^0.20.2" +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -5569,7 +6318,7 @@ globby@^11.1.0: globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= + integrity sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw== dependencies: array-union "^1.0.1" glob "^7.0.3" @@ -5582,10 +6331,22 @@ globjoin@^0.1.4: resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg== -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: - version "4.2.9" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" - integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== gzip-size@^6.0.0: version "6.0.0" @@ -5604,27 +6365,15 @@ hard-rejection@^2.1.0: resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - -has-bigints@^1.0.2: +has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== -has-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" - integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" @@ -5638,17 +6387,12 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.1.1" -has-symbols@^1.0.1: +has-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== -has-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== - -has-symbols@^1.0.3: +has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== @@ -5663,12 +6407,12 @@ has-tostringtag@^1.0.0: has-unicode@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== dependencies: get-value "^2.0.3" has-values "^0.1.4" @@ -5677,7 +6421,7 @@ has-value@^0.3.1: has-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== dependencies: get-value "^2.0.6" has-values "^1.0.0" @@ -5686,17 +6430,17 @@ has-value@^1.0.0: has-values@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== has-values@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== dependencies: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.0, has@^1.0.3: +has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== @@ -5720,12 +6464,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hex-color-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" - integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== - -history@^4.10.1: +history@^4.10.1, history@^4.7.2: version "4.10.1" resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== @@ -5737,21 +6476,10 @@ history@^4.10.1: tiny-warning "^1.0.0" value-equal "^1.0.1" -history@^4.7.2: - version "4.7.2" - resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" - integrity sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA== - dependencies: - invariant "^2.2.1" - loose-envify "^1.2.0" - resolve-pathname "^2.2.0" - value-equal "^0.4.0" - warning "^3.0.0" - hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== dependencies: hash.js "^1.0.3" minimalistic-assert "^1.0.0" @@ -5776,11 +6504,6 @@ homedir-polyfill@^1.0.1: dependencies: parse-passwd "^1.0.0" -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - hosted-git-info@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" @@ -5791,23 +6514,13 @@ hosted-git-info@^4.0.1: hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= + integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== dependencies: inherits "^2.0.1" obuf "^1.0.0" readable-stream "^2.0.1" wbuf "^1.1.0" -hsl-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" - integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= - -hsla-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" - integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= - html-encoding-sniffer@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" @@ -5816,24 +6529,24 @@ html-encoding-sniffer@^3.0.0: whatwg-encoding "^2.0.0" html-entities@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" - integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== + version "1.4.0" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc" + integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA== html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== -html-tags@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961" - integrity sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg== +html-tags@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" + integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= + integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== http-errors@2.0.0: version "2.0.0" @@ -5849,27 +6562,22 @@ http-errors@2.0.0: http-errors@~1.6.2: version "1.6.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== dependencies: depd "~1.1.2" inherits "2.0.3" setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -http-link-header@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-1.0.5.tgz#8e6d9ed1d393e8d5e01aa5c48bd97aa38d7e261c" - integrity sha512-msKrMbv/xHzhdOD4sstbEr+NbGqpv8ZtZliiCeByGENJo1jK1GZ/81zHF9HpWtEH5ihovPpdqHXniwZapJCKEA== - -"http-parser-js@>=0.4.0 <0.4.11": - version "0.4.10" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" - integrity sha1-ksnBN0w1CF912zWexWzCV8u5P6Q= +http-link-header@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-1.1.1.tgz#f0e6971b0ed86e858d2077066ecb7ba4f2e50de9" + integrity sha512-mW3N/rTYpCn99s1do0zx6nzFZSwLH9HGfUM4ZqLWJ16ylmYaC2v5eYGqrNTQlByx8AzUgGI+V/32gXPugs1+Sw== http-parser-js@>=0.5.1: - version "0.5.3" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" - integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== + version "0.5.8" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== http-proxy-agent@^5.0.0: version "5.0.0" @@ -5902,7 +6610,7 @@ http-proxy@^1.17.0: https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== https-proxy-agent@^5.0.1: version "5.0.1" @@ -5917,6 +6625,16 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +human-signals@^4.3.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" + integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== + +husky@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" + integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -5942,64 +6660,36 @@ idb-keyval@^3.2.0: integrity sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ== idb@^7.0.1: - version "7.0.2" - resolved "https://registry.yarnpkg.com/idb/-/idb-7.0.2.tgz#7a067e20dd16539938e456814b7d714ba8db3892" - integrity sha512-jjKrT1EnyZewQ/gCBb/eyiYrhGzws2FeY92Yx8qT9S9GeQAmo4JFVIiWRIfKW/6Ob9A+UDAOW9j9jn58fy2HIg== + version "7.1.1" + resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" + integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -ieee754@^1.2.1: +ieee754@^1.1.4, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -iferr@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" - integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= +ignore@^5.2.0, ignore@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +immer@^9.0.21: + version "9.0.21" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" + integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== -ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +immutable@^3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" + integrity sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg== -immutable@^4.0.0, immutable@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef" - integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ== +immutable@^4.0.0, immutable@^4.0.0-rc.1, immutable@^4.3.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f" + integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA== -import-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" - integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= - dependencies: - import-from "^2.1.0" - -import-fresh@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" - integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= - dependencies: - caller-path "^2.0.0" - resolve-from "^3.0.0" - -import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" - integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-fresh@^3.1.0: +import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -6007,13 +6697,6 @@ import-fresh@^3.1.0: parent-module "^1.0.0" resolve-from "^4.0.0" -import-from@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" - integrity sha1-M1238qev/VOqpHHUuAId7ja387E= - dependencies: - resolve-from "^3.0.0" - import-lazy@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" @@ -6028,9 +6711,9 @@ import-local@^2.0.0: resolve-cwd "^2.0.0" import-local@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" - integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== dependencies: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" @@ -6048,19 +6731,19 @@ imports-loader@^1.2.0: imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== indent-string@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= +indent-string@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5" + integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== -infer-owner@^1.0.3, infer-owner@^1.0.4: +infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== @@ -6068,7 +6751,7 @@ infer-owner@^1.0.3, infer-owner@^1.0.4: inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" @@ -6081,17 +6764,17 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, i inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= + integrity sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA== inherits@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== ini@^1.3.4, ini@^1.3.5: - version "1.3.7" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" - integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== internal-ip@^4.3.0: version "4.3.0" @@ -6101,12 +6784,12 @@ internal-ip@^4.3.0: default-gateway "^4.2.0" ipaddr.js "^1.9.0" -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== +internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" + integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== dependencies: - get-intrinsic "^1.1.0" + get-intrinsic "^1.2.0" has "^1.0.3" side-channel "^1.0.4" @@ -6115,79 +6798,58 @@ interpret@^1.4.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== -intersection-observer@^0.12.0, intersection-observer@^0.12.2: +intersection-observer@^0.12.0: version "0.12.2" resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.12.2.tgz#4a45349cc0cd91916682b1f44c28d7ec737dc375" integrity sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg== -intl-format-cache@^2.0.5: - version "2.2.9" - resolved "https://registry.yarnpkg.com/intl-format-cache/-/intl-format-cache-2.2.9.tgz#fb560de20c549cda20b569cf1ffb6dc62b5b93b4" - integrity sha512-Zv/u8wRpekckv0cLkwpVdABYST4hZNTDaX7reFetrYTJwxExR2VyTqQm+l0WmL0Qo8Mjb9Tf33qnfj0T7pjxdQ== - -intl-messageformat-parser@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-1.4.0.tgz#b43d45a97468cadbe44331d74bb1e8dea44fc075" - integrity sha1-tD1FqXRoytvkQzHXS7Ho3qRPwHU= - -intl-messageformat-parser@^4.1.1: - version "4.1.4" - resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-4.1.4.tgz#98f3415e6990d44bebf2e0ad8e4cfbacf3ef5ed3" - integrity sha512-zV4kBUD1yhxSyaXm6bGhmP4HFH9Gh4pRQwNn+xq5P+B1dT8mpaAfU75nfUn4HgddIB6pyFnzM5MQjO55UpJwkQ== +intl-messageformat@10.5.3, intl-messageformat@^10.3.5: + version "10.5.3" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.3.tgz#db0779d4a1988faa2977d76574489b7a25f0d5d0" + integrity sha512-TzKn1uhJBMyuKTO4zUX47SU+d66fu1W9tVzIiZrQ6hBqQQeYscBMIzKL/qEXnFbJrH9uU5VV3+T5fWib4SIcKA== dependencies: - "@formatjs/intl-unified-numberformat" "^3.3.3" + "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/fast-memoize" "2.2.0" + "@formatjs/icu-messageformat-parser" "2.6.2" + tslib "^2.4.0" -intl-messageformat@^2.0.0, intl-messageformat@^2.1.0, intl-messageformat@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-2.2.0.tgz#345bcd46de630b7683330c2e52177ff5eab484fc" - integrity sha1-NFvNRt5jC3aDMwwuUhd/9eq0hPw= - dependencies: - intl-messageformat-parser "1.4.0" - -intl-relativeformat@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-2.2.0.tgz#6aca95d019ec8d30b6c5653b6629f9983ea5b6c5" - integrity sha512-4bV/7kSKaPEmu6ArxXf9xjv1ny74Zkwuey8Pm01NH4zggPP7JHwg2STk8Y3JdspCKRDriwIyLRfEXnj2ZLr4Bw== - dependencies: - intl-messageformat "^2.0.0" - -intl-relativeformat@^6.4.3: - version "6.4.3" - resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-6.4.3.tgz#cb5559e1e257cc2e763583502012a354bb777efe" - integrity sha512-VxZXZfhuX/zBVfxzE/J6kPUpsyWKYjqtZ3jVGZwr6wzK5BOLVpe1vSlwCQX56w5UjlpL63fS8Nxq0kgTyf1gJA== - -intl@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/intl/-/intl-1.2.5.tgz#82244a2190c4e419f8371f5aa34daa3420e2abde" - integrity sha1-giRKIZDE5Bn4Nx9ao02qNCDiq94= - -invariant@^2.1.1, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: +invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== dependencies: loose-envify "^1.0.0" +ioredis@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.3.2.tgz#9139f596f62fc9c72d873353ac5395bcf05709f7" + integrity sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA== + dependencies: + "@ioredis/commands" "^1.1.1" + cluster-key-slot "^1.1.0" + debug "^4.3.4" + denque "^2.1.0" + lodash.defaults "^4.2.0" + lodash.isarguments "^3.1.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + integrity sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw== ip@^1.1.0, ip@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + version "1.1.8" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48" + integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg== ipaddr.js@1.9.1, ipaddr.js@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -is-absolute-url@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" - integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= - is-absolute-url@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" @@ -6196,7 +6858,7 @@ is-absolute-url@^3.0.3: is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A== dependencies: kind-of "^3.0.2" @@ -6207,30 +6869,46 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== +is-arguments@^1.0.4, is-arguments@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-async-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" + integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== + dependencies: + has-tostringtag "^1.0.0" is-bigint@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" - integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + integrity sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q== dependencies: binary-extensions "^1.0.0" @@ -6242,52 +6920,36 @@ is-binary-path@~2.1.0: binary-extensions "^2.0.0" is-boolean-object@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8" - integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng== + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== dependencies: call-bind "^1.0.2" + has-tostringtag "^1.0.0" -is-callable@^1.1.4: - version "1.2.2" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" - integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== - -is-callable@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" - integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== - -is-color-stop@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" - integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== dependencies: - css-color-names "^0.0.4" - hex-color-regex "^1.1.0" - hsl-regex "^1.0.0" - hsla-regex "^1.0.0" - rgb-regex "^1.0.1" - rgba-regex "^1.0.0" + builtin-modules "^3.3.0" -is-core-module@^2.2.0, is-core-module@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" - integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== - dependencies: - has "^1.0.3" +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.5.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" - integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== +is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.5.0, is-core-module@^2.9.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== dependencies: has "^1.0.3" is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg== dependencies: kind-of "^3.0.2" @@ -6298,10 +6960,12 @@ is-data-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== +is-date-object@^1.0.1, is-date-object@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" is-descriptor@^0.1.0: version "0.1.6" @@ -6321,20 +6985,25 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" -is-directory@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-docker@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== is-electron@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-electron/-/is-electron-2.2.0.tgz#8943084f09e8b731b3a7a0298a7b5d56f6b7eef0" - integrity sha512-SpMppC2XR3YdxSzczXReBjqs2zGscWQpBIKqwXYBFic0ERaxNVgwLCHwOLZeESfdJQjX0RDvrJ1lBXX2ij+G1Q== + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-electron/-/is-electron-2.2.2.tgz#3778902a2044d76de98036f5dc58089ac4d80bb9" + integrity sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg== is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== is-extendable@^1.0.1: version "1.0.1" @@ -6346,27 +7015,46 @@ is-extendable@^1.0.1: is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-finalizationregistry@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" + integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== + dependencies: + call-bind "^1.0.2" is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-fullwidth-code-point@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" + integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + is-generator-fn@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + integrity sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw== dependencies: is-extglob "^2.1.0" @@ -6377,18 +7065,22 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-inside-container@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + dependencies: + is-docker "^3.0.0" + +is-map@^2.0.1, is-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== + is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" - integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= - -is-nan@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" - integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== is-negative-zero@^2.0.2: version "2.0.2" @@ -6396,14 +7088,16 @@ is-negative-zero@^2.0.2: integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== is-number-object@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" - integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== dependencies: kind-of "^3.0.2" @@ -6415,12 +7109,7 @@ is-number@^7.0.0: is-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + integrity sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg== is-path-cwd@^2.0.0: version "2.2.0" @@ -6441,6 +7130,11 @@ is-path-inside@^2.1.0: dependencies: path-is-inside "^1.0.2" +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -6463,14 +7157,7 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-regex@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" - integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== - dependencies: - has-symbols "^1.0.1" - -is-regex@^1.1.4: +is-regex@^1.0.4, is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== @@ -6481,12 +7168,12 @@ is-regex@^1.1.4: is-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" - integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= + integrity sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA== -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== +is-set@^2.0.1, is-set@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== is-shared-array-buffer@^1.0.2: version "1.0.2" @@ -6498,12 +7185,17 @@ is-shared-array-buffer@^1.0.2: is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" @@ -6512,25 +7204,30 @@ is-string@^1.0.5, is-string@^1.0.7: dependencies: has-tostringtag "^1.0.0" -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== - dependencies: - has-symbols "^1.0.1" - -is-symbol@^1.0.3: +is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: has-symbols "^1.0.2" +is-typed-array@^1.1.10, is-typed-array@^1.1.9: + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== + dependencies: + which-typed-array "^1.1.11" + is-url@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== +is-weakmap@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" + integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== + is-weakref@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -6538,6 +7235,14 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" +is-weakset@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" + integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -6546,49 +7251,56 @@ is-windows@^1.0.1, is-windows@^1.0.2: is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== dependencies: isarray "1.0.0" isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== -istanbul-lib-coverage@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" - integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== - -istanbul-lib-coverage@^3.2.0: +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" - integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== dependencies: "@babel/core" "^7.12.3" "@babel/parser" "^7.14.7" @@ -6597,421 +7309,461 @@ istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: semver "^6.3.0" istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== dependencies: istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" + make-dir "^4.0.0" supports-color "^7.1.0" istanbul-lib-source-maps@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" - integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== dependencies: debug "^4.1.1" istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" istanbul-reports@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.3.tgz#4bcae3103b94518117930d51283690960b50d3c2" - integrity sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg== + version "3.1.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" + integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +iterator.prototype@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.0.tgz#690c88b043d821f783843aaf725d7ac3b62e3b46" + integrity sha512-rjuhAk1AJ1fssphHD0IFV6TWL40CwRZ53FrztKx43yk2v6rguBYsY4Bj1VU4HmoMmKwZUlx7mfnhDf9cOp4YTw== + dependencies: + define-properties "^1.1.4" + get-intrinsic "^1.1.3" + has-symbols "^1.0.3" + has-tostringtag "^1.0.0" + reflect.getprototypeof "^1.0.3" + +jackspeak@^2.0.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.3.tgz#95e4cbcc03b3eb357bf6bcce14a903fb3d1151e1" + integrity sha512-R2bUw+kVZFS/h1AZqBKrSgDmdmjApzgY0AlCPumopFiAlbUxE2gf+SCuBzQ0cP5hHmUmFYF5yw55T97Th5Kstg== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jake@^10.8.5: - version "10.8.5" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" - integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== + version "10.8.7" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f" + integrity sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w== dependencies: async "^3.2.3" chalk "^4.0.2" - filelist "^1.0.1" - minimatch "^3.0.4" + filelist "^1.0.4" + minimatch "^3.1.2" -jest-changed-files@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.2.0.tgz#b6598daa9803ea6a4dce7968e20ab380ddbee289" - integrity sha512-qPVmLLyBmvF5HJrY7krDisx6Voi8DmlV3GZYX0aFNbaQsZeoz1hfxcCMbqDGuQCxU1dJy9eYc2xscE8QrCCYaA== +jest-changed-files@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" + integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== dependencies: execa "^5.0.0" p-limit "^3.1.0" -jest-circus@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.2.2.tgz#1dc4d35fd49bf5e64d3cc505fb2db396237a6dfa" - integrity sha512-upSdWxx+Mh4DV7oueuZndJ1NVdgtTsqM4YgywHEx05UMH5nxxA2Qu9T9T9XVuR021XxqSoaKvSmmpAbjwwwxMw== +jest-circus@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.2.tgz#1e6ffca60151ac66cad63fce34f443f6b5bb4258" + integrity sha512-G9mN+KOYIUe2sB9kpJkO9Bk18J4dTDArNFPwoZ7WKHKel55eKIS/u2bLthxgojwlf9NLCVQfgzM/WsOVvoC6Fw== dependencies: - "@jest/environment" "^29.2.2" - "@jest/expect" "^29.2.2" - "@jest/test-result" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/environment" "^29.6.2" + "@jest/expect" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - dedent "^0.7.0" + dedent "^1.0.0" is-generator-fn "^2.0.0" - jest-each "^29.2.1" - jest-matcher-utils "^29.2.2" - jest-message-util "^29.2.1" - jest-runtime "^29.2.2" - jest-snapshot "^29.2.2" - jest-util "^29.2.1" + jest-each "^29.6.2" + jest-matcher-utils "^29.6.2" + jest-message-util "^29.6.2" + jest-runtime "^29.6.2" + jest-snapshot "^29.6.2" + jest-util "^29.6.2" p-limit "^3.1.0" - pretty-format "^29.2.1" + pretty-format "^29.6.2" + pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.2.2.tgz#feaf0aa57d327e80d4f2f18d5f8cd2e77cac5371" - integrity sha512-R45ygnnb2CQOfd8rTPFR+/fls0d+1zXS6JPYTBBrnLPrhr58SSuPTiA5Tplv8/PXpz4zXR/AYNxmwIj6J6nrvg== +jest-cli@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.2.tgz#edb381763398d1a292cd1b636a98bfa5644b8fda" + integrity sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q== dependencies: - "@jest/core" "^29.2.2" - "@jest/test-result" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/core" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/types" "^29.6.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.2.2" - jest-util "^29.2.1" - jest-validate "^29.2.2" + jest-config "^29.6.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.2.2.tgz#bf98623a46454d644630c1f0de8bba3f495c2d59" - integrity sha512-Q0JX54a5g1lP63keRfKR8EuC7n7wwny2HoTRDb8cx78IwQOiaYUVZAdjViY3WcTxpR02rPUpvNVmZ1fkIlZPcw== +jest-config@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.2.tgz#c68723f06b31ca5e63030686e604727d406cd7c3" + integrity sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.2.2" - "@jest/types" "^29.2.1" - babel-jest "^29.2.2" + "@jest/test-sequencer" "^29.6.2" + "@jest/types" "^29.6.1" + babel-jest "^29.6.2" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.2.2" - jest-environment-node "^29.2.2" - jest-get-type "^29.2.0" - jest-regex-util "^29.2.0" - jest-resolve "^29.2.2" - jest-runner "^29.2.2" - jest-util "^29.2.1" - jest-validate "^29.2.2" + jest-circus "^29.6.2" + jest-environment-node "^29.6.2" + jest-get-type "^29.4.3" + jest-regex-util "^29.4.3" + jest-resolve "^29.6.2" + jest-runner "^29.6.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.2.1" + pretty-format "^29.6.2" slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^25.2.1: - version "25.5.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" - integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A== - dependencies: - chalk "^3.0.0" - diff-sequences "^25.2.6" - jest-get-type "^25.2.6" - pretty-format "^25.5.0" - -jest-diff@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.2.1.tgz#027e42f5a18b693fb2e88f81b0ccab533c08faee" - integrity sha512-gfh/SMNlQmP3MOUgdzxPOd4XETDJifADpT937fN1iUGz+9DgOu2eUPHH25JDkLVcLwwqxv3GzVyK4VBUr9fjfA== +jest-diff@^29.6.2: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.4.tgz#85aaa6c92a79ae8cd9a54ebae8d5b6d9a513314a" + integrity sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw== dependencies: chalk "^4.0.0" - diff-sequences "^29.2.0" - jest-get-type "^29.2.0" - pretty-format "^29.2.1" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.6.3" -jest-docblock@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.2.0.tgz#307203e20b637d97cee04809efc1d43afc641e82" - integrity sha512-bkxUsxTgWQGbXV5IENmfiIuqZhJcyvF7tU4zJ/7ioTutdz4ToB5Yx6JOFBpgI+TphRY4lhOyCWGNH/QFQh5T6A== +jest-diff@^29.6.4, jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" + integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== dependencies: detect-newline "^3.0.0" -jest-each@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.2.1.tgz#6b0a88ee85c2ba27b571a6010c2e0c674f5c9b29" - integrity sha512-sGP86H/CpWHMyK3qGIGFCgP6mt+o5tu9qG4+tobl0LNdgny0aitLXs9/EBacLy3Bwqy+v4uXClqJgASJWcruYw== +jest-each@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.2.tgz#c9e4b340bcbe838c73adf46b76817b15712d02ce" + integrity sha512-MsrsqA0Ia99cIpABBc3izS1ZYoYfhIy0NNWqPSE0YXbQjwchyt6B1HD2khzyPe1WiJA7hbxXy77ZoUQxn8UlSw== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.6.1" chalk "^4.0.0" - jest-get-type "^29.2.0" - jest-util "^29.2.1" - pretty-format "^29.2.1" + jest-get-type "^29.4.3" + jest-util "^29.6.2" + pretty-format "^29.6.2" -jest-environment-jsdom@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.2.1.tgz#5bfbbc52a74b333c7e69ff3a4f540af850a7a718" - integrity sha512-MipBdmrjgzEdQMkK7b7wBShOfv1VqO6FVwa9S43bZwKYLC4dlWnPiCgNpZX3ypNEpJO8EMpMhg4HrUkWUZXGiw== +jest-environment-jsdom@^29.5.0: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.6.2.tgz#4fc68836a7774a771819a2f980cb47af3b1629da" + integrity sha512-7oa/+266AAEgkzae8i1awNEfTfjwawWKLpiw2XesZmaoVVj9u9t8JOYx18cG29rbPNtkUlZ8V4b5Jb36y/VxoQ== dependencies: - "@jest/environment" "^29.2.1" - "@jest/fake-timers" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/environment" "^29.6.2" + "@jest/fake-timers" "^29.6.2" + "@jest/types" "^29.6.1" "@types/jsdom" "^20.0.0" "@types/node" "*" - jest-mock "^29.2.1" - jest-util "^29.2.1" + jest-mock "^29.6.2" + jest-util "^29.6.2" jsdom "^20.0.0" -jest-environment-node@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.2.2.tgz#a64b272773870c3a947cd338c25fd34938390bc2" - integrity sha512-B7qDxQjkIakQf+YyrqV5dICNs7tlCO55WJ4OMSXsqz1lpI/0PmeuXdx2F7eU8rnPbRkUR/fItSSUh0jvE2y/tw== +jest-environment-node@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.2.tgz#a9ea2cabff39b08eca14ccb32c8ceb924c8bb1ad" + integrity sha512-YGdFeZ3T9a+/612c5mTQIllvWkddPbYcN2v95ZH24oWMbGA4GGS2XdIF92QMhUhvrjjuQWYgUGW2zawOyH63MQ== dependencies: - "@jest/environment" "^29.2.2" - "@jest/fake-timers" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/environment" "^29.6.2" + "@jest/fake-timers" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" - jest-mock "^29.2.2" - jest-util "^29.2.1" + jest-mock "^29.6.2" + jest-util "^29.6.2" -jest-get-type@^25.2.6: - version "25.2.6" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" - integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig== +jest-get-type@^29.4.3, jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== -jest-get-type@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.2.0.tgz#726646f927ef61d583a3b3adb1ab13f3a5036408" - integrity sha512-uXNJlg8hKFEnDgFsrCjznB+sTxdkuqiCL6zMgA75qEbAJjJYTs9XPrvDctrEig2GDow22T/LvHgO57iJhXB/UA== - -jest-haste-map@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.2.1.tgz#f803fec57f8075e6c55fb5cd551f99a72471c699" - integrity sha512-wF460rAFmYc6ARcCFNw4MbGYQjYkvjovb9GBT+W10Um8q5nHq98jD6fHZMDMO3tA56S8XnmNkM8GcA8diSZfnA== +jest-haste-map@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.2.tgz#298c25ea5255cfad8b723179d4295cf3a50a70d1" + integrity sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.6.1" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^29.2.0" - jest-util "^29.2.1" - jest-worker "^29.2.1" + jest-regex-util "^29.4.3" + jest-util "^29.6.2" + jest-worker "^29.6.2" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.2.1.tgz#ec551686b7d512ec875616c2c3534298b1ffe2fc" - integrity sha512-1YvSqYoiurxKOJtySc+CGVmw/e1v4yNY27BjWTVzp0aTduQeA7pdieLiW05wTYG/twlKOp2xS/pWuikQEmklug== +jest-leak-detector@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.2.tgz#e2b307fee78cab091c37858a98c7e1d73cdf5b38" + integrity sha512-aNqYhfp5uYEO3tdWMb2bfWv6f0b4I0LOxVRpnRLAeque2uqOVVMLh6khnTcE2qJ5wAKop0HcreM1btoysD6bPQ== dependencies: - jest-get-type "^29.2.0" - pretty-format "^29.2.1" + jest-get-type "^29.4.3" + pretty-format "^29.6.2" -jest-matcher-utils@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz#9202f8e8d3a54733266784ce7763e9a08688269c" - integrity sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw== +jest-matcher-utils@^29.6.2: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.4.tgz#327db7ababea49455df3b23e5d6109fe0c709d24" + integrity sha512-KSzwyzGvK4HcfnserYqJHYi7sZVqdREJ9DMPAKVbS98JsIAvumihaNUbjrWw0St7p9IY7A9UskCW5MYlGmBQFQ== dependencies: chalk "^4.0.0" - jest-diff "^29.2.1" - jest-get-type "^29.2.0" - pretty-format "^29.2.1" + jest-diff "^29.6.4" + jest-get-type "^29.6.3" + pretty-format "^29.6.3" -jest-message-util@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.2.1.tgz#3a51357fbbe0cc34236f17a90d772746cf8d9193" - integrity sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw== +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.6.2: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.3.tgz#bce16050d86801b165f20cfde34dc01d3cf85fbf" + integrity sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.2.1" + "@jest/types" "^29.6.3" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^29.2.1" + pretty-format "^29.6.3" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.2.1.tgz#a0d361cffcb28184fa9c5443adbf591fa5759775" - integrity sha512-NDphaY/GqyQpTfnTZiTqqpMaw4Z0I7XnB7yBgrT6IwYrLGxpOhrejYr4ANY4YvO2sEGdd8Tx/6D0+WLQy7/qDA== +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== dependencies: - "@jest/types" "^29.2.1" - "@types/node" "*" - jest-util "^29.2.1" + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" -jest-mock@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.2.2.tgz#9045618b3f9d27074bbcf2d55bdca6a5e2e8bca7" - integrity sha512-1leySQxNAnivvbcx0sCB37itu8f4OX2S/+gxLAV4Z62shT4r4dTG9tACDywUAEZoLSr36aYUTsVp3WKwWt4PMQ== +jest-mock@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.2.tgz#ef9c9b4d38c34a2ad61010a021866dad41ce5e00" + integrity sha512-hoSv3lb3byzdKfwqCuT6uTscan471GUECqgNYykg6ob0yiAw3zYc7OrPnI9Qv8Wwoa4lC7AZ9hyS4AiIx5U2zg== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.6.1" "@types/node" "*" - jest-util "^29.2.1" + jest-util "^29.6.2" jest-pnp-resolver@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== -jest-regex-util@^29.2.0: - version "29.2.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.2.0.tgz#82ef3b587e8c303357728d0322d48bbfd2971f7b" - integrity sha512-6yXn0kg2JXzH30cr2NlThF+70iuO/3irbaB4mh5WyqNIvLLP+B6sFdluO1/1RJmslyh/f9osnefECflHvTbwVA== +jest-regex-util@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" + integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== -jest-resolve-dependencies@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.2.2.tgz#1f444766f37a25f1490b5137408b6ff746a05d64" - integrity sha512-wWOmgbkbIC2NmFsq8Lb+3EkHuW5oZfctffTGvwsA4JcJ1IRk8b2tg+hz44f0lngvRTeHvp3Kyix9ACgudHH9aQ== +jest-resolve-dependencies@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.2.tgz#36435269b6672c256bcc85fb384872c134cc4cf2" + integrity sha512-LGqjDWxg2fuQQm7ypDxduLu/m4+4Lb4gczc13v51VMZbVP5tSBILqVx8qfWcsdP8f0G7aIqByIALDB0R93yL+w== dependencies: - jest-regex-util "^29.2.0" - jest-snapshot "^29.2.2" + jest-regex-util "^29.4.3" + jest-snapshot "^29.6.2" -jest-resolve@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.2.2.tgz#ad6436053b0638b41e12bbddde2b66e1397b35b5" - integrity sha512-3gaLpiC3kr14rJR3w7vWh0CBX2QAhfpfiQTwrFPvVrcHe5VUBtIXaR004aWE/X9B2CFrITOQAp5gxLONGrk6GA== +jest-resolve@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.2.tgz#f18405fe4b50159b7b6d85e81f6a524d22afb838" + integrity sha512-G/iQUvZWI5e3SMFssc4ug4dH0aZiZpsDq9o1PtXTV1210Ztyb2+w+ZgQkB3iOiC5SmAEzJBOHWz6Hvrd+QnNPw== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.2.1" + jest-haste-map "^29.6.2" jest-pnp-resolver "^1.2.2" - jest-util "^29.2.1" - jest-validate "^29.2.2" + jest-util "^29.6.2" + jest-validate "^29.6.2" resolve "^1.20.0" - resolve.exports "^1.1.0" + resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.2.2.tgz#6b5302ed15eba8bf05e6b14d40f1e8d469564da3" - integrity sha512-1CpUxXDrbsfy9Hr9/1zCUUhT813kGGK//58HeIw/t8fa/DmkecEwZSWlb1N/xDKXg3uCFHQp1GCvlSClfImMxg== +jest-runner@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.2.tgz#89e8e32a8fef24781a7c4c49cd1cb6358ac7fc01" + integrity sha512-wXOT/a0EspYgfMiYHxwGLPCZfC0c38MivAlb2lMEAlwHINKemrttu1uSbcGbfDV31sFaPWnWJPmb2qXM8pqZ4w== dependencies: - "@jest/console" "^29.2.1" - "@jest/environment" "^29.2.2" - "@jest/test-result" "^29.2.1" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/console" "^29.6.2" + "@jest/environment" "^29.6.2" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^29.2.0" - jest-environment-node "^29.2.2" - jest-haste-map "^29.2.1" - jest-leak-detector "^29.2.1" - jest-message-util "^29.2.1" - jest-resolve "^29.2.2" - jest-runtime "^29.2.2" - jest-util "^29.2.1" - jest-watcher "^29.2.2" - jest-worker "^29.2.1" + jest-docblock "^29.4.3" + jest-environment-node "^29.6.2" + jest-haste-map "^29.6.2" + jest-leak-detector "^29.6.2" + jest-message-util "^29.6.2" + jest-resolve "^29.6.2" + jest-runtime "^29.6.2" + jest-util "^29.6.2" + jest-watcher "^29.6.2" + jest-worker "^29.6.2" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.2.2.tgz#4068ee82423769a481460efd21d45a8efaa5c179" - integrity sha512-TpR1V6zRdLynckKDIQaY41od4o0xWL+KOPUCZvJK2bu5P1UXhjobt5nJ2ICNeIxgyj9NGkO0aWgDqYPVhDNKjA== +jest-runtime@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.2.tgz#692f25e387f982e89ab83270e684a9786248e545" + integrity sha512-2X9dqK768KufGJyIeLmIzToDmsN0m7Iek8QNxRSI/2+iPFYHF0jTwlO3ftn7gdKd98G/VQw9XJCk77rbTGZnJg== dependencies: - "@jest/environment" "^29.2.2" - "@jest/fake-timers" "^29.2.2" - "@jest/globals" "^29.2.2" - "@jest/source-map" "^29.2.0" - "@jest/test-result" "^29.2.1" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/environment" "^29.6.2" + "@jest/fake-timers" "^29.6.2" + "@jest/globals" "^29.6.2" + "@jest/source-map" "^29.6.0" + "@jest/test-result" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.2.1" - jest-message-util "^29.2.1" - jest-mock "^29.2.2" - jest-regex-util "^29.2.0" - jest-resolve "^29.2.2" - jest-snapshot "^29.2.2" - jest-util "^29.2.1" + jest-haste-map "^29.6.2" + jest-message-util "^29.6.2" + jest-mock "^29.6.2" + jest-regex-util "^29.4.3" + jest-resolve "^29.6.2" + jest-snapshot "^29.6.2" + jest-util "^29.6.2" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.2.2.tgz#1016ce60297b77382386bad561107174604690c2" - integrity sha512-GfKJrpZ5SMqhli3NJ+mOspDqtZfJBryGA8RIBxF+G+WbDoC7HCqKaeAss4Z/Sab6bAW11ffasx8/vGsj83jyjA== +jest-snapshot@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.2.tgz#9b431b561a83f2bdfe041e1cab8a6becdb01af9c" + integrity sha512-1OdjqvqmRdGNvWXr/YZHuyhh5DeaLp1p/F8Tht/MrMw4Kr1Uu/j4lRG+iKl1DAqUJDWxtQBMk41Lnf/JETYBRA== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.2.2" - "@jest/transform" "^29.2.2" - "@jest/types" "^29.2.1" - "@types/babel__traverse" "^7.0.6" - "@types/prettier" "^2.1.5" + "@jest/expect-utils" "^29.6.2" + "@jest/transform" "^29.6.2" + "@jest/types" "^29.6.1" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.2.2" + expect "^29.6.2" graceful-fs "^4.2.9" - jest-diff "^29.2.1" - jest-get-type "^29.2.0" - jest-haste-map "^29.2.1" - jest-matcher-utils "^29.2.2" - jest-message-util "^29.2.1" - jest-util "^29.2.1" + jest-diff "^29.6.2" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.6.2" + jest-message-util "^29.6.2" + jest-util "^29.6.2" natural-compare "^1.4.0" - pretty-format "^29.2.1" - semver "^7.3.5" + pretty-format "^29.6.2" + semver "^7.5.3" -jest-util@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.2.1.tgz#f26872ba0dc8cbefaba32c34f98935f6cf5fc747" - integrity sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g== +jest-util@^29.6.2: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.3.tgz#e15c3eac8716440d1ed076f09bc63ace1aebca63" + integrity sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.2.2.tgz#e43ce1931292dfc052562a11bc681af3805eadce" - integrity sha512-eJXATaKaSnOuxNfs8CLHgdABFgUrd0TtWS8QckiJ4L/QVDF4KVbZFBBOwCBZHOS0Rc5fOxqngXeGXE3nGQkpQA== +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== dependencies: - "@jest/types" "^29.2.1" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.2.tgz#25d972af35b2415b83b1373baf1a47bb266c1082" + integrity sha512-vGz0yMN5fUFRRbpJDPwxMpgSXW1LDKROHfBopAvDcmD6s+B/s8WJrwi+4bfH4SdInBA5C3P3BI19dBtKzx1Arg== + dependencies: + "@jest/types" "^29.6.1" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^29.2.0" + jest-get-type "^29.4.3" leven "^3.1.0" - pretty-format "^29.2.1" + pretty-format "^29.6.2" -jest-watcher@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.2.2.tgz#7093d4ea8177e0a0da87681a9e7b09a258b9daf7" - integrity sha512-j2otfqh7mOvMgN2WlJ0n7gIx9XCMWntheYGlBK7+5g3b1Su13/UAK7pdKGyd4kDlrLwtH2QPvRv5oNIxWvsJ1w== +jest-watcher@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.2.tgz#77c224674f0620d9f6643c4cfca186d8893ca088" + integrity sha512-GZitlqkMkhkefjfN/p3SJjrDaxPflqxEAv3/ik10OirZqJGYH5rPiIsgVcfof0Tdqg3shQGdEIxDBx+B4tuLzA== dependencies: - "@jest/test-result" "^29.2.1" - "@jest/types" "^29.2.1" + "@jest/test-result" "^29.6.2" + "@jest/types" "^29.6.1" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.2.1" + jest-util "^29.6.2" string-length "^4.0.1" -jest-worker@^26.2.1: +jest-worker@^26.2.1, jest-worker@^26.5.0: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== @@ -7020,39 +7772,41 @@ jest-worker@^26.2.1: merge-stream "^2.0.0" supports-color "^7.0.0" -jest-worker@^26.5.0: - version "26.5.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.5.0.tgz#87deee86dbbc5f98d9919e0dadf2c40e3152fa30" - integrity sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug== +jest-worker@^29.6.2: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.2.tgz#682fbc4b6856ad0aa122a5403c6d048b83f3fb44" + integrity sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ== dependencies: "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" - -jest-worker@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.2.1.tgz#8ba68255438252e1674f990f0180c54dfa26a3b1" - integrity sha512-ROHTZ+oj7sBrgtv46zZ84uWky71AoYi0vEV9CdEtc1FQunsoAGe5HbQmW76nI5QWdvECVPrSi1MCVUmizSavMg== - dependencies: - "@types/node" "*" - jest-util "^29.2.1" + jest-util "^29.6.2" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.2.2: - version "29.2.2" - resolved "https://registry.yarnpkg.com/jest/-/jest-29.2.2.tgz#24da83cbbce514718acd698926b7679109630476" - integrity sha512-r+0zCN9kUqoON6IjDdjbrsWobXM/09Nd45kIPRD8kloaRh1z5ZCMdVsgLXGxmlL7UpAJsvCYOQNO+NjvG/gqiQ== +jest@^29.5.0: + version "29.6.2" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.6.2.tgz#3bd55b9fd46a161b2edbdf5f1d1bd0d1eab76c42" + integrity sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg== dependencies: - "@jest/core" "^29.2.2" - "@jest/types" "^29.2.1" + "@jest/core" "^29.6.2" + "@jest/types" "^29.6.1" import-local "^3.0.2" - jest-cli "^29.2.2" + jest-cli "^29.6.2" -js-base64@^2.1.9: - version "2.6.4" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" - integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== +jpeg-autorotate@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/jpeg-autorotate/-/jpeg-autorotate-7.1.1.tgz#c57905c6afd3b54373a6a1d0249ed6e07f7b043b" + integrity sha512-ewTZTG/QWOM0D5h/yKcQ3QgyrnQYsr3qmcS+bqoAwgQAY1KBa31aJ+q+FlElaxo/rSYqfF1ixf+8EIgluBkgTg== + dependencies: + colors "^1.4.0" + glob "^7.1.6" + jpeg-js "^0.4.2" + piexifjs "^1.0.6" + yargs-parser "^20.2.1" + +jpeg-js@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" + integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" @@ -7074,18 +7828,23 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsdom@^20.0.0, jsdom@^20.0.1: - version "20.0.1" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.1.tgz#d95b4a3b6e1eec6520aa01d9d908eade8c6ba153" - integrity sha512-pksjj7Rqoa+wdpkKcLzQRHhJCEE42qQhl/xLMUKHgoSejaKOdaXEAnqs6uDNwMl/fciHTzKeR8Wm8cw7N+g98A== +jsdoc-type-pratt-parser@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz#136f0571a99c184d84ec84662c45c29ceff71114" + integrity sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ== + +jsdom@^20.0.0: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db" + integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ== dependencies: abab "^2.0.6" - acorn "^8.8.0" + acorn "^8.8.1" acorn-globals "^7.0.0" cssom "^0.5.0" cssstyle "^2.3.0" data-urls "^3.0.2" - decimal.js "^10.4.1" + decimal.js "^10.4.2" domexception "^4.0.0" escodegen "^2.0.0" form-data "^4.0.0" @@ -7098,12 +7857,41 @@ jsdom@^20.0.0, jsdom@^20.0.1: saxes "^6.0.0" symbol-tree "^3.2.4" tough-cookie "^4.1.2" - w3c-xmlserializer "^3.0.0" + w3c-xmlserializer "^4.0.0" webidl-conversions "^7.0.0" whatwg-encoding "^2.0.0" whatwg-mimetype "^3.0.0" whatwg-url "^11.0.0" - ws "^8.9.0" + ws "^8.11.0" + xml-name-validator "^4.0.0" + +jsdom@^22.1.0: + version "22.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.1.0.tgz#0fca6d1a37fbeb7f4aac93d1090d782c56b611c8" + integrity sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw== + dependencies: + abab "^2.0.6" + cssstyle "^3.0.0" + data-urls "^4.0.0" + decimal.js "^10.4.3" + domexception "^4.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.4" + parse5 "^7.1.2" + rrweb-cssom "^0.6.0" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^4.1.2" + w3c-xmlserializer "^4.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^12.0.1" + ws "^8.13.0" xml-name-validator "^4.0.0" jsesc@^2.5.1: @@ -7114,9 +7902,14 @@ jsesc@^2.5.1: jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== -json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== @@ -7144,38 +7937,26 @@ json-schema@^0.4.0: json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.2.tgz#e06f23128e0bbe342dc996ed5a19e28b57b580e0" + integrity sha512-eunSSaEnxV12z+Z73y/j5N37/In40GK4GmsSy+tEHJMxknvqnA7/djeYtAgW0GsWHUfg+847WJjKaEylk2y09g== dependencies: - jsonify "~0.0.0" + jsonify "^0.0.1" -json3@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" - integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== +json5@^1.0.1, json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" -json5@^2.1.2, json5@^2.2.0, json5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= - optionalDependencies: - graceful-fs "^4.1.6" +json5@^2.1.2, json5@^2.2.0, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== jsonfile@^6.0.1: version "6.1.0" @@ -7186,28 +7967,45 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= +jsonify@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" + integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== jsonpointer@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.0.tgz#f802669a524ec4805fa7389eadbc9921d5dc8072" - integrity sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg== + version "5.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" + integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== -"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz#afe5efe4332cd3515c065072bd4d6b0aa22152bd" - integrity sha512-4ZCADZHRkno244xlNnn4AOG6sRQ7iBZ5BbgZ4vW4y5IZw7cVUD1PPeblm1xx/nfmMxPdt/LHsXZW8z/j58+l9Q== +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.3.5" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" + integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" + +jsx-ast-utils@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz#76b3e6e6cece5c69d49a5792c3d01bd1a0cdc7ea" + integrity sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw== dependencies: array-includes "^3.1.5" - object.assign "^4.1.2" + object.assign "^4.1.3" keycode@^2.1.7: - version "2.2.0" - resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.0.tgz#3d0af56dc7b8b8e5cba8d0a97f107204eec22b04" - integrity sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ= + version "2.2.1" + resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.1.tgz#09c23b2be0611d26117ea2501c2c391a01f39eff" + integrity sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg== + +keyv@^4.5.3: + version "4.5.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" + integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug== + dependencies: + json-buffer "3.0.1" killable@^1.0.1: version "1.0.1" @@ -7225,24 +8023,24 @@ kleur@^3.0.3: integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== klona@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" - integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== + version "2.0.6" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" + integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== -known-css-properties@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.25.0.tgz#6ebc4d4b412f602e5cfbeb4086bd544e34c0a776" - integrity sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA== +known-css-properties@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.27.0.tgz#82a9358dda5fe7f7bd12b5e7142c0a205393c0c5" + integrity sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg== language-subtag-registry@~0.3.2: - version "0.3.20" - resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz#a00a37121894f224f763268e431c55556b0c0755" - integrity sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg== + version "0.3.22" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" + integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== -language-tags@^1.0.5: +language-tags@=1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" - integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo= + integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ== dependencies: language-subtag-registry "~0.3.2" @@ -7259,50 +8057,72 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" +lilconfig@2.1.0, lilconfig@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +lint-staged@^13.2.2: + version "13.2.3" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.2.3.tgz#f899aad6c093473467e9c9e316e3c2d8a28f87a7" + integrity sha512-zVVEXLuQIhr1Y7R7YAWx4TZLdvuzk7DnmrsTNL0fax6Z3jrpFcas+vKbzxhhvp6TA55m1SQuWkpzI1qbfDZbAg== + dependencies: + chalk "5.2.0" + cli-truncate "^3.1.0" + commander "^10.0.0" + debug "^4.3.4" + execa "^7.0.0" + lilconfig "2.1.0" + listr2 "^5.0.7" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-inspect "^1.12.3" + pidtree "^0.6.0" + string-argv "^0.3.1" + yaml "^2.2.2" + +listr2@^5.0.7: + version "5.0.8" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-5.0.8.tgz#a9379ffeb4bd83a68931a65fb223a11510d6ba23" + integrity sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.19" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.8.0" + through "^2.3.8" + wrap-ansi "^7.0.0" loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== +loader-utils@^1.2.3, loader-utils@^1.4.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" + integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" json5 "^1.0.1" loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" json5 "^2.1.2" -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -7318,6 +8138,13 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + lockfile@^1.0: version "1.0.4" resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609" @@ -7328,72 +8155,112 @@ lockfile@^1.0: lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== -lodash.defaults@^4.0.1: +lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + +lodash.escape@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" + integrity sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw== + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== lodash.get@^4.0: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== lodash.has@^4.0: version "4.5.2" resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" - integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= + integrity sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g== + +lodash.invokemap@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz#1748cda5d8b0ef8369c4eb3ec54c21feba1f2d62" + integrity sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w== + +lodash.isarguments@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== lodash.isboolean@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" - integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0= + integrity sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA== lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.pullall@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.pullall/-/lodash.pullall-4.2.0.tgz#9d98b8518b7c965b0fae4099bd9fb7df8bbf38ba" + integrity sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg== + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== + +lodash.uniqby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" + integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww== lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + loglevel@^1.6.8: - version "1.7.0" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0" - integrity sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ== + version "1.8.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.1.tgz#5c621f83d5b48c54ae93b6156353f555963377b4" + integrity sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg== loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" @@ -7416,10 +8283,15 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lz-string@^1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" - integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= +"lru-cache@^9.1.1 || ^10.0.0": + version "10.0.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" + integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== + +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== magic-string@^0.25.0, magic-string@^0.25.7: version "0.25.9" @@ -7428,21 +8300,27 @@ magic-string@^0.25.0, magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.8" -make-dir@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== +magic-string@^0.30.0: + version "0.30.3" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.3.tgz#403755dfd9d6b398dfa40635d52e96c5ac095b85" + integrity sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw== dependencies: - pify "^4.0.1" - semver "^5.6.0" + "@jridgewell/sourcemap-codec" "^1.4.15" -make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: +make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -7453,14 +8331,14 @@ makeerror@1.0.12: map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== map-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== -map-obj@^4.0.0: +map-obj@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== @@ -7468,14 +8346,14 @@ map-obj@^4.0.0: map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== dependencies: object-visit "^1.0.0" mark-loader@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/mark-loader/-/mark-loader-0.1.6.tgz#0abb477dca7421d70e20128ff6489f5cae8676d5" - integrity sha1-CrtHfcp0IdcOIBKP9kifXK6GdtU= + integrity sha512-eEhrlUX2D44vADzgo2xKTyWTf3vyiGTApvxtOKHx8+CVepdzNXaMZS7cg+LlaigsIACpTEudChSHGE6noDGy/A== marky@^1.2.5: version "1.2.5" @@ -7496,20 +8374,30 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== -mdn-data@2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" - integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== memoize-one@^6.0.0: version "6.0.0" @@ -7519,7 +8407,7 @@ memoize-one@^6.0.0: memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= + integrity sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ== dependencies: errno "^0.1.3" readable-stream "^2.0.1" @@ -7532,28 +8420,28 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" -meow@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" - integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ== +meow@^10.1.5: + version "10.1.5" + resolved "https://registry.yarnpkg.com/meow/-/meow-10.1.5.tgz#be52a1d87b5f5698602b0f32875ee5940904aa7f" + integrity sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw== dependencies: - "@types/minimist" "^1.2.0" - camelcase-keys "^6.2.2" - decamelize "^1.2.0" + "@types/minimist" "^1.2.2" + camelcase-keys "^7.0.0" + decamelize "^5.0.0" decamelize-keys "^1.1.0" hard-rejection "^2.1.0" minimist-options "4.1.0" - normalize-package-data "^3.0.0" - read-pkg-up "^7.0.1" - redent "^3.0.0" - trim-newlines "^3.0.0" - type-fest "^0.18.0" - yargs-parser "^20.2.3" + normalize-package-data "^3.0.2" + read-pkg-up "^8.0.0" + redent "^4.0.0" + trim-newlines "^4.0.2" + type-fest "^1.2.2" + yargs-parser "^20.2.9" merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== merge-stream@^2.0.0: version "2.0.0" @@ -7568,7 +8456,7 @@ merge2@^1.3.0, merge2@^1.4.1: methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" @@ -7605,63 +8493,39 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.44.0, "mime-db@>= 1.43.0 < 2": - version "1.44.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" - integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== - -mime-db@1.51.0: - version "1.51.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" - integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== - -mime-db@1.52.0: +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12: +mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" -mime-types@~2.1.17, mime-types@~2.1.24: - version "2.1.27" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" - integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== - dependencies: - mime-db "1.44.0" - -mime-types@~2.1.34: - version "2.1.34" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" - integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== - dependencies: - mime-db "1.51.0" - mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.3.1: - version "2.4.7" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.7.tgz#962aed9be0ed19c91fd7dc2ece5d7f4e89a90d74" - integrity sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA== - mime@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" - integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -min-indent@^1.0.0: +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + +min-indent@^1.0.0, min-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== @@ -7683,9 +8547,9 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== -minimatch@^3.0.4, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -7693,9 +8557,16 @@ minimatch@^3.0.4, minimatch@^3.1.2: brace-expansion "^1.1.7" minimatch@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" - integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.1: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== dependencies: brace-expansion "^2.0.1" @@ -7708,10 +8579,10 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== minipass-collect@^1.0.2: version "1.0.2" @@ -7735,12 +8606,22 @@ minipass-pipeline@^1.2.2: minipass "^3.0.0" minipass@^3.0.0, minipass@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" - integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== dependencies: yallist "^4.0.0" +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.0.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.3.tgz#05ea638da44e475037ed94d1c7efcc76a25e1974" + integrity sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg== + minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -7749,22 +8630,6 @@ minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" - integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -7773,46 +8638,44 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.6, mkdirp@~0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== dependencies: - minimist "^1.2.5" + minimist "^1.2.6" mkdirp@^1.0, mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mkdirp@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== + mousetrap@^1.5.2: version "1.6.5" resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9" integrity sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA== -move-concurrently@^1.0.1: +mrmime@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" - integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= - dependencies: - aproba "^1.1.1" - copy-concurrently "^1.0.0" - fs-write-stream-atomic "^1.0.8" - mkdirp "^0.5.1" - rimraf "^2.5.4" - run-queue "^1.0.3" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" + integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.2, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@2.1.3, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -7820,7 +8683,7 @@ ms@2.1.3: multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" - integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= + integrity sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ== multicast-dns@^6.0.1: version "6.2.3" @@ -7831,14 +8694,14 @@ multicast-dns@^6.0.1: thunky "^1.0.2" nan@^2.12.1: - version "2.14.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" - integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== + version "2.17.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" + integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== -nanoid@^3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" - integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== +nanoid@^3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== nanomatch@^1.2.9: version "1.2.13" @@ -7860,7 +8723,7 @@ nanomatch@^1.2.9: natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== negotiator@0.6.3: version "0.6.3" @@ -7872,20 +8735,15 @@ neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -next-tick@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== node-fetch@^2.6.0: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + version "2.6.11" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25" + integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w== dependencies: whatwg-url "^5.0.0" @@ -7895,14 +8753,14 @@ node-forge@^0.10.0: integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== node-gyp-build@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" - integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== + version "4.6.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" + integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-libs-browser@^2.2.1: version "2.2.1" @@ -7933,27 +8791,12 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-releases@^1.1.71: - version "1.1.72" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" - integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== +node-releases@^2.0.12, node-releases@^2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== -node-releases@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" - integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== - -normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-package-data@^3.0.0: +normalize-package-data@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== @@ -7966,7 +8809,7 @@ normalize-package-data@^3.0.0: normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== dependencies: remove-trailing-separator "^1.0.1" @@ -7978,17 +8821,12 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: normalize-range@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= - -normalize-url@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" - integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== dependencies: path-key "^2.0.0" @@ -7999,6 +8837,13 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +npm-run-path@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" + integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== + dependencies: + path-key "^4.0.0" + npmlog@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-7.0.1.tgz#7372151a01ccb095c47d8bf1d0771a4ff1f53ac8" @@ -8016,52 +8861,49 @@ nth-check@^1.0.2: dependencies: boolbase "~1.0.0" -num2fraction@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" - integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" nwsapi@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" - integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== + version "2.2.7" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" + integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ== + +nwsapi@^2.2.4: + version "2.2.5" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.5.tgz#a52744c61b3889dd44b0a158687add39b8d935e2" + integrity sha512-6xpotnECFy/og7tKSBVmUNft7J3jyXAka4XvG6AUhFWRz+Q/Ljus7znJAA3bxColfQLdS+XsjoodtJfCgeTEFQ== object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== dependencies: copy-descriptor "^0.1.0" define-property "^0.2.5" kind-of "^3.0.3" -object-fit-images@^3.2.3: - version "3.2.4" - resolved "https://registry.yarnpkg.com/object-fit-images/-/object-fit-images-3.2.4.tgz#6c299d38fdf207746e5d2d46c2877f6f25d15b52" - integrity sha512-G+7LzpYfTfqUyrZlfrou/PLLLAPNC52FTy5y1CBywX+1/FkxIloOyQXBmZ3Zxa2AWO+lMF0JTuvqbr7G5e5CWg== +object-inspect@^1.12.3, object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== -object-inspect@^1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" - integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== - -object-inspect@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" - integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== - -object-is@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81" - integrity sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg== +object-is@^1.0.1, object-is@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" object-keys@^1.1.1: version "1.1.1" @@ -8071,81 +8913,84 @@ object-keys@^1.1.1: object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== dependencies: isobject "^3.0.0" -object.assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" - integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.18.0-next.0" - has-symbols "^1.0.1" - object-keys "^1.1.1" - -object.assign@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" - object-keys "^1.1.1" - -object.entries@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861" - integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g== +object.assign@^4.1.3, object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" -object.fromentries@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.5.tgz#7b37b205109c21e741e605727fe8b0ad5fa08251" - integrity sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw== +object.entries@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.6.tgz#9737d0e5b8291edd340a3e3264bb8a3b00d5fa23" + integrity sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.1.4" + es-abstract "^1.20.4" + +object.fromentries@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.6.tgz#cdb04da08c539cffa912dcd368b886e0904bfa73" + integrity sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" object.getownpropertydescriptors@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + version "2.1.6" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz#5e5c384dd209fa4efffead39e3a0512770ccc312" + integrity sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ== dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" + array.prototype.reduce "^1.0.5" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.21.2" + safe-array-concat "^1.0.0" -object.hasown@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.1.tgz#ad1eecc60d03f49460600430d97f23882cf592a3" - integrity sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A== +object.groupby@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.0.tgz#cb29259cf90f37e7bac6437686c1ea8c916d12a9" + integrity sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.21.2" + get-intrinsic "^1.2.1" + +object.hasown@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.2.tgz#f919e21fad4eb38a57bc6345b3afd496515c3f92" + integrity sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw== dependencies: define-properties "^1.1.4" - es-abstract "^1.19.5" + es-abstract "^1.20.4" object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== dependencies: isobject "^3.0.1" -object.values@^1.1.0, object.values@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" - integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== +object.values@^1.1.0, object.values@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" + integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.1.4" + es-abstract "^1.20.4" -obuf@^1.0.0, obuf@^1.1.2: +obuf@^1.0.0, obuf@^1.1.2, obuf@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== @@ -8165,17 +9010,34 @@ on-headers@~1.0.2: once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -onetime@^5.1.2: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +open@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" + integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== + dependencies: + default-browser "^4.0.0" + define-lazy-prop "^3.0.0" + is-inside-container "^1.0.0" + is-wsl "^2.2.0" + opencollective-postinstall@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" @@ -8193,53 +9055,27 @@ opn@^5.5.0: dependencies: is-wsl "^1.1.0" -optionator@^0.8.1: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" - word-wrap "^1.2.3" - -original@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" - integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== - dependencies: - url-parse "^1.4.3" os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= + integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" - integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== - dependencies: - p-try "^1.0.0" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" @@ -8248,27 +9084,13 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe" - integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg== - dependencies: - p-try "^2.0.0" - -p-limit@^3.1.0: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= - dependencies: - p-limit "^1.1.0" - p-locate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" @@ -8283,6 +9105,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-map@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" @@ -8302,11 +9131,6 @@ p-retry@^3.0.1: dependencies: retry "^0.12.0" -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= - p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -8322,15 +9146,6 @@ pako@~1.0.5: resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== -parallel-transform@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" - integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== - dependencies: - cyclist "^1.0.1" - inherits "^2.0.3" - readable-stream "^2.1.5" - parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -8349,40 +9164,7 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" -parse-css-font@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/parse-css-font/-/parse-css-font-2.0.2.tgz#7b60b060705a25a9b90b7f0ed493e5823248a652" - integrity sha1-e2CwYHBaJam5C38O1JPlgjJIplI= - dependencies: - css-font-size-keywords "^1.0.0" - css-font-stretch-keywords "^1.0.1" - css-font-style-keywords "^1.0.1" - css-font-weight-keywords "^1.0.0" - css-global-keywords "^1.0.1" - css-list-helpers "^1.0.1" - css-system-font-keywords "^1.0.0" - tcomb "^2.5.0" - unquote "^1.1.0" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parse-json@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.1.tgz#7cfe35c1ccd641bce3981467e6c2ece61b3b3878" - integrity sha512-ztoZ4/DYeXQq4E21v169sC8qWINGpcosGv9XhTDvg9/hWvx/zrFkc9BiWxR58OJLHGk28j5BL0SDLeV2WmFZlQ== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - lines-and-columns "^1.1.6" - -parse-json@^5.2.0: +parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== @@ -8395,12 +9177,12 @@ parse-json@^5.2.0: parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== -parse5@^7.0.0, parse5@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.1.tgz#4649f940ccfb95d8754f37f73078ea20afe0c746" - integrity sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg== +parse5@^7.0.0, parse5@^7.1.1, parse5@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== dependencies: entities "^4.4.0" @@ -8412,7 +9194,7 @@ parseurl@~1.3.2, parseurl@~1.3.3: pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== path-browserify@0.0.1: version "0.0.1" @@ -8427,12 +9209,12 @@ path-complete-extname@^1.0.0: path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + integrity sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q== path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== path-exists@^4.0.0: version "4.0.0" @@ -8442,37 +9224,50 @@ path-exists@^4.0.0: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6, path-parse@^1.0.7: +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + +path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" + integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== + dependencies: + lru-cache "^9.1.1 || ^10.0.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== path-to-regexp@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" - integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30= + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== dependencies: isarray "0.0.1" @@ -8482,9 +9277,9 @@ path-type@^4.0.0: integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== pbkdf2@^3.0.3: - version "3.1.1" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" - integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg== + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" @@ -8495,32 +9290,42 @@ pbkdf2@^3.0.3: performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" - integrity sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU= + integrity sha512-YHk5ez1hmMR5LOkb9iJkLKqoBlL7WD5M8ljC75ZfzXriuBIVNuecaXuU7e+hOwyqf24Wxhh7Vxgt7Hnw9288Tg== performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -pg-connection-string@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.4.0.tgz#c979922eb47832999a204da5dbe1ebf2341b6a10" - integrity sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ== +pg-cloudflare@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" + integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== + +pg-connection-string@^2.6.0, pg-connection-string@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.2.tgz#713d82053de4e2bd166fab70cd4f26ad36aab475" + integrity sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA== pg-int8@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== -pg-pool@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.2.2.tgz#a560e433443ed4ad946b84d774b3f22452694dff" - integrity sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA== +pg-numeric@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pg-numeric/-/pg-numeric-1.0.2.tgz#816d9a44026086ae8ae74839acd6a09b0636aa3a" + integrity sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw== -pg-protocol@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.4.0.tgz#43a71a92f6fe3ac559952555aa3335c8cb4908be" - integrity sha512-El+aXWcwG/8wuFICMQjM5ZSAm6OWiJicFdNYo+VY3QP+8vI4SvLIWVe51PppTzMhikUJR+PsyIFKqfdXPz/yxA== +pg-pool@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.1.tgz#5a902eda79a8d7e3c928b77abf776b3cb7d351f7" + integrity sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og== + +pg-protocol@*, pg-protocol@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" + integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== pg-types@^2.1.0: version "2.2.0" @@ -8533,55 +9338,65 @@ pg-types@^2.1.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" +pg-types@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-4.0.1.tgz#31857e89d00a6c66b06a14e907c3deec03889542" + integrity sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g== + dependencies: + pg-int8 "1.0.1" + pg-numeric "1.0.2" + postgres-array "~3.0.1" + postgres-bytea "~3.0.0" + postgres-date "~2.0.1" + postgres-interval "^3.0.0" + postgres-range "^1.1.1" + pg@^8.5.0: - version "8.5.1" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.5.1.tgz#34dcb15f6db4a29c702bf5031ef2e1e25a06a120" - integrity sha512-9wm3yX9lCfjvA98ybCyw2pADUivyNWT/yIP4ZcDVpMN0og70BUWYEGXPCTAQdGTAqnytfRADb7NERrY1qxhIqw== + version "8.11.3" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.3.tgz#d7db6e3fe268fcedd65b8e4599cda0b8b4bf76cb" + integrity sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g== dependencies: buffer-writer "2.0.0" packet-reader "1.0.0" - pg-connection-string "^2.4.0" - pg-pool "^3.2.2" - pg-protocol "^1.4.0" + pg-connection-string "^2.6.2" + pg-pool "^3.6.1" + pg-protocol "^1.6.0" pg-types "^2.1.0" pgpass "1.x" + optionalDependencies: + pg-cloudflare "^1.1.1" pgpass@1.x: - version "1.0.4" - resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.4.tgz#85eb93a83800b20f8057a2b029bf05abaf94ea9c" - integrity sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w== + version "1.0.5" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== dependencies: - split2 "^3.1.1" - -picocolors@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" - integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== + split2 "^4.1.0" picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== - -picomatch@^2.2.2, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== +pidtree@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" + integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== + +piexifjs@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/piexifjs/-/piexifjs-1.0.6.tgz#883811d73f447218d0d06e9ed7866d04533e59e0" + integrity sha512-0wVyH0cKohzBQ5Gi2V1BuxYpxWfxF3cSqfFXfPIpl5tl9XLS5z4ogqhUCD20AbHi0h9aJkqXNJnkVev6gwh2ag== pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== pify@^4.0.1: version "4.0.1" @@ -8591,19 +9406,19 @@ pify@^4.0.1: pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== dependencies: pinkie "^2.0.0" pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== pirates@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.4.tgz#07df81e61028e402735cdd49db701e4885b4e6e6" - integrity sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw== + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== pkg-dir@^3.0.0: version "3.0.0" @@ -8620,159 +9435,130 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: find-up "^4.0.0" portfinder@^1.0.26: - version "1.0.28" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" - integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== + version "1.0.32" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81" + integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg== dependencies: - async "^2.6.2" - debug "^3.1.1" - mkdirp "^0.5.5" + async "^2.6.4" + debug "^3.2.7" + mkdirp "^0.5.6" posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== -postcss-calc@^7.0.1: - version "7.0.4" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.4.tgz#5e177ddb417341e6d4a193c5d9fd8ada79094f8b" - integrity sha512-0I79VRAd1UTkaHzY9w83P39YGO/M3bG7/tNLrHGEunBolfoGM0hSjrGvjoeaj0JE/zIw5GsI2KZ0UwDJqv5hjw== +postcss-calc@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6" + integrity sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ== dependencies: - postcss "^7.0.27" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" + postcss-selector-parser "^6.0.11" + postcss-value-parser "^4.2.0" -postcss-colormin@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" - integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== +postcss-colormin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-6.0.0.tgz#d4250652e952e1c0aca70c66942da93d3cdeaafe" + integrity sha512-EuO+bAUmutWoZYgHn2T1dG1pPqHU6L4TjzPlu4t1wZGXQ/fxV16xg2EJmYi0z+6r+MGV1yvpx1BHkUaRrPa2bw== dependencies: - browserslist "^4.0.0" - color "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + browserslist "^4.21.4" + caniuse-api "^3.0.0" + colord "^2.9.1" + postcss-value-parser "^4.2.0" -postcss-convert-values@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" - integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== +postcss-convert-values@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-6.0.0.tgz#ec94a954957e5c3f78f0e8f65dfcda95280b8996" + integrity sha512-U5D8QhVwqT++ecmy8rnTb+RL9n/B806UVaS3m60lqle4YDFcpbS3ae5bTQIh3wOGUSDHSEtMYLs/38dNG7EYFw== dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + browserslist "^4.21.4" + postcss-value-parser "^4.2.0" -postcss-discard-comments@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" - integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== - dependencies: - postcss "^7.0.0" +postcss-discard-comments@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-6.0.0.tgz#9ca335e8b68919f301b24ba47dde226a42e535fe" + integrity sha512-p2skSGqzPMZkEQvJsgnkBhCn8gI7NzRH2683EEjrIkoMiwRELx68yoUJ3q3DGSGuQ8Ug9Gsn+OuDr46yfO+eFw== -postcss-discard-duplicates@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" - integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== - dependencies: - postcss "^7.0.0" +postcss-discard-duplicates@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.0.tgz#c26177a6c33070922e67e9a92c0fd23d443d1355" + integrity sha512-bU1SXIizMLtDW4oSsi5C/xHKbhLlhek/0/yCnoMQany9k3nPBq+Ctsv/9oMmyqbR96HYHxZcHyK2HR5P/mqoGA== -postcss-discard-empty@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" - integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== - dependencies: - postcss "^7.0.0" +postcss-discard-empty@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-6.0.0.tgz#06c1c4fce09e22d2a99e667c8550eb8a3a1b9aee" + integrity sha512-b+h1S1VT6dNhpcg+LpyiUrdnEZfICF0my7HAKgJixJLW7BnNmpRH34+uw/etf5AhOlIhIAuXApSzzDzMI9K/gQ== -postcss-discard-overridden@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" - integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== - dependencies: - postcss "^7.0.0" +postcss-discard-overridden@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-6.0.0.tgz#49c5262db14e975e349692d9024442de7cd8e234" + integrity sha512-4VELwssYXDFigPYAZ8vL4yX4mUepF/oCBeeIT4OXsJPYOtvJumyz9WflmJWTfDwCUcpDR+z0zvCWBXgTx35SVw== -postcss-load-config@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a" - integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw== +postcss-loader@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-4.3.0.tgz#2c4de9657cd4f07af5ab42bd60a673004da1b8cc" + integrity sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q== dependencies: - cosmiconfig "^5.0.0" - import-cwd "^2.0.0" - -postcss-loader@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" - integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== - dependencies: - loader-utils "^1.1.0" - postcss "^7.0.0" - postcss-load-config "^2.0.0" - schema-utils "^1.0.0" + cosmiconfig "^7.0.0" + klona "^2.0.4" + loader-utils "^2.0.0" + schema-utils "^3.0.0" + semver "^7.3.4" postcss-media-query-parser@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" - integrity sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ= + integrity sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig== -postcss-merge-longhand@^4.0.11: - version "4.0.11" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" - integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== +postcss-merge-longhand@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-6.0.0.tgz#6f627b27db939bce316eaa97e22400267e798d69" + integrity sha512-4VSfd1lvGkLTLYcxFuISDtWUfFS4zXe0FpF149AyziftPFQIWxjvFSKhA4MIxMe4XM3yTDgQMbSNgzIVxChbIg== dependencies: - css-color-names "0.0.4" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - stylehacks "^4.0.0" + postcss-value-parser "^4.2.0" + stylehacks "^6.0.0" -postcss-merge-rules@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" - integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== +postcss-merge-rules@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-6.0.1.tgz#39f165746404e646c0f5c510222ccde4824a86aa" + integrity sha512-a4tlmJIQo9SCjcfiCcCMg/ZCEe0XTkl/xK0XHBs955GWg9xDX3NwP9pwZ78QUOWB8/0XCjZeJn98Dae0zg6AAw== dependencies: - browserslist "^4.0.0" + browserslist "^4.21.4" caniuse-api "^3.0.0" - cssnano-util-same-parent "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" - vendors "^1.0.0" + cssnano-utils "^4.0.0" + postcss-selector-parser "^6.0.5" -postcss-minify-font-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" - integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== +postcss-minify-font-values@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-6.0.0.tgz#68d4a028f9fa5f61701974724b2cc9445d8e6070" + integrity sha512-zNRAVtyh5E8ndZEYXA4WS8ZYsAp798HiIQ1V2UF/C/munLp2r1UGHwf1+6JFu7hdEhJFN+W1WJQKBrtjhFgEnA== dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-minify-gradients@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" - integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== +postcss-minify-gradients@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-6.0.0.tgz#22b5c88cc63091dadbad34e31ff958404d51d679" + integrity sha512-wO0F6YfVAR+K1xVxF53ueZJza3L+R3E6cp0VwuXJQejnNUH0DjcAFe3JEBeTY1dLwGa0NlDWueCA1VlEfiKgAA== dependencies: - cssnano-util-get-arguments "^4.0.0" - is-color-stop "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + colord "^2.9.1" + cssnano-utils "^4.0.0" + postcss-value-parser "^4.2.0" -postcss-minify-params@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" - integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== +postcss-minify-params@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-6.0.0.tgz#2b3a85a9e3b990d7a16866f430f5fd1d5961b539" + integrity sha512-Fz/wMQDveiS0n5JPcvsMeyNXOIMrwF88n7196puSuQSWSa+/Ofc1gDOSY2xi8+A4PqB5dlYCKk/WfqKqsI+ReQ== dependencies: - alphanum-sort "^1.0.0" - browserslist "^4.0.0" - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - uniqs "^2.0.0" + browserslist "^4.21.4" + cssnano-utils "^4.0.0" + postcss-value-parser "^4.2.0" -postcss-minify-selectors@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" - integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== +postcss-minify-selectors@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-6.0.0.tgz#5046c5e8680a586e5a0cad52cc9aa36d6be5bda2" + integrity sha512-ec/q9JNCOC2CRDNnypipGfOhbYPuUkewGwLnbv6omue/PSASbHSU7s6uSQ0tcFRVv731oMIx8k0SP4ZX6be/0g== dependencies: - alphanum-sort "^1.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" + postcss-selector-parser "^6.0.5" postcss-modules-extract-imports@^3.0.0: version "3.0.0" @@ -8780,9 +9566,9 @@ postcss-modules-extract-imports@^3.0.0: integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524" + integrity sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA== dependencies: icss-utils "^5.0.0" postcss-selector-parser "^6.0.2" @@ -8802,210 +9588,140 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" -postcss-normalize-charset@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" - integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== - dependencies: - postcss "^7.0.0" +postcss-normalize-charset@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-6.0.0.tgz#36cc12457259064969fb96f84df491652a4b0975" + integrity sha512-cqundwChbu8yO/gSWkuFDmKrCZ2vJzDAocheT2JTd0sFNA4HMGoKMfbk2B+J0OmO0t5GUkiAkSM5yF2rSLUjgQ== -postcss-normalize-display-values@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" - integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== +postcss-normalize-display-values@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.0.tgz#8d2961415078644d8c6bbbdaf9a2fdd60f546cd4" + integrity sha512-Qyt5kMrvy7dJRO3OjF7zkotGfuYALETZE+4lk66sziWSPzlBEt7FrUshV6VLECkI4EN8Z863O6Nci4NXQGNzYw== dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-normalize-positions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" - integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== +postcss-normalize-positions@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-6.0.0.tgz#25b96df99a69f8925f730eaee0be74416865e301" + integrity sha512-mPCzhSV8+30FZyWhxi6UoVRYd3ZBJgTRly4hOkaSifo0H+pjDYcii/aVT4YE6QpOil15a5uiv6ftnY3rm0igPg== dependencies: - cssnano-util-get-arguments "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-normalize-repeat-style@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" - integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== +postcss-normalize-repeat-style@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.0.tgz#ddf30ad8762feb5b1eb97f39f251acd7b8353299" + integrity sha512-50W5JWEBiOOAez2AKBh4kRFm2uhrT3O1Uwdxz7k24aKtbD83vqmcVG7zoIwo6xI2FZ/HDlbrCopXhLeTpQib1A== dependencies: - cssnano-util-get-arguments "^4.0.0" - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-normalize-string@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" - integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== +postcss-normalize-string@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-6.0.0.tgz#948282647a51e409d69dde7910f0ac2ff97cb5d8" + integrity sha512-KWkIB7TrPOiqb8ZZz6homet2KWKJwIlysF5ICPZrXAylGe2hzX/HSf4NTX2rRPJMAtlRsj/yfkrWGavFuB+c0w== dependencies: - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-normalize-timing-functions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" - integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== +postcss-normalize-timing-functions@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.0.tgz#5f13e650b8c43351989fc5de694525cc2539841c" + integrity sha512-tpIXWciXBp5CiFs8sem90IWlw76FV4oi6QEWfQwyeREVwUy39VSeSqjAT7X0Qw650yAimYW5gkl2Gd871N5SQg== dependencies: - cssnano-util-get-match "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-normalize-unicode@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" - integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== +postcss-normalize-unicode@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.0.tgz#741b3310f874616bdcf07764f5503695d3604730" + integrity sha512-ui5crYkb5ubEUDugDc786L/Me+DXp2dLg3fVJbqyAl0VPkAeALyAijF2zOsnZyaS1HyfPuMH0DwyY18VMFVNkg== dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + browserslist "^4.21.4" + postcss-value-parser "^4.2.0" -postcss-normalize-url@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" - integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== +postcss-normalize-url@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-6.0.0.tgz#d0a31e962a16401fb7deb7754b397a323fb650b4" + integrity sha512-98mvh2QzIPbb02YDIrYvAg4OUzGH7s1ZgHlD3fIdTHLgPLRpv1ZTKJDnSAKr4Rt21ZQFzwhGMXxpXlfrUBKFHw== dependencies: - is-absolute-url "^2.0.0" - normalize-url "^3.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-normalize-whitespace@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" - integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== +postcss-normalize-whitespace@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.0.tgz#accb961caa42e25ca4179b60855b79b1f7129d4d" + integrity sha512-7cfE1AyLiK0+ZBG6FmLziJzqQCpTQY+8XjMhMAz8WSBSCsCNNUKujgIgjCAmDT3cJ+3zjTXFkoD15ZPsckArVw== dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" -postcss-object-fit-images@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/postcss-object-fit-images/-/postcss-object-fit-images-1.1.2.tgz#8b773043db14672ef6cd6f2cb1f0d8b26a9f573b" - integrity sha1-i3cwQ9sUZy72zW8ssfDYsmqfVzs= +postcss-ordered-values@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-6.0.0.tgz#374704cdff25560d44061d17ba3c6308837a3218" + integrity sha512-K36XzUDpvfG/nWkjs6d1hRBydeIxGpKS2+n+ywlKPzx1nMYDYpoGbcjhj5AwVYJK1qV2/SDoDEnHzlPD6s3nMg== dependencies: - parse-css-font "^2.0.2" - postcss "^5.0.16" - quote "^0.4.0" + cssnano-utils "^4.0.0" + postcss-value-parser "^4.2.0" -postcss-ordered-values@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" - integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== +postcss-reduce-initial@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz#7d16e83e60e27e2fa42f56ec0b426f1da332eca7" + integrity sha512-s2UOnidpVuXu6JiiI5U+fV2jamAw5YNA9Fdi/GRK0zLDLCfXmSGqQtzpUPtfN66RtCbb9fFHoyZdQaxOB3WxVA== dependencies: - cssnano-util-get-arguments "^4.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - -postcss-reduce-initial@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" - integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== - dependencies: - browserslist "^4.0.0" + browserslist "^4.21.4" caniuse-api "^3.0.0" - has "^1.0.0" - postcss "^7.0.0" -postcss-reduce-transforms@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" - integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== +postcss-reduce-transforms@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.0.tgz#28ff2601a6d9b96a2f039b3501526e1f4d584a46" + integrity sha512-FQ9f6xM1homnuy1wLe9lP1wujzxnwt1EwiigtWwuyf8FsqqXUDUp2Ulxf9A5yjlUOTdCJO6lonYjg1mgqIIi2w== dependencies: - cssnano-util-get-match "^4.0.0" - has "^1.0.0" - postcss "^7.0.0" - postcss-value-parser "^3.0.0" + postcss-value-parser "^4.2.0" postcss-resolve-nested-selector@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz#29ccbc7c37dedfac304e9fff0bf1596b3f6a0e4e" - integrity sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4= + integrity sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw== postcss-safe-parser@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz#bb4c29894171a94bc5c996b9a30317ef402adaa1" integrity sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ== -postcss-scss@^4.0.2, postcss-scss@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.5.tgz#8ee33c1dda8d9d4753b565ec79014803dc6edabf" - integrity sha512-F7xpB6TrXyqUh3GKdyB4Gkp3QL3DDW1+uI+gxx/oJnUt/qXI4trj5OGlp9rOKdoABGULuqtqeG+3HEVQk4DjmA== +postcss-scss@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-4.0.7.tgz#cfe5507aaff81b3d8992039ad015da4bd3dccd2f" + integrity sha512-xPv2GseoyXPa58Nro7M73ZntttusuCmZdeOojUFR5PZDz2BR62vfYx1w9TyOnp1+nYFowgOMipsCBhxzVkAEPw== -postcss-selector-parser@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" - integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== - dependencies: - dot-prop "^5.2.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - -postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.6: - version "6.0.10" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" - integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== +postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5: + version "6.0.13" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" + integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss-svgo@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.3.tgz#343a2cdbac9505d416243d496f724f38894c941e" - integrity sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw== +postcss-svgo@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-6.0.0.tgz#7b18742d38d4505a0455bbe70d52b49f00eaf69d" + integrity sha512-r9zvj/wGAoAIodn84dR/kFqwhINp5YsJkLoujybWG59grR/IHx+uQ2Zo+IcOwM0jskfYX3R0mo+1Kip1VSNcvw== dependencies: - postcss "^7.0.0" - postcss-value-parser "^3.0.0" - svgo "^1.0.0" + postcss-value-parser "^4.2.0" + svgo "^3.0.2" -postcss-unique-selectors@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" - integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== +postcss-unique-selectors@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-6.0.0.tgz#c94e9b0f7bffb1203894e42294b5a1b3fb34fbe1" + integrity sha512-EPQzpZNxOxP7777t73RQpZE5e9TrnCrkvp7AH7a0l89JmZiPnS82y216JowHXwpBCQitfyxrof9TK3rYbi7/Yw== dependencies: - alphanum-sort "^1.0.0" - postcss "^7.0.0" - uniqs "^2.0.0" + postcss-selector-parser "^6.0.5" -postcss-value-parser@^3.0.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" - integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== - -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^5.0.16: - version "5.2.18" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" - integrity sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg== +postcss@^8.2.15, postcss@^8.4.24, postcss@^8.4.25: + version "8.4.30" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.30.tgz#0e0648d551a606ef2192a26da4cabafcc09c1aa7" + integrity sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g== dependencies: - chalk "^1.1.3" - js-base64 "^2.1.9" - source-map "^0.5.6" - supports-color "^3.2.3" - -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.27, postcss@^7.0.32: - version "7.0.32" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" - integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== - dependencies: - chalk "^2.4.2" - source-map "^0.6.1" - supports-color "^6.1.0" - -postcss@^8.2.15, postcss@^8.4.17, postcss@^8.4.18: - version "8.4.18" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.18.tgz#6d50046ea7d3d66a85e0e782074e7203bc7fbca2" - integrity sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA== - dependencies: - nanoid "^3.3.4" + nanoid "^3.3.6" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -9014,16 +9730,33 @@ postgres-array@~2.0.0: resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== +postgres-array@~3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98" + integrity sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog== + postgres-bytea@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" - integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== + +postgres-bytea@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-3.0.0.tgz#9048dc461ac7ba70a6a42d109221619ecd1cb089" + integrity sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw== + dependencies: + obuf "~1.1.2" postgres-date@~1.0.4: version "1.0.7" resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== +postgres-date@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-2.0.1.tgz#638b62e5c33764c292d37b08f5257ecb09231457" + integrity sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw== + postgres-interval@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" @@ -9031,52 +9764,62 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" +postgres-interval@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-3.0.0.tgz#baf7a8b3ebab19b7f38f07566c7aab0962f0c86a" + integrity sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw== + +postgres-range@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.3.tgz#9ccd7b01ca2789eb3c2e0888b3184225fa859f76" + integrity sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" -prettier@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" - integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== +prettier@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.1.tgz#65271fc9320ce4913c57747a70ce635b30beaa40" + integrity sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ== pretty-bytes@^5.3.0, pretty-bytes@^5.4.1: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== -pretty-format@^25.2.1, pretty-format@^25.5.0: - version "25.5.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" - integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ== - dependencies: - "@jest/types" "^25.5.0" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^16.12.0" - pretty-format@^27.0.2: - version "27.0.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.0.2.tgz#9283ff8c4f581b186b2d4da461617143dca478a4" - integrity sha512-mXKbbBPnYTG7Yra9qFBtqj+IXcsvxsvOBco3QHxtxTl+hHKq6QdzMZ+q0CtL4ORHZgwGImRr2XZUX2EWzORxig== + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== dependencies: - "@jest/types" "^27.0.2" - ansi-regex "^5.0.0" + ansi-regex "^5.0.1" ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-format@^29.2.1: - version "29.2.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.2.1.tgz#86e7748fe8bbc96a6a4e04fa99172630907a9611" - integrity sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA== +pretty-format@^29.0.0, pretty-format@^29.6.3, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== dependencies: - "@jest/schemas" "^29.0.0" + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +pretty-format@^29.6.2: + version "29.6.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.3.tgz#d432bb4f1ca6f9463410c3fb25a0ba88e594ace7" + integrity sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw== + dependencies: + "@jest/schemas" "^29.6.3" ansi-styles "^5.0.0" react-is "^18.0.0" @@ -9088,42 +9831,27 @@ process-nextick-args@~2.0.0: process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +prom-client@^14.2.0: + version "14.2.0" + resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-14.2.0.tgz#ca94504e64156f6506574c25fb1c34df7812cf11" + integrity sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA== + dependencies: + tdigest "^0.1.1" promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= - -promise.prototype.finally@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.3.tgz#d3186e58fcf4df1682a150f934ccc27b7893389c" - integrity sha512-EXRF3fC9/0gz4qkt/f5EP5iW4kj9oFpBICNpCNOb/52+8nlHIX07FPLbi/q4qYBQ1xZqivMzTpNQSnArVASolQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== prompts@^2.0.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" - integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA== + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== dependencies: kleur "^3.0.3" - sisteransi "^1.0.4" - -prop-types-extra@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.1.1.tgz#58c3b74cbfbb95d304625975aa2f0848329a010b" - integrity sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew== - dependencies: - react-is "^16.3.2" - warning "^4.0.0" + sisteransi "^1.0.5" prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" @@ -9150,12 +9878,12 @@ proxy-from-env@^1.1.0: prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== psl@^1.1.33: - version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== public-encrypt@^4.0.0: version "4.0.3" @@ -9169,14 +9897,6 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -9185,34 +9905,25 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - -punycode@1.4.1, punycode@^1.2.4: +punycode@1.4.1, punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +pure-rand@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" + integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== qs@6.11.0: version "6.11.0" @@ -9221,15 +9932,17 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" +qs@^6.11.0: + version "6.11.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" + integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== + dependencies: + side-channel "^1.0.4" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA== querystringify@^2.1.1: version "2.2.0" @@ -9241,17 +9954,12 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -quick-lru@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" - integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== -quote@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/quote/-/quote-0.4.0.tgz#10839217f6c1362b89194044d29b233fd7f32f01" - integrity sha1-EIOSF/bBNiuJGUBE0psjP9fzLwE= - -raf@^3.1.0, raf@^3.4.1: +raf@^3.1.0: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== @@ -9288,15 +9996,13 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" -react-dom@^16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89" - integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw== +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" + scheduler "^0.23.0" react-event-listener@^0.6.0: version "0.6.6" @@ -9308,9 +10014,9 @@ react-event-listener@^0.6.0: warning "^4.0.1" react-fast-compare@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" - integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" + integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== react-helmet@^6.1.0: version "6.1.0" @@ -9325,7 +10031,7 @@ react-helmet@^6.1.0: react-hotkeys@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-1.1.4.tgz#a0712aa2e0c03a759fd7885808598497a4dace72" - integrity sha1-oHEqouDAOnWf14hYCFmEl6TaznI= + integrity sha512-TI7YEZXl3ChOfq9MO5LohWA809hxs4pbLAPodPdFrvunLa7uk9TlMKpftnChMlAcbiqDrOGAhnGWZCMbNnd65A== dependencies: lodash.isboolean "^3.0.3" lodash.isequal "^4.5.0" @@ -9345,42 +10051,37 @@ react-immutable-pure-component@^2.2.2: resolved "https://registry.yarnpkg.com/react-immutable-pure-component/-/react-immutable-pure-component-2.2.2.tgz#3014d3e20cd5a7a4db73b81f1f1464f4d351684b" integrity sha512-vkgoMJUDqHZfXXnjVlG3keCxSO/U6WeDQ5/Sl0GK2cH8TOxEzQ5jXqDXHEL/jqk6fsNxV05oH5kD7VNMUE2k+A== -react-intl-translations-manager@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/react-intl-translations-manager/-/react-intl-translations-manager-5.0.3.tgz#aee010ecf35975673e033ca5d7d3f4147894324d" - integrity sha512-EfBeugnOGFcdUbQyY9TqBMbuauQ8wm73ZqFr0UqCljhbXl7YDHQcVzclWFRkVmlUffzxitLQFhAZEVVeRNQSwA== +react-intl@^6.4.2: + version "6.4.7" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.4.7.tgz#28ec40350ff791a6a773f5e76b9e12835ae17e19" + integrity sha512-0hnOHAZhxTFqD1hGTxrF40qNyZJPPYiGhWIIxIz0Udz+3e3c7sdN80qlxArR+AbJ+jb5ALXZkJYH20+GPFCM0Q== dependencies: - chalk "^2.3.2" - glob "^7.1.2" - json-stable-stringify "^1.0.1" - mkdirp "^0.5.1" + "@formatjs/ecma402-abstract" "1.17.2" + "@formatjs/icu-messageformat-parser" "2.6.2" + "@formatjs/intl" "2.9.3" + "@formatjs/intl-displaynames" "6.5.2" + "@formatjs/intl-listformat" "7.4.2" + "@types/hoist-non-react-statics" "^3.3.1" + "@types/react" "16 || 17 || 18" + hoist-non-react-statics "^3.3.2" + intl-messageformat "10.5.3" + tslib "^2.4.0" -react-intl@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-2.9.0.tgz#c97c5d17d4718f1575fdbd5a769f96018a3b1843" - integrity sha512-27jnDlb/d2A7mSJwrbOBnUgD+rPep+abmoJE511Tf8BnoONIAUehy/U1zZCHGO17mnOwMWxqN4qC0nW11cD6rA== - dependencies: - hoist-non-react-statics "^3.3.0" - intl-format-cache "^2.0.5" - intl-messageformat "^2.1.0" - intl-relativeformat "^2.1.0" - invariant "^2.1.1" +"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-is@^16.12.0, react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.6: +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1, react-is@^17.0.2: +react-is@^17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: - version "18.1.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.1.0.tgz#61aaed3096d30eacf2a2127118b5b41387d32a67" - integrity sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg== - react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -9402,17 +10103,19 @@ react-notification@^6.8.5: dependencies: prop-types "^15.6.2" -react-overlays@^0.9.3: - version "0.9.3" - resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.9.3.tgz#5bac8c1e9e7e057a125181dee2d784864dd62902" - integrity sha512-u2T7nOLnK+Hrntho4p0Nxh+BsJl0bl4Xuwj/Y0a56xywLMetgAfyjnDVrudLXsNcKGaspoC+t3C1V80W9QQTdQ== +react-overlays@*, react-overlays@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-5.2.1.tgz#49dc007321adb6784e1f212403f0fb37a74ab86b" + integrity sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA== dependencies: - classnames "^2.2.5" - dom-helpers "^3.2.1" - prop-types "^15.5.10" - prop-types-extra "^1.0.1" - react-transition-group "^2.2.1" - warning "^3.0.0" + "@babel/runtime" "^7.13.8" + "@popperjs/core" "^2.11.6" + "@restart/hooks" "^0.4.7" + "@types/warning" "^3.0.0" + dom-helpers "^5.2.0" + prop-types "^15.7.2" + uncontrollable "^7.2.1" + warning "^4.0.3" react-redux-loading-bar@^5.0.4: version "5.0.4" @@ -9422,17 +10125,17 @@ react-redux-loading-bar@^5.0.4: prop-types "^15.7.2" react-lifecycles-compat "^3.0.4" -react-redux@^7.2.9: - version "7.2.9" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d" - integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ== +react-redux@^8.0.4: + version "8.1.2" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.2.tgz#9076bbc6b60f746659ad6d51cb05de9c5e1e9188" + integrity sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw== dependencies: - "@babel/runtime" "^7.15.4" - "@types/react-redux" "^7.1.20" + "@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" - loose-envify "^1.4.0" - prop-types "^15.7.2" - react-is "^17.0.2" + react-is "^18.0.0" + use-sync-external-store "^1.0.0" react-router-dom@^4.1.1: version "4.3.1" @@ -9467,10 +10170,10 @@ react-router@^4.3.1: prop-types "^15.6.1" warning "^4.0.1" -react-select@^5.5.4: - version "5.5.4" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.5.4.tgz#da05b8b66d33f6fc1f92fdccd0fa50d7f4418554" - integrity sha512-lyr19joBUm/CNJgjZMBSnFvJ/MeHCmBYvQ050qYAP3EPa7Oenlnx9guhU+SW0goYgxLQyqwRvkFllQpFAp8tmQ== +react-select@*, react-select@^5.7.3: + version "5.7.4" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.7.4.tgz#d8cad96e7bc9d6c8e2709bdda8f4363c5dd7ea7d" + integrity sha512-NhuE56X+p9QDFh4BgeygHFIvJJszO1i1KSkg/JPcIJrbovyRtI+GuOEa4XzFCEpZRAEoEI8u/cAHK+jG/PgUzQ== dependencies: "@babel/runtime" "^7.12.0" "@emotion/cache" "^11.4.0" @@ -9482,6 +10185,14 @@ react-select@^5.5.4: react-transition-group "^4.3.0" use-isomorphic-layout-effect "^1.1.2" +react-shallow-renderer@^16.15.0: + version "16.15.0" + resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz#48fb2cf9b23d23cde96708fe5273a7d3446f4457" + integrity sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA== + dependencies: + object-assign "^4.1.1" + react-is "^16.12.0 || ^17.0.0 || ^18.0.0" + react-side-effect@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.2.tgz#dc6345b9e8f9906dc2eeb68700b615e0b4fe752a" @@ -9525,22 +10236,21 @@ react-swipeable-views@^0.14.0: react-swipeable-views-utils "^0.14.0" warning "^4.0.1" -react-test-renderer@^16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae" - integrity sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg== +react-test-renderer@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-18.2.0.tgz#1dd912bd908ff26da5b9fca4fd1c489b9523d37e" + integrity sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA== dependencies: - object-assign "^4.1.1" - prop-types "^15.6.2" - react-is "^16.8.6" - scheduler "^0.19.1" + react-is "^18.2.0" + react-shallow-renderer "^16.15.0" + scheduler "^0.23.0" -react-textarea-autosize@^8.3.4: - version "8.3.4" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.4.tgz#270a343de7ad350534141b02c9cb78903e553524" - integrity sha512-CdtmP8Dc19xL8/R6sWvtknD/eCXkQr30dtvC4VmGInhRsfF8X/ihXCq6+9l9qbxmKRiq407/7z5fxE7cVWQNgQ== +react-textarea-autosize@*, react-textarea-autosize@^8.4.1: + version "8.5.3" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz#d1e9fe760178413891484847d3378706052dd409" + integrity sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ== dependencies: - "@babel/runtime" "^7.10.2" + "@babel/runtime" "^7.20.13" use-composed-ref "^1.3.0" use-latest "^1.2.1" @@ -9551,58 +10261,46 @@ react-toggle@^4.1.3: dependencies: classnames "^2.2.5" -react-transition-group@^2.2.1: - version "2.9.0" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" - integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg== - dependencies: - dom-helpers "^3.4.0" - loose-envify "^1.4.0" - prop-types "^15.6.2" - react-lifecycles-compat "^3.0.4" - react-transition-group@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683" - integrity sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw== + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== dependencies: "@babel/runtime" "^7.5.5" dom-helpers "^5.0.1" loose-envify "^1.4.0" prop-types "^15.6.2" -react@^16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" - integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== +read-pkg-up@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-8.0.0.tgz#72f595b65e66110f43b052dd9af4de6b10534670" + integrity sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ== dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" + find-up "^5.0.0" + read-pkg "^6.0.0" + type-fest "^1.0.1" -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== +read-pkg@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-6.0.0.tgz#a67a7d6a1c2b0c3cd6aa2ea521f40c458a4a504c" + integrity sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q== dependencies: "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" + normalize-package-data "^3.0.2" + parse-json "^5.2.0" + type-fest "^1.0.1" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.3.3, readable-stream@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -9612,19 +10310,19 @@ read-pkg@^5.2.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.0, readable-stream@^3.0.6, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== +readable-stream@^3.0.6, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" util-deprecate "^1.0.1" readable-stream@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.2.0.tgz#a7ef523d3b39e4962b0db1a1af22777b10eeca46" - integrity sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A== + version "4.4.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.4.0.tgz#55ce132d60a988c460d75c631e9ccf6a7229b468" + integrity sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg== dependencies: abort-controller "^3.0.0" buffer "^6.0.3" @@ -9640,10 +10338,10 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" - integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" @@ -9655,51 +10353,59 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -redis-errors@^1.0.0: +redent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-4.0.0.tgz#0c0ba7caabb24257ab3bb7a4fd95dd1d5c5681f9" + integrity sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag== + dependencies: + indent-string "^5.0.0" + strip-indent "^4.0.0" + +redis-errors@^1.0.0, redis-errors@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" - integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== -redis-parser@3.0.0: +redis-parser@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" - integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== dependencies: redis-errors "^1.0.0" -"redis@^4.0.6 <4.1.0": - version "4.0.6" - resolved "https://registry.yarnpkg.com/redis/-/redis-4.0.6.tgz#a2ded4d9f4f4bad148e54781051618fc684cd858" - integrity sha512-IaPAxgF5dV0jx+A9l6yd6R9/PAChZIoAskDVRzUODeLDNhsMlq7OLLTmu0AwAr0xjrJ1bibW5xdpRwqIQ8Q0Xg== - dependencies: - "@node-redis/bloom" "1.0.1" - "@node-redis/client" "1.0.5" - "@node-redis/graph" "1.0.0" - "@node-redis/json" "1.0.2" - "@node-redis/search" "1.0.5" - "@node-redis/time-series" "1.0.2" - redux-immutable@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/redux-immutable/-/redux-immutable-4.0.0.tgz#3a1a32df66366462b63691f0e1dc35e472bbc9f3" - integrity sha1-Ohoy32Y2ZGK2NpHw4dw15HK7yfM= + integrity sha512-SchSn/DWfGb3oAejd+1hhHx01xUoxY+V7TeK0BKqpkLKiQPVFf7DYzEaKmrEVxsWxielKfSK9/Xq66YyxgR1cg== -redux-thunk@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714" - integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== +redux-thunk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b" + integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q== -redux@^4.0.0, redux@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13" - integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA== +redux@^4.0.0, redux@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== dependencies: "@babel/runtime" "^7.9.2" -regenerate-unicode-properties@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" - integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== +reflect.getprototypeof@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.3.tgz#2738fd896fcc3477ffbd4190b40c2458026b6928" + integrity sha512-TTAOZpkJ2YLxl7mVHWrNo3iDMEkYlva/kgFcXndqMgbo/AZUmmavEkdXV+hXtE4P8xdyEKRzalaFqZVuwIk/Nw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + get-intrinsic "^1.1.1" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" + +regenerate-unicode-properties@^10.1.0: + version "10.1.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" + integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== dependencies: regenerate "^1.4.2" @@ -9713,15 +10419,20 @@ regenerator-runtime@^0.12.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== -regenerator-runtime@^0.13.10, regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4: - version "0.13.10" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee" - integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw== +regenerator-runtime@^0.13.3: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== -regenerator-transform@^0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" - integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + +regenerator-transform@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" + integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== dependencies: "@babel/runtime" "^7.8.4" @@ -9733,76 +10444,58 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" - integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.3, regexp.prototype.flags@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" + integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" + define-properties "^1.2.0" + functions-have-names "^1.2.3" -regexpp@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" - integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== - -regexpu-core@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.1.0.tgz#2f8504c3fd0ebe11215783a41541e21c79942c6d" - integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA== +regexpu-core@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== dependencies: + "@babel/regjsgen" "^0.8.0" regenerate "^1.4.2" - regenerate-unicode-properties "^10.0.1" - regjsgen "^0.6.0" - regjsparser "^0.8.2" + regenerate-unicode-properties "^10.1.0" + regjsparser "^0.9.1" unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" -regjsgen@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" - integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== - -regjsparser@^0.8.2: - version "0.8.4" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" - integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== dependencies: jsesc "~0.5.0" remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + version "1.1.4" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" + integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== requestidlecallback@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/requestidlecallback/-/requestidlecallback-0.3.0.tgz#6fb74e0733f90df3faa4838f9f6a2a5f9b742ac5" - integrity sha1-b7dOBzP5DfP6pIOPn2oqX5t0KsU= + integrity sha512-TWHFkT7S9p7IxLC5A1hYmAYQx2Eb9w1skrXmQ+dS1URyvR8tenMLl4lHbqEOUnpEYxNKpkVMXUgknVpBZWXXfQ== require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== require-from-string@^2.0.2: version "2.0.2" @@ -9817,22 +10510,22 @@ require-main-filename@^2.0.0: require-package-name@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/require-package-name/-/require-package-name-2.0.1.tgz#c11e97276b65b8e2923f75dabf5fb2ef0c3841b9" - integrity sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk= + integrity sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q== requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== -reselect@^4.1.6: - version "4.1.6" - resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.6.tgz#19ca2d3d0b35373a74dc1c98692cdaffb6602656" - integrity sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ== +reselect@^4.1.8: + version "4.1.8" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" + integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= + integrity sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg== dependencies: resolve-from "^3.0.0" @@ -9846,7 +10539,7 @@ resolve-cwd@^3.0.0: resolve-dir@^1.0.0, resolve-dir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= + integrity sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg== dependencies: expand-tilde "^2.0.0" global-modules "^1.0.0" @@ -9854,7 +10547,7 @@ resolve-dir@^1.0.0, resolve-dir@^1.0.1: resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= + integrity sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw== resolve-from@^4.0.0: version "4.0.0" @@ -9866,42 +10559,69 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-pathname@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" - integrity sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg== - resolve-pathname@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== -resolve.exports@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" - integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== -resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0: - version "1.22.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" - integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== +resolve@^1.14.2: + version "1.22.6" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" + integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== dependencies: - is-core-module "^2.8.1" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^2.0.0-next.3: - version "2.0.0-next.3" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" - integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q== +resolve@^1.19.0: + version "1.22.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" + is-core-module "^2.11.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^1.20.0, resolve@^1.22.4: + version "1.22.4" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" + integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.4: + version "2.0.0-next.4" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660" + integrity sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" ret@~0.1.10: version "0.1.15" @@ -9911,24 +10631,19 @@ ret@~0.1.10: retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rgb-regex@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" - integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== -rgba-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" - integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= - -rimraf@^2.5.4, rimraf@^2.6.3: +rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -9942,6 +10657,13 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" +rimraf@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.1.tgz#0881323ab94ad45fec7c0221f27ea1a142f3f0d0" + integrity sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg== + dependencies: + glob "^10.2.5" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -9961,12 +10683,24 @@ rollup-plugin-terser@^7.0.0: terser "^5.0.0" rollup@^2.43.1: - version "2.72.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.72.1.tgz#861c94790537b10008f0ca0fbc60e631aabdd045" - integrity sha512-NTc5UGy/NWFGpSqF1lFY8z9Adri6uhyMLI6LvPAXdBKoPRFhIIiBUpt+Qg2awixqO3xvzSijjhnb4+QEZwJmxA== + version "2.79.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" + integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== optionalDependencies: fsevents "~2.3.2" +rrweb-cssom@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" + integrity sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw== + +run-applescript@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" + integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== + dependencies: + execa "^5.0.0" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -9974,12 +10708,22 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -run-queue@^1.0.0, run-queue@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" - integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= +rxjs@^7.8.0: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== dependencies: - aproba "^1.1.1" + tslib "^2.1.0" + +safe-array-concat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.0.tgz#2064223cba3c08d2ee05148eedbc563cd6d84060" + integrity sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + has-symbols "^1.0.3" + isarray "^2.0.5" safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" @@ -9991,10 +10735,19 @@ safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== dependencies: ret "~0.1.10" @@ -10004,9 +10757,9 @@ safe-regex@^1.1.0: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sass-loader@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.2.0.tgz#3d64c1590f911013b3fa48a0b22a83d5e1494716" - integrity sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw== + version "10.4.1" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.4.1.tgz#bea4e173ddf512c9d7f53e9ec686186146807cbf" + integrity sha512-aX/iJZTTpNUNx/OSYzo2KsjIUQHqvWsAhhUijFjAPdZTEhstjZI9zTNvkTTwsx+uNUJqUwOw5gacxQMx4hJxGQ== dependencies: klona "^2.0.4" loader-utils "^2.0.0" @@ -10014,10 +10767,10 @@ sass-loader@^10.2.0: schema-utils "^3.0.0" semver "^7.3.2" -sass@^1.55.0: - version "1.55.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.55.0.tgz#0c4d3c293cfe8f8a2e8d3b666e1cf1bff8065d1c" - integrity sha512-Pk+PMy7OGLs9WaxZGJMn7S96dvlyVBwwtToX895WmCpAOr5YiJYEUJfiJidMuKb613z2xNWcXCHEuOvjZbqC6A== +sass@^1.62.1: + version "1.67.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.67.0.tgz#fed84d74b9cd708db603b1380d6dc1f71bb24f6f" + integrity sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -10035,13 +10788,12 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" schema-utils@^1.0.0: version "1.0.0" @@ -10052,7 +10804,7 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -schema-utils@^2.2.0, schema-utils@^2.6.5: +schema-utils@^2.6.5: version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== @@ -10062,11 +10814,11 @@ schema-utils@^2.2.0, schema-utils@^2.6.5: ajv-keywords "^3.5.2" schema-utils@^3.0, schema-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" - integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== dependencies: - "@types/json-schema" "^7.0.6" + "@types/json-schema" "^7.0.8" ajv "^6.12.5" ajv-keywords "^3.5.2" @@ -10081,29 +10833,36 @@ scroll-behavior@^0.9.1: select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= + integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== selfsigned@^1.10.8: - version "1.10.8" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" - integrity sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w== + version "1.10.14" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.14.tgz#ee51d84d9dcecc61e07e4aba34f229ab525c1574" + integrity sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA== dependencies: node-forge "^0.10.0" -"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: +semver@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: - version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== +semver@^7.3.2: + version "7.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" + integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== + dependencies: + lru-cache "^6.0.0" + +semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" @@ -10126,11 +10885,6 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" -serialize-javascript@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" - integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== - serialize-javascript@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" @@ -10148,7 +10902,7 @@ serialize-javascript@^5.0.1: serve-index@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= + integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== dependencies: accepts "~1.3.4" batch "0.6.1" @@ -10171,7 +10925,7 @@ serve-static@1.15.0: set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" @@ -10186,7 +10940,7 @@ set-value@^2.0.0, set-value@^2.0.1: setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== setprototypeof@1.1.0: version "1.1.0" @@ -10221,7 +10975,7 @@ shallow-equal@^1.2.1: shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== dependencies: shebang-regex "^1.0.0" @@ -10235,7 +10989,7 @@ shebang-command@^2.0.0: shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== shebang-regex@^3.0.0: version "3.0.0" @@ -10251,33 +11005,26 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== - -signal-exit@^3.0.7: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= - dependencies: - is-arrayish "^0.3.1" +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== -sirv@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.10.tgz#3e591f5a9ae2520f50d5830f5fae38d97e7be194" - integrity sha512-H5EZCoZaggEUQy8ocKsF7WAToGuZhjJlLvM3XOef46CbdIgbNeQ1p32N1PCuCjkVYwrAVOSMacN6CXXgIzuspg== +sirv@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446" + integrity sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA== dependencies: - "@polka/url" "^1.0.0-next.9" - mime "^2.3.1" - totalist "^1.0.0" + "@polka/url" "^1.0.0-next.20" + mrmime "^1.0.0" + totalist "^3.0.0" -sisteransi@^1.0.4: +sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== @@ -10287,6 +11034,15 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" @@ -10296,6 +11052,14 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" +slice-ansi@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" + integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + dependencies: + ansi-styles "^6.0.0" + is-fullwidth-code-point "^4.0.0" + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -10327,24 +11091,23 @@ snapdragon@^0.8.1: use "^3.1.0" sockjs-client@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.5.0.tgz#2f8ff5d4b659e0d092f7aba0b7c386bd2aa20add" - integrity sha512-8Dt3BDi4FYNrCFGTL/HtwVzkARrENdwOUf1ZoW/9p3M8lZdFT35jVdrHza+qgxuG9H3/shR4cuX/X9umUrjP8Q== + version "1.6.1" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.6.1.tgz#350b8eda42d6d52ddc030c39943364c11dcad806" + integrity sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw== dependencies: - debug "^3.2.6" - eventsource "^1.0.7" - faye-websocket "^0.11.3" + debug "^3.2.7" + eventsource "^2.0.2" + faye-websocket "^0.11.4" inherits "^2.0.4" - json3 "^3.3.3" - url-parse "^1.4.7" + url-parse "^1.5.10" sockjs@^0.3.21: - version "0.3.21" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.21.tgz#b34ffb98e796930b60a0cfa11904d6a339a7d417" - integrity sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw== + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== dependencies: faye-websocket "^0.11.3" - uuid "^3.4.0" + uuid "^8.3.2" websocket-driver "^0.7.4" source-list-map@^2.0.0: @@ -10352,7 +11115,7 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== @@ -10376,7 +11139,7 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-support@~0.5.12, source-map-support@~0.5.19, source-map-support@~0.5.20: +source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -10385,46 +11148,46 @@ source-map-support@~0.5.12, source-map-support@~0.5.19, source-map-support@~0.5. source-map "^0.6.0" source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + version "0.4.1" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== source-map@0.5.6: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" - integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= + integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA== source-map@^0.5.6, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.8.0-beta.0, source-map@~0.8.0-beta.0: +source-map@^0.7.3: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +source-map@^0.8.0-beta.0: version "0.8.0-beta.0" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== dependencies: whatwg-url "^7.0.0" -source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - sourcemap-codec@^1.4.8: version "1.4.8" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" @@ -10434,7 +11197,7 @@ spdx-exceptions@^2.1.0: resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== -spdx-expression-parse@^3.0.0: +spdx-expression-parse@^3.0.0, spdx-expression-parse@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== @@ -10443,9 +11206,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.11" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" - integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== + version "3.0.13" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5" + integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w== spdy-transport@^3.0.0: version "3.0.0" @@ -10477,29 +11240,20 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" -split2@^3.1.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" - integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== - dependencies: - readable-stream "^3.0.0" +split2@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -ssri@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" - integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== - dependencies: - figgy-pudding "^3.5.1" - -ssri@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.0.tgz#79ca74e21f8ceaeddfcb4b90143c458b8d988808" - integrity sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA== +ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== dependencies: minipass "^3.1.1" @@ -10509,31 +11263,31 @@ stable@^0.1.8: integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== stack-generator@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.5.tgz#fb00e5b4ee97de603e0773ea78ce944d81596c36" - integrity sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q== + version "2.0.10" + resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.10.tgz#8ae171e985ed62287d4f1ed55a1633b3fb53bb4d" + integrity sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ== dependencies: - stackframe "^1.1.1" + stackframe "^1.3.4" stack-utils@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" - integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: escape-string-regexp "^2.0.0" -stackframe@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" - integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA== +stackframe@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" + integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== stacktrace-gps@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz#7688dc2fc09ffb3a13165ebe0dbcaf41bcf0c69a" - integrity sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg== + version "3.1.2" + resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz#0c40b24a9b119b20da4525c398795338966a2fb0" + integrity sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ== dependencies: source-map "0.5.6" - stackframe "^1.1.1" + stackframe "^1.3.4" stacktrace-js@^2.0.2: version "2.0.2" @@ -10544,10 +11298,15 @@ stacktrace-js@^2.0.2: stack-generator "^2.0.5" stacktrace-gps "^3.0.4" +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== dependencies: define-property "^0.2.5" object-copy "^0.1.0" @@ -10560,7 +11319,14 @@ statuses@2.0.1: "statuses@>= 1.4.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +stop-iteration-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" + integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + dependencies: + internal-slot "^1.0.4" stream-browserify@^2.0.1: version "2.0.2" @@ -10570,14 +11336,6 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" -stream-each@^1.1.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" - integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== - dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" - stream-http@^2.7.2: version "2.8.3" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" @@ -10589,20 +11347,21 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== +string-argv@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" + integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== string-length@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1" - integrity sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw== + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== dependencies: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + name string-width-cjs version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10620,37 +11379,55 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string.prototype.matchall@^4.0.6, string.prototype.matchall@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d" - integrity sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg== +string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string.prototype.matchall@^4.0.6, string.prototype.matchall@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz#3bf85722021816dcd1bf38bb714915887ca79fd3" + integrity sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" - get-intrinsic "^1.1.1" + define-properties "^1.1.4" + es-abstract "^1.20.4" + get-intrinsic "^1.1.3" has-symbols "^1.0.3" internal-slot "^1.0.3" - regexp.prototype.flags "^1.4.1" + regexp.prototype.flags "^1.4.3" side-channel "^1.0.4" -string.prototype.trimend@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" - integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== +string.prototype.trim@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" + integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" + es-abstract "^1.20.4" -string.prototype.trimstart@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" - integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== +string.prototype.trimend@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" + integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" + es-abstract "^1.20.4" + +string.prototype.trimstart@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" + integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" @@ -10682,10 +11459,17 @@ stringz@^2.1.0: dependencies: char-regex "^1.0.2" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== dependencies: ansi-regex "^2.0.0" @@ -10696,17 +11480,17 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== dependencies: - ansi-regex "^5.0.1" + ansi-regex "^6.0.1" strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== strip-bom@^4.0.0: version "4.0.0" @@ -10721,13 +11505,18 @@ strip-comments@^2.0.1: strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -10735,7 +11524,14 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: +strip-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.0.0.tgz#b41379433dd06f5eae805e21d631e07ee670d853" + integrity sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA== + dependencies: + min-indent "^1.0.1" + +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -10743,122 +11539,110 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: style-search@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" - integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI= + integrity sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg== -stylehacks@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" - integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== +stylehacks@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-6.0.0.tgz#9fdd7c217660dae0f62e14d51c89f6c01b3cb738" + integrity sha512-+UT589qhHPwz6mTlCLSt/vMNTJx8dopeJlZAlBMJPWA3ORqu6wmQY7FBXf+qD+FsqoBJODyqNxOUP3jdntFRdw== dependencies: - browserslist "^4.0.0" - postcss "^7.0.0" - postcss-selector-parser "^3.0.0" + browserslist "^4.21.4" + postcss-selector-parser "^6.0.4" -stylelint-config-recommended-scss@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-7.0.0.tgz#db16b6ae6055e72e3398916c0f13d6eb685902a2" - integrity sha512-rGz1J4rMAyJkvoJW4hZasuQBB7y9KIrShb20l9DVEKKZSEi1HAy0vuNlR8HyCKy/jveb/BdaQFcoiYnmx4HoiA== +stylelint-config-recommended-scss@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-13.0.0.tgz#dd8c319e15a6412262cd8554e4aad9bfba1bbb11" + integrity sha512-7AmMIsHTsuwUQm7I+DD5BGeIgCvqYZ4BpeYJJpb1cUXQwrJAKjA+GBotFZgUEGP8lAM+wmd91ovzOi8xfAyWEw== dependencies: - postcss-scss "^4.0.2" - stylelint-config-recommended "^8.0.0" - stylelint-scss "^4.0.0" + postcss-scss "^4.0.7" + stylelint-config-recommended "^13.0.0" + stylelint-scss "^5.1.0" -stylelint-config-recommended@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-8.0.0.tgz#7736be9984246177f017c39ec7b1cd0f19ae9117" - integrity sha512-IK6dWvE000+xBv9jbnHOnBq01gt6HGVB2ZTsot+QsMpe82doDQ9hvplxfv4YnpEuUwVGGd9y6nbaAnhrjcxhZQ== +stylelint-config-recommended@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-13.0.0.tgz#c48a358cc46b629ea01f22db60b351f703e00597" + integrity sha512-EH+yRj6h3GAe/fRiyaoO2F9l9Tgg50AOFhaszyfov9v6ayXJ1IkSHwTxd7lB48FmOeSGDPLjatjO11fJpmarkQ== -stylelint-config-standard-scss@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-standard-scss/-/stylelint-config-standard-scss-5.0.0.tgz#afc5e43c73e7a15875b8f30f54204b01a2634743" - integrity sha512-zoXLibojHZYPFjtkc4STZtAJ2yGTq3Bb4MYO0oiyO6f/vNxDKRcSDZYoqN260Gv2eD5niQIr1/kr5SXlFj9kcQ== +stylelint-config-standard-scss@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-standard-scss/-/stylelint-config-standard-scss-11.0.0.tgz#98332b68a9c98b6fce54c7698741e103719942b5" + integrity sha512-fGE79NBOLg09a9afqGH/guJulRULCaQWWv4cv1v2bMX92B+fGb0y56WqIguwvFcliPmmUXiAhKrrnXilIeXoHA== dependencies: - stylelint-config-recommended-scss "^7.0.0" - stylelint-config-standard "^26.0.0" + stylelint-config-recommended-scss "^13.0.0" + stylelint-config-standard "^34.0.0" -stylelint-config-standard@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-26.0.0.tgz#4701b8d582d34120eec7d260ba779e4c2d953635" - integrity sha512-hUuB7LaaqM8abvkOO84wh5oYSkpXgTzHu2Zza6e7mY+aOmpNTjoFBRxSLlzY0uAOMWEFx0OMKzr+reG1BUtcqQ== +stylelint-config-standard@^34.0.0: + version "34.0.0" + resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-34.0.0.tgz#309f3c48118a02aae262230c174282e40e766cf4" + integrity sha512-u0VSZnVyW9VSryBG2LSO+OQTjN7zF9XJaAJRX/4EwkmU0R2jYwmBSN10acqZisDitS0CLiEiGjX7+Hrq8TAhfQ== dependencies: - stylelint-config-recommended "^8.0.0" + stylelint-config-recommended "^13.0.0" -stylelint-scss@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.2.0.tgz#e25fd390ee38a7e89fcfaec2a8f9dce2ec6ddee8" - integrity sha512-HHHMVKJJ5RM9pPIbgJ/XA67h9H0407G68Rm69H4fzFbFkyDMcTV1Byep3qdze5+fJ3c0U7mJrbj6S0Fg072uZA== +stylelint-scss@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-5.1.0.tgz#dd318bc5c65f7a11f3ecacc7b6e8b67e7f2f1df1" + integrity sha512-E+KlQFXv1Euha43qw3q+wKBSli557wxbbo6/39DWhRNXlUa9Cz+FYrcgz+PT6ag0l6UisCYjAGCNhoSl4FcwlA== dependencies: - lodash "^4.17.21" postcss-media-query-parser "^0.2.3" postcss-resolve-nested-selector "^0.1.1" - postcss-selector-parser "^6.0.6" - postcss-value-parser "^4.1.0" + postcss-selector-parser "^6.0.13" + postcss-value-parser "^4.2.0" -stylelint@^14.14.0: - version "14.14.0" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-14.14.0.tgz#1acb52497c9a921f23f9c4014d4e0ee6eba768d0" - integrity sha512-yUI+4xXfPHVnueYddSQ/e1GuEA/2wVhWQbGj16AmWLtQJtn28lVxfS4b0CsWyVRPgd3Auzi0NXOthIEUhtQmmA== +stylelint@^15.10.1: + version "15.10.2" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.10.2.tgz#0ee5a8371d3a2e1ff27fefd48309d3ddef7c3405" + integrity sha512-UxqSb3hB74g4DTO45QhUHkJMjKKU//lNUAOWyvPBVPZbCknJ5HjOWWZo+UDuhHa9FLeVdHBZXxu43eXkjyIPWg== dependencies: - "@csstools/selector-specificity" "^2.0.2" + "@csstools/css-parser-algorithms" "^2.3.0" + "@csstools/css-tokenizer" "^2.1.1" + "@csstools/media-query-list-parser" "^2.1.2" + "@csstools/selector-specificity" "^3.0.0" balanced-match "^2.0.0" colord "^2.9.3" - cosmiconfig "^7.0.1" - css-functions-list "^3.1.0" + cosmiconfig "^8.2.0" + css-functions-list "^3.2.0" + css-tree "^2.3.1" debug "^4.3.4" - fast-glob "^3.2.12" + fast-glob "^3.3.0" fastest-levenshtein "^1.0.16" file-entry-cache "^6.0.1" global-modules "^2.0.0" globby "^11.1.0" globjoin "^0.1.4" - html-tags "^3.2.0" - ignore "^5.2.0" + html-tags "^3.3.1" + ignore "^5.2.4" import-lazy "^4.0.0" imurmurhash "^0.1.4" is-plain-object "^5.0.0" - known-css-properties "^0.25.0" + known-css-properties "^0.27.0" mathml-tag-names "^2.1.3" - meow "^9.0.0" + meow "^10.1.5" micromatch "^4.0.5" normalize-path "^3.0.0" picocolors "^1.0.0" - postcss "^8.4.17" - postcss-media-query-parser "^0.2.3" + postcss "^8.4.25" postcss-resolve-nested-selector "^0.1.1" postcss-safe-parser "^6.0.0" - postcss-selector-parser "^6.0.10" + postcss-selector-parser "^6.0.13" postcss-value-parser "^4.2.0" resolve-from "^5.0.0" string-width "^4.2.3" strip-ansi "^6.0.1" style-search "^0.1.0" - supports-hyperlinks "^2.3.0" + supports-hyperlinks "^3.0.0" svg-tags "^1.0.0" - table "^6.8.0" - v8-compile-cache "^2.3.0" - write-file-atomic "^4.0.2" + table "^6.8.1" + write-file-atomic "^5.0.1" -stylis@4.0.13: - version "4.0.13" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" - integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag== +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== substring-trie@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/substring-trie/-/substring-trie-1.0.2.tgz#7b42592391628b4f2cb17365c6cce4257c7b7af5" - integrity sha1-e0JZI5Fii08ssXNlxszkJXx7evU= - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -supports-color@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" - integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= - dependencies: - has-flag "^1.0.0" + integrity sha512-klL9Z/pqyq1JZE1kAa9hxI3i0J2f6XQonARUOEO3qwdtI9I46qVFX10gwdfQBCkM2yKIKj29eZFtufTN4D6GLg== supports-color@^5.3.0: version "5.5.0" @@ -10888,10 +11672,10 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" -supports-hyperlinks@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" - integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== +supports-hyperlinks@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz#c711352a5c89070779b4dad54c05a2f14b15c94b" + integrity sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA== dependencies: has-flag "^4.0.0" supports-color "^7.0.0" @@ -10901,12 +11685,17 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +svg-parser@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + svg-tags@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" - integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q= + integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA== -svgo@^1.0.0: +svgo@^1.2.2: version "1.3.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== @@ -10925,15 +11714,35 @@ svgo@^1.0.0: unquote "~1.1.1" util.promisify "~1.0.0" +svgo@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.0.2.tgz#5e99eeea42c68ee0dc46aa16da093838c262fe0a" + integrity sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^5.1.0" + css-tree "^2.2.1" + csso "^5.0.5" + picocolors "^1.0.0" + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -table@^6.0.9, table@^6.8.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca" - integrity sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA== +synckit@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3" + integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q== + dependencies: + "@pkgr/utils" "^2.3.1" + tslib "^2.5.0" + +table@^6.8.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" + integrity sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA== dependencies: ajv "^8.0.1" lodash.truncate "^4.4.2" @@ -10946,22 +11755,29 @@ tapable@^1.0, tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== +tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + tar@^6.0.2: - version "6.1.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" - integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + version "6.2.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" + integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" - minipass "^3.0.0" + minipass "^5.0.0" minizlib "^2.1.1" mkdirp "^1.0.3" yallist "^4.0.0" -tcomb@^2.5.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0" - integrity sha1-ENYpWAQWaaXVNWe5pO6M3iKxwrA= +tdigest@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.2.tgz#96c64bac4ff10746b910b0e23b515794e12faced" + integrity sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA== + dependencies: + bintrees "1.0.2" temp-dir@^2.0.0: version "2.0.0" @@ -10978,22 +11794,7 @@ tempy@^0.6.0: type-fest "^0.16.0" unique-string "^2.0.0" -terser-webpack-plugin@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" - integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA== - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^2.1.2" - source-map "^0.6.1" - terser "^4.1.2" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" - -terser-webpack-plugin@^4.2.3: +terser-webpack-plugin@^1.4.3, terser-webpack-plugin@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz#28daef4a83bd17c1db0297070adc07fc8cfc6a9a" integrity sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ== @@ -11008,49 +11809,43 @@ terser-webpack-plugin@^4.2.3: terser "^5.3.4" webpack-sources "^1.4.3" -terser@^4.1.2: - version "4.8.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.1.tgz#a00e5634562de2239fd404c649051bf6fc21144f" - integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - terser@^5.0.0: - version "5.13.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.13.1.tgz#66332cdc5a01b04a224c9fad449fc1a18eaa1799" - integrity sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA== + version "5.18.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.18.0.tgz#dc811fb8e3481a875d545bda247c8730ee4dc76b" + integrity sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA== dependencies: - acorn "^8.5.0" + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" commander "^2.20.0" - source-map "~0.8.0-beta.0" source-map-support "~0.5.20" terser@^5.3.4: - version "5.3.4" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.4.tgz#e510e05f86e0bd87f01835c3238839193f77a60c" - integrity sha512-dxuB8KQo8Gt6OVOeLg/rxfcxdNZI/V1G6ze1czFUzPeCFWZRtvZMgSzlZZ5OYBZ4HoG607F6pFPNLekJyV+yVw== + version "5.19.4" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.4.tgz#941426fa482bf9b40a0308ab2b3cd0cf7c775ebd" + integrity sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g== dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" commander "^2.20.0" - source-map "~0.7.2" - source-map-support "~0.5.19" + source-map-support "~0.5.20" tesseract.js-core@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tesseract.js-core/-/tesseract.js-core-2.2.0.tgz#6ef78051272a381969fac3e45a226e85022cffef" integrity sha512-a8L+OJTbUipBsEDsJhDPlnLB0TY1MkTZqw5dqUwmiDSjUzwvU7HWLg/2+WDRulKUi4LE+7PnHlaBlW0k+V0U0w== -tesseract.js@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tesseract.js/-/tesseract.js-2.1.1.tgz#5c50fc95542ce8d834cb952bfb75a8fc85f1441d" - integrity sha512-utg0A8UzT1KwBvZf+UMGmM8LU6izeol6yIem0Z44+7Qqd/YWgRVQ99XOG18ApTOXX48lGE++PDwlcZYkv0ygRQ== +tesseract.js@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/tesseract.js/-/tesseract.js-2.1.5.tgz#2f757ff059f249721096fe9f94029c349650902c" + integrity sha512-7CIS3SWr7TXpeaH9+HS7iUtVbCfPFYOO3p6rkRAkdtsOtrbz6496x59na6SCbFAIaZulQxy8BjwSu3qL3AoDRg== dependencies: + blueimp-load-image "^3.0.0" bmp-js "^0.1.0" file-type "^12.4.1" idb-keyval "^3.2.0" is-electron "^2.2.0" is-url "^1.2.4" + jpeg-autorotate "^7.1.1" node-fetch "^2.6.0" opencollective-postinstall "^2.0.2" regenerator-runtime "^0.13.3" @@ -11070,22 +11865,12 @@ test-exclude@^6.0.0: text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -throng@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/throng/-/throng-4.0.0.tgz#983c6ba1993b58eae859998aa687ffe88df84c17" - integrity sha1-mDxroZk7WOroWZmKpof/6I34TBc= - dependencies: - lodash.defaults "^4.0.1" - -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== thunky@^1.0.2: version "1.1.0" @@ -11093,32 +11878,32 @@ thunky@^1.0.2: integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== timers-browserify@^2.0.4: - version "2.0.11" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" - integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== + version "2.0.12" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" + integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== dependencies: setimmediate "^1.0.4" -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" - integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= - tiny-invariant@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" - integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + version "1.3.1" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" + integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== tiny-queue@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tiny-queue/-/tiny-queue-0.2.1.tgz#25a67f2c6e253b2ca941977b5ef7442ef97a6046" - integrity sha1-JaZ/LG4lOyypQZd7XvdELvl6YEY= + integrity sha512-EijGsv7kzd9I9g0ByCl6h42BWNGUZrlCSejfrb3AKeHC33SGbASu1VDf5O3rRiiUOhAC9CHdZxFPbZu0HmR70A== tiny-warning@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== +titleize@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" + integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -11127,24 +11912,24 @@ tmpl@1.0.5: to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= + integrity sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA== to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== dependencies: kind-of "^3.0.2" to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== dependencies: is-number "^3.0.0" repeat-string "^1.6.1" @@ -11171,15 +11956,15 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -totalist@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" - integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== +totalist@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" + integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== tough-cookie@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" - integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== + version "4.1.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" + integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== dependencies: psl "^1.1.33" punycode "^2.1.1" @@ -11189,7 +11974,7 @@ tough-cookie@^4.1.2: tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" - integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= + integrity sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA== dependencies: punycode "^2.1.0" @@ -11200,35 +11985,74 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" +tr46@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" + integrity sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw== + dependencies: + punycode "^2.3.0" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -trim-newlines@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" - integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== +trim-newlines@^4.0.2: + version "4.1.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-4.1.1.tgz#28c88deb50ed10c7ba6dc2474421904a00139125" + integrity sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ== -tsconfig-paths@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" - integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== +ts-api-utils@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" + integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== + +tsconfig-paths@^3.14.2: + version "3.14.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" + integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== dependencies: "@types/json5" "^0.0.29" - json5 "^1.0.1" + json5 "^1.0.2" minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.9.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" - integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== +tslib@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" + integrity sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA== + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.1.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410" + integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig== + +tslib@^2.4.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +tslib@^2.5.0: + version "2.5.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.3.tgz#24944ba2d990940e6e982c4bea147aba80209913" + integrity sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= + integrity sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw== twemoji-parser@^11.0.2: version "11.0.2" @@ -11252,47 +12076,30 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= - dependencies: - prelude-ls "~1.1.2" - type-detect@4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" - integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== - type-fest@^0.16.0: version "0.16.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== -type-fest@^0.18.0: - version "0.18.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" - integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== - type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^1.0.1, type-fest@^1.2.1, type-fest@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" + integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== type-is@~1.6.18: version "1.6.18" @@ -11302,20 +12109,49 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -type@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== +typed-array-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" + integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-typed-array "^1.1.10" -type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" - integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" + +"typescript@^4.7 || 5", typescript@^5.0.4: + version "5.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" + integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== unbox-primitive@^1.0.2: version "1.0.2" @@ -11327,11 +12163,28 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +uncontrollable@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738" + integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ== + dependencies: + "@babel/runtime" "^7.6.3" + "@types/react" ">=16.9.11" + invariant "^2.2.4" + react-lifecycles-compat "^3.0.4" + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== +unicode-emoji-utils@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/unicode-emoji-utils/-/unicode-emoji-utils-1.1.2.tgz#e15c6522d98380f4b145ea9126be59c01dabc297" + integrity sha512-b0fe4T08DjwayBPvKtG+tKyNMwx/Qdc50EZJhOJlGDwqU24DaxNrHMT8Kl75hVmLabrXC6TQ+CuMEVV163z1eQ== + dependencies: + emoji-regex "10.2.1" + unicode-match-property-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" @@ -11340,15 +12193,15 @@ unicode-match-property-ecmascript@^2.0.0: unicode-canonical-property-names-ecmascript "^2.0.0" unicode-property-aliases-ecmascript "^2.0.0" -unicode-match-property-value-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" - integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== unicode-property-aliases-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" - integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== union-value@^1.0.0: version "1.0.1" @@ -11360,16 +12213,6 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" - integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= - unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -11391,11 +12234,6 @@ unique-string@^2.0.0: dependencies: crypto-random-string "^2.0.0" -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - universalify@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" @@ -11409,47 +12247,52 @@ universalify@^2.0.0: unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -unquote@^1.1.0, unquote@~1.1.1: +unquote@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" - integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + integrity sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg== unset-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== dependencies: has-value "^0.3.1" isobject "^3.0.0" +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== + upath@^1.1.1, upath@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -update-browserslist-db@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz#2924d3927367a38d5c555413a7ce138fc95fcb18" - integrity sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg== +update-browserslist-db@^1.0.11: + version "1.0.12" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.12.tgz#868ce670ac09b4a4d4c86b608701c0dee2dc41cd" + integrity sha512-tE1smlR58jxbFMtrMpFNRmsrOXlpNXss965T1CrpwuZUzUAg/TBQc94SpyhDLSzrqrJS9xTRBthnZAGcE1oaxg== dependencies: escalade "^3.1.1" picocolors "^1.0.0" uri-js@^4.2.2: - version "4.4.0" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" - integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== -url-parse@^1.4.3, url-parse@^1.4.7, url-parse@^1.5.3: +url-parse@^1.5.10, url-parse@^1.5.3: version "1.5.10" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== @@ -11458,12 +12301,12 @@ url-parse@^1.4.3, url-parse@^1.4.7, url-parse@^1.5.3: requires-port "^1.0.0" url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= + version "0.11.1" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.1.tgz#26f90f615427eca1b9f4d6a28288c147e2302a32" + integrity sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA== dependencies: - punycode "1.3.2" - querystring "0.2.0" + punycode "^1.4.1" + qs "^6.11.0" use-composed-ref@^1.3.0: version "1.3.0" @@ -11482,22 +12325,27 @@ use-latest@^1.2.1: dependencies: use-isomorphic-layout-effect "^1.1.1" +use-sync-external-store@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -utf-8-validate@^5.0.10: - version "5.0.10" - resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" - integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== +utf-8-validate@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-6.0.3.tgz#7d8c936d854e86b24d1d655f138ee27d2636d777" + integrity sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA== dependencies: node-gyp-build "^4.3.0" util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== util.promisify@~1.0.0: version "1.0.1" @@ -11512,7 +12360,7 @@ util.promisify@~1.0.0: util@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= + integrity sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ== dependencies: inherits "2.0.1" @@ -11526,27 +12374,32 @@ util@^0.11.0: utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^3.3.2, uuid@^3.4.0: +uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.3.1: +uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1, v8-compile-cache@^2.3.0: +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + +v8-compile-cache@^2.1.1: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== v8-to-istanbul@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz#b6f994b0b5d4ef255e17a0d17dc444a9f5132fa4" - integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w== + version "9.1.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" + integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA== dependencies: "@jridgewell/trace-mapping" "^0.3.12" "@types/istanbul-lib-coverage" "^2.0.1" @@ -11560,11 +12413,6 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -value-equal@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" - integrity sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw== - value-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" @@ -11573,22 +12421,17 @@ value-equal@^1.0.1: vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -vendors@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" - integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -w3c-xmlserializer@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz#06cdc3eefb7e4d0b20a560a5a3aeb0d2d9a65923" - integrity sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg== +w3c-xmlserializer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" + integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== dependencies: xml-name-validator "^4.0.0" @@ -11602,34 +12445,34 @@ walker@^1.0.8: warning@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" - integrity sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w= + integrity sha512-jMBt6pUrKn5I+OGgtQ4YZLdhIeJmObddh6CsibPxyQ5yPZm1XExSyzC1LCNX7BzhxWgiHmizBWJTHJIjMjTQYQ== dependencies: loose-envify "^1.0.0" -warning@^4.0.0, warning@^4.0.1: +warning@^4.0.1, warning@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== dependencies: loose-envify "^1.0.0" -watchpack-chokidar2@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" - integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== +watchpack-chokidar2@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" + integrity sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww== dependencies: chokidar "^2.1.8" watchpack@^1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.4.tgz#6e9da53b3c80bb2d6508188f5b200410866cd30b" - integrity sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg== + version "1.7.5" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" + integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== dependencies: graceful-fs "^4.1.2" neo-async "^2.5.0" optionalDependencies: chokidar "^3.4.1" - watchpack-chokidar2 "^2.0.0" + watchpack-chokidar2 "^2.0.1" wbuf@^1.1.0, wbuf@^1.7.3: version "1.7.3" @@ -11641,7 +12484,7 @@ wbuf@^1.1.0, wbuf@^1.7.3: webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== webidl-conversions@^4.0.2: version "4.0.2" @@ -11668,19 +12511,27 @@ webpack-assets-manifest@^4.0.6: tapable "^1.0" webpack-sources "^1.0" -webpack-bundle-analyzer@^4.6.1: - version "4.6.1" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.6.1.tgz#bee2ee05f4ba4ed430e4831a319126bb4ed9f5a6" - integrity sha512-oKz9Oz9j3rUciLNfpGFjOb49/jEpXNmWdVH8Ls//zNcnLlQdTGXQQMsBbb/gR7Zl8WNLxVCq+0Hqbx3zv6twBw== +webpack-bundle-analyzer@^4.8.0: + version "4.9.1" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz#d00bbf3f17500c10985084f22f1a2bf45cb2f09d" + integrity sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w== dependencies: + "@discoveryjs/json-ext" "0.5.7" acorn "^8.0.4" acorn-walk "^8.0.0" - chalk "^4.1.0" commander "^7.2.0" + escape-string-regexp "^4.0.0" gzip-size "^6.0.0" - lodash "^4.17.20" + is-plain-object "^5.0.0" + lodash.debounce "^4.0.8" + lodash.escape "^4.0.1" + lodash.flatten "^4.4.0" + lodash.invokemap "^4.6.0" + lodash.pullall "^4.2.0" + lodash.uniqby "^4.7.0" opener "^1.5.2" - sirv "^1.0.7" + picocolors "^1.0.0" + sirv "^2.0.3" ws "^7.3.1" webpack-cli@^3.3.12: @@ -11701,9 +12552,9 @@ webpack-cli@^3.3.12: yargs "^13.3.2" webpack-dev-middleware@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" - integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== + version "3.7.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" + integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ== dependencies: memory-fs "^0.4.1" mime "^2.4.4" @@ -11758,15 +12609,15 @@ webpack-log@^2.0.0: ansi-colors "^3.0.0" uuid "^3.3.2" -webpack-merge@^5.8.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" - integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== +webpack-merge@^5.9.0: + version "5.9.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.9.0.tgz#dc160a1c4cf512ceca515cc231669e9ddb133826" + integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg== dependencies: clone-deep "^4.0.1" wildcard "^2.0.0" -webpack-sources@^1.0, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: +webpack-sources@^1.0, webpack-sources@^1.1.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== @@ -11774,10 +12625,10 @@ webpack-sources@^1.0, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-so source-list-map "^2.0.0" source-map "~0.6.1" -webpack@^4.46.0: - version "4.46.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" - integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q== +webpack@^4.47.0: + version "4.47.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.47.0.tgz#8b8a02152d7076aeb03b61b47dad2eeed9810ebc" + integrity sha512-td7fYwgLSrky3fI1EuU5cneU4+pbH6GgOfuKNS1tNPcfdGinGELAqsb/BP4nnvZyKSG2i/xFGU7+n2PvZA8HJQ== dependencies: "@webassemblyjs/ast" "1.9.0" "@webassemblyjs/helper-module-context" "1.9.0" @@ -11803,16 +12654,7 @@ webpack@^4.46.0: watchpack "^1.7.4" webpack-sources "^1.4.1" -websocket-driver@>=0.5.1: - version "0.7.3" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9" - integrity sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg== - dependencies: - http-parser-js ">=0.4.0 <0.4.11" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-driver@^0.7.4: +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: version "0.7.4" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== @@ -11846,10 +12688,18 @@ whatwg-url@^11.0.0: tr46 "^3.0.0" webidl-conversions "^7.0.0" +whatwg-url@^12.0.0, whatwg-url@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-12.0.1.tgz#fd7bcc71192e7c3a2a97b9a8d6b094853ed8773c" + integrity sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ== + dependencies: + tr46 "^4.1.1" + webidl-conversions "^7.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== dependencies: tr46 "~0.0.3" webidl-conversions "^3.0.0" @@ -11874,10 +12724,49 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-builtin-type@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" + integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== + dependencies: + function.prototype.name "^1.1.5" + has-tostringtag "^1.0.0" + is-async-function "^2.0.0" + is-date-object "^1.0.5" + is-finalizationregistry "^1.0.2" + is-generator-function "^1.0.10" + is-regex "^1.1.4" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + +which-collection@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" + integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== + dependencies: + is-map "^2.0.1" + is-set "^2.0.1" + is-weakmap "^2.0.1" + is-weakset "^2.0.1" + which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== + +which-typed-array@^1.1.10, which-typed-array@^1.1.11, which-typed-array@^1.1.9: + version "1.1.11" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" + integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" @@ -11906,34 +12795,29 @@ wide-align@^1.1.5: string-width "^1.0.2 || 2 || 3 || 4" wildcard@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" - integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== -word-wrap@^1.2.3, word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -workbox-background-sync@6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz#3141afba3cc8aa2ae14c24d0f6811374ba8ff6a9" - integrity sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g== +workbox-background-sync@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-7.0.0.tgz#2b84b96ca35fec976e3bd2794b70e4acec46b3a5" + integrity sha512-S+m1+84gjdueM+jIKZ+I0Lx0BDHkk5Nu6a3kTVxP4fdj3gKouRNmhO8H290ybnJTOPfBDtTMXSQA/QLTvr7PeA== dependencies: idb "^7.0.1" - workbox-core "6.5.4" + workbox-core "7.0.0" -workbox-broadcast-update@6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz#8441cff5417cd41f384ba7633ca960a7ffe40f66" - integrity sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw== +workbox-broadcast-update@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-7.0.0.tgz#7f611ca1a94ba8ac0aa40fa171c9713e0f937d22" + integrity sha512-oUuh4jzZrLySOo0tC0WoKiSg90bVAcnE98uW7F8GFiSOXnhogfNDGZelPJa+6KpGBO5+Qelv04Hqx2UD+BJqNQ== dependencies: - workbox-core "6.5.4" + workbox-core "7.0.0" -workbox-build@6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-6.5.4.tgz#7d06d31eb28a878817e1c991c05c5b93409f0389" - integrity sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA== +workbox-build@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-7.0.0.tgz#02ab5ef2991b3369b8b9395703f08912212769b4" + integrity sha512-CttE7WCYW9sZC+nUYhQg3WzzGPr4IHmrPnjKiu3AMXsiNQKx+l4hHl63WTrnicLmKEKHScWDH8xsGBdrYgtBzg== dependencies: "@apideck/better-ajv-errors" "^0.3.1" "@babel/core" "^7.11.1" @@ -11957,139 +12841,142 @@ workbox-build@6.5.4: strip-comments "^2.0.1" tempy "^0.6.0" upath "^1.2.0" - workbox-background-sync "6.5.4" - workbox-broadcast-update "6.5.4" - workbox-cacheable-response "6.5.4" - workbox-core "6.5.4" - workbox-expiration "6.5.4" - workbox-google-analytics "6.5.4" - workbox-navigation-preload "6.5.4" - workbox-precaching "6.5.4" - workbox-range-requests "6.5.4" - workbox-recipes "6.5.4" - workbox-routing "6.5.4" - workbox-strategies "6.5.4" - workbox-streams "6.5.4" - workbox-sw "6.5.4" - workbox-window "6.5.4" + workbox-background-sync "7.0.0" + workbox-broadcast-update "7.0.0" + workbox-cacheable-response "7.0.0" + workbox-core "7.0.0" + workbox-expiration "7.0.0" + workbox-google-analytics "7.0.0" + workbox-navigation-preload "7.0.0" + workbox-precaching "7.0.0" + workbox-range-requests "7.0.0" + workbox-recipes "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" + workbox-streams "7.0.0" + workbox-sw "7.0.0" + workbox-window "7.0.0" -workbox-cacheable-response@6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz#a5c6ec0c6e2b6f037379198d4ef07d098f7cf137" - integrity sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug== +workbox-cacheable-response@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-7.0.0.tgz#ee27c036728189eed69d25a135013053277482d2" + integrity sha512-0lrtyGHn/LH8kKAJVOQfSu3/80WDc9Ma8ng0p2i/5HuUndGttH+mGMSvOskjOdFImLs2XZIimErp7tSOPmu/6g== dependencies: - workbox-core "6.5.4" + workbox-core "7.0.0" -workbox-core@6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-6.5.4.tgz#df48bf44cd58bb1d1726c49b883fb1dffa24c9ba" - integrity sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q== +workbox-core@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-7.0.0.tgz#dec114ec923cc2adc967dd9be1b8a0bed50a3545" + integrity sha512-81JkAAZtfVP8darBpfRTovHg8DGAVrKFgHpOArZbdFd78VqHr5Iw65f2guwjE2NlCFbPFDoez3D3/6ZvhI/rwQ== -workbox-expiration@6.5.4, workbox-expiration@^6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-6.5.4.tgz#501056f81e87e1d296c76570bb483ce5e29b4539" - integrity sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ== +workbox-expiration@7.0.0, workbox-expiration@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-7.0.0.tgz#3d90bcf2a7577241de950f89784f6546b66c2baa" + integrity sha512-MLK+fogW+pC3IWU9SFE+FRStvDVutwJMR5if1g7oBJx3qwmO69BNoJQVaMXq41R0gg3MzxVfwOGKx3i9P6sOLQ== dependencies: idb "^7.0.1" - workbox-core "6.5.4" + workbox-core "7.0.0" -workbox-google-analytics@6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz#c74327f80dfa4c1954cbba93cd7ea640fe7ece7d" - integrity sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg== +workbox-google-analytics@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-7.0.0.tgz#603b2c4244af1e85de0fb26287d4e17d3293452a" + integrity sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg== dependencies: - workbox-background-sync "6.5.4" - workbox-core "6.5.4" - workbox-routing "6.5.4" - workbox-strategies "6.5.4" + workbox-background-sync "7.0.0" + workbox-core "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" -workbox-navigation-preload@6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz#ede56dd5f6fc9e860a7e45b2c1a8f87c1c793212" - integrity sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng== +workbox-navigation-preload@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-7.0.0.tgz#4913878dbbd97057181d57baa18d2bbdde085c6c" + integrity sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA== dependencies: - workbox-core "6.5.4" + workbox-core "7.0.0" -workbox-precaching@6.5.4, workbox-precaching@^6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-6.5.4.tgz#740e3561df92c6726ab5f7471e6aac89582cab72" - integrity sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg== +workbox-precaching@7.0.0, workbox-precaching@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-7.0.0.tgz#3979ba8033aadf3144b70e9fe631d870d5fbaa03" + integrity sha512-EC0vol623LJqTJo1mkhD9DZmMP604vHqni3EohhQVwhJlTgyKyOkMrZNy5/QHfOby+39xqC01gv4LjOm4HSfnA== dependencies: - workbox-core "6.5.4" - workbox-routing "6.5.4" - workbox-strategies "6.5.4" + workbox-core "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" -workbox-range-requests@6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz#86b3d482e090433dab38d36ae031b2bb0bd74399" - integrity sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg== +workbox-range-requests@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-7.0.0.tgz#97511901e043df27c1aa422adcc999a7751f52ed" + integrity sha512-SxAzoVl9j/zRU9OT5+IQs7pbJBOUOlriB8Gn9YMvi38BNZRbM+RvkujHMo8FOe9IWrqqwYgDFBfv6sk76I1yaQ== dependencies: - workbox-core "6.5.4" + workbox-core "7.0.0" -workbox-recipes@6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-6.5.4.tgz#cca809ee63b98b158b2702dcfb741b5cc3e24acb" - integrity sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA== +workbox-recipes@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-recipes/-/workbox-recipes-7.0.0.tgz#1a6a01c8c2dfe5a41eef0fed3fe517e8a45c6514" + integrity sha512-DntcK9wuG3rYQOONWC0PejxYYIDHyWWZB/ueTbOUDQgefaeIj1kJ7pdP3LZV2lfrj8XXXBWt+JDRSw1lLLOnww== dependencies: - workbox-cacheable-response "6.5.4" - workbox-core "6.5.4" - workbox-expiration "6.5.4" - workbox-precaching "6.5.4" - workbox-routing "6.5.4" - workbox-strategies "6.5.4" + workbox-cacheable-response "7.0.0" + workbox-core "7.0.0" + workbox-expiration "7.0.0" + workbox-precaching "7.0.0" + workbox-routing "7.0.0" + workbox-strategies "7.0.0" -workbox-routing@6.5.4, workbox-routing@^6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-6.5.4.tgz#6a7fbbd23f4ac801038d9a0298bc907ee26fe3da" - integrity sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg== +workbox-routing@7.0.0, workbox-routing@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-7.0.0.tgz#6668438a06554f60645aedc77244a4fe3a91e302" + integrity sha512-8YxLr3xvqidnbVeGyRGkaV4YdlKkn5qZ1LfEePW3dq+ydE73hUUJJuLmGEykW3fMX8x8mNdL0XrWgotcuZjIvA== dependencies: - workbox-core "6.5.4" + workbox-core "7.0.0" -workbox-strategies@6.5.4, workbox-strategies@^6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-6.5.4.tgz#4edda035b3c010fc7f6152918370699334cd204d" - integrity sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw== +workbox-strategies@7.0.0, workbox-strategies@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-7.0.0.tgz#dcba32b3f3074476019049cc490fe1a60ea73382" + integrity sha512-dg3qJU7tR/Gcd/XXOOo7x9QoCI9nk74JopaJaYAQ+ugLi57gPsXycVdBnYbayVj34m6Y8ppPwIuecrzkpBVwbA== dependencies: - workbox-core "6.5.4" + workbox-core "7.0.0" -workbox-streams@6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-6.5.4.tgz#1cb3c168a6101df7b5269d0353c19e36668d7d69" - integrity sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg== +workbox-streams@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-7.0.0.tgz#36722aecd04785f88b6f709e541c094fc658c0f9" + integrity sha512-moVsh+5to//l6IERWceYKGiftc+prNnqOp2sgALJJFbnNVpTXzKISlTIsrWY+ogMqt+x1oMazIdHj25kBSq/HQ== dependencies: - workbox-core "6.5.4" - workbox-routing "6.5.4" + workbox-core "7.0.0" + workbox-routing "7.0.0" -workbox-sw@6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-6.5.4.tgz#d93e9c67924dd153a61367a4656ff4d2ae2ed736" - integrity sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA== +workbox-sw@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-7.0.0.tgz#7350126411e3de1409f7ec243df8d06bb5b08b86" + integrity sha512-SWfEouQfjRiZ7GNABzHUKUyj8pCoe+RwjfOIajcx6J5mtgKkN+t8UToHnpaJL5UVVOf5YhJh+OHhbVNIHe+LVA== -workbox-webpack-plugin@^6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz#baf2d3f4b8f435f3469887cf4fba2b7fac3d0fd7" - integrity sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg== +workbox-webpack-plugin@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-7.0.0.tgz#6c61661a2cacde1239192a5877a041a2943d1a55" + integrity sha512-R1ZzCHPfzeJjLK2/TpKUhxSQ3fFDCxlWxgRhhSjMQLz3G2MlBnyw/XeYb34e7SGgSv0qG22zEhMIzjMNqNeKbw== dependencies: fast-json-stable-stringify "^2.1.0" pretty-bytes "^5.4.1" upath "^1.2.0" webpack-sources "^1.4.3" - workbox-build "6.5.4" + workbox-build "7.0.0" -workbox-window@6.5.4, workbox-window@^6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-6.5.4.tgz#d991bc0a94dff3c2dbb6b84558cff155ca878e91" - integrity sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug== +workbox-window@7.0.0, workbox-window@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-7.0.0.tgz#a683ab33c896e4f16786794eac7978fc98a25d08" + integrity sha512-j7P/bsAWE/a7sxqTzXo3P2ALb1reTfZdvVp6OJ/uLr/C2kZAMvjeWGm8V4htQhor7DOvYg0sSbFN2+flT5U0qA== dependencies: "@types/trusted-types" "^2.0.2" - workbox-core "6.5.4" + workbox-core "7.0.0" -worker-farm@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" - integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + name wrap-ansi-cjs + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: - errno "~0.1.7" + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" wrap-ansi@^5.1.0: version "5.1.0" @@ -12100,21 +12987,30 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^4.0.1, write-file-atomic@^4.0.2: +write-file-atomic@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== @@ -12122,22 +13018,30 @@ write-file-atomic@^4.0.1, write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" +write-file-atomic@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" + integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^4.0.1" + ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + version "6.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" + integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== dependencies: async-limiter "~1.0.0" ws@^7.3.1: - version "7.4.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" - integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@^8.10.0, ws@^8.9.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.10.0.tgz#00a28c09dfb76eae4eb45c3b565f771d6951aa51" - integrity sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw== +ws@^8.11.0, ws@^8.12.1, ws@^8.13.0: + version "8.14.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.1.tgz#4b9586b4f70f9e6534c7bb1d3dc0baa8b8cf01e0" + integrity sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A== xml-name-validator@^4.0.0: version "4.0.0" @@ -12149,7 +13053,7 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xtend@^4.0.0, xtend@~4.0.1: +xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== @@ -12160,25 +13064,30 @@ y18n@^4.0.0: integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== y18n@^5.0.5: - version "5.0.5" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18" - integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg== - -yallist@4.0.0, yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yaml@^1.10.0, yaml@^1.7.2: +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.2.2: + version "2.3.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" + integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== + yargs-parser@^13.1.2: version "13.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" @@ -12187,15 +13096,15 @@ yargs-parser@^13.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.3: +yargs-parser@^20.2.1, yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^21.0.0: - version "21.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.0.tgz#a485d3966be4317426dd56bdb6a30131b281dc55" - integrity sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@^13.3.2: version "13.3.2" @@ -12213,10 +13122,10 @@ yargs@^13.3.2: y18n "^4.0.0" yargs-parser "^13.1.2" -yargs@^17.3.1, yargs@^17.6.0: - version "17.6.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.0.tgz#e134900fc1f218bc230192bdec06a0a5f973e46c" - integrity sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g== +yargs@^17.3.1, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1" escalade "^3.1.1" @@ -12224,7 +13133,7 @@ yargs@^17.3.1, yargs@^17.6.0: require-directory "^2.1.1" string-width "^4.2.3" y18n "^5.0.5" - yargs-parser "^21.0.0" + yargs-parser "^21.1.1" yocto-queue@^0.1.0: version "0.1.0" @@ -12234,4 +13143,4 @@ yocto-queue@^0.1.0: zlibjs@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/zlibjs/-/zlibjs-0.3.1.tgz#50197edb28a1c42ca659cc8b4e6a9ddd6d444554" - integrity sha1-UBl+2yihxCymWcyLTmqd3W1ERVQ= + integrity sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==