@@ -244,14 +220,14 @@ export default class Card extends PureComponent {
);
} else {
embed = (
-
+
);
}
return (
-
+
{embed}
{description}
diff --git a/app/javascript/packs/sign_up.js b/app/javascript/packs/sign_up.js
index 9aae9c11b8..cf9c837773 100644
--- a/app/javascript/packs/sign_up.js
+++ b/app/javascript/packs/sign_up.js
@@ -13,4 +13,30 @@ ready(() => {
console.error(error);
});
}, 5000);
+
+ document.querySelectorAll('.timer-button').forEach(button => {
+ let counter = 30;
+
+ const container = document.createElement('span');
+
+ const updateCounter = () => {
+ container.innerText = ` (${counter})`;
+ };
+
+ updateCounter();
+
+ const countdown = setInterval(() => {
+ counter--;
+
+ if (counter === 0) {
+ button.disabled = false;
+ button.removeChild(container);
+ clearInterval(countdown);
+ } else {
+ updateCounter();
+ }
+ }, 1000);
+
+ button.appendChild(container);
+ });
});
diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss
index babfbbbad0..2a5285ee02 100644
--- a/app/javascript/styles/mastodon/accounts.scss
+++ b/app/javascript/styles/mastodon/accounts.scss
@@ -187,7 +187,6 @@
}
}
-.account-role,
.information-badge,
.simple_form .recommended,
.simple_form .not_recommended {
@@ -212,10 +211,30 @@
}
.account-role {
+ display: inline-flex;
+ padding: 4px;
+ padding-inline-end: 8px;
border: 1px solid $highlight-text-color;
+ color: $highlight-text-color;
+ font-weight: 500;
+ font-size: 12px;
+ letter-spacing: 0.5px;
+ line-height: 16px;
+ gap: 4px;
+ border-radius: 6px;
+ align-items: center;
- .fa {
- color: var(--user-role-accent, $highlight-text-color);
+ svg {
+ width: auto;
+ height: 15px;
+ opacity: 0.85;
+ fill: currentColor;
+ }
+
+ &__domain {
+ font-weight: 400;
+ opacity: 0.75;
+ letter-spacing: 0;
}
}
diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss
index a77f8425dd..6714b24268 100644
--- a/app/javascript/styles/mastodon/basics.scss
+++ b/app/javascript/styles/mastodon/basics.scss
@@ -164,7 +164,7 @@ body {
a {
&:focus {
border-radius: 4px;
- outline: $ui-button-icon-focus-outline;
+ outline: $ui-button-focus-outline;
}
&:focus:not(:focus-visible) {
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index ea3e4d67e9..8103536454 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -3302,6 +3302,8 @@ $ui-header-height: 55px;
text-decoration: none;
overflow: hidden;
white-space: nowrap;
+ border: 0;
+ border-left: 4px solid transparent;
&:hover,
&:focus,
@@ -3313,6 +3315,11 @@ $ui-header-height: 55px;
outline: 0;
}
+ &:focus-visible {
+ border-color: $ui-button-focus-outline-color;
+ border-radius: 0;
+ }
+
&--transparent {
background: transparent;
color: $ui-secondary-color;
@@ -3529,13 +3536,16 @@ button.icon-button.active i.fa-retweet {
}
.status-card {
- display: block;
+ display: flex;
+ align-items: center;
position: relative;
font-size: 14px;
color: $darker-text-color;
margin-top: 14px;
text-decoration: none;
overflow: hidden;
+ border: 1px solid lighten($ui-base-color, 8%);
+ border-radius: 8px;
&__actions {
bottom: 0;
@@ -3546,11 +3556,13 @@ button.icon-button.active i.fa-retweet {
display: flex;
justify-content: center;
align-items: center;
+ cursor: pointer;
& > div {
background: rgba($base-shadow-color, 0.6);
border-radius: 8px;
padding: 12px 9px;
+ backdrop-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
flex: 0 0 auto;
display: flex;
justify-content: center;
@@ -3591,7 +3603,8 @@ a.status-card {
&:active {
.status-card__title,
.status-card__host,
- .status-card__author {
+ .status-card__author,
+ .status-card__description {
color: $highlight-text-color;
}
}
@@ -3606,7 +3619,8 @@ a.status-card {
&:active {
.status-card__title,
.status-card__host,
- .status-card__author {
+ .status-card__author,
+ .status-card__description {
color: $highlight-text-color;
}
}
@@ -3639,19 +3653,32 @@ a.status-card {
line-height: 24px;
color: $primary-text-color;
overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.status-card.expanded .status-card__title {
+ white-space: normal;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
}
.status-card__content {
flex: 1 1 auto;
overflow: hidden;
- padding: 15px 0;
- padding-bottom: 0;
+ padding: 15px;
+ box-sizing: border-box;
+ max-width: 100%;
}
.status-card__host {
display: block;
font-size: 14px;
margin-bottom: 8px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.status-card__author {
@@ -3659,17 +3686,30 @@ a.status-card {
margin-top: 8px;
font-size: 14px;
color: $primary-text-color;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
strong {
font-weight: 500;
}
}
+.status-card__description {
+ display: block;
+ margin-top: 8px;
+ font-size: 14px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
.status-card__image {
- width: 100%;
+ flex: 0 0 auto;
+ width: 120px;
+ aspect-ratio: 1;
background: lighten($ui-base-color, 8%);
position: relative;
- border-radius: 8px;
& > .fa {
font-size: 21px;
@@ -3682,7 +3722,6 @@ a.status-card {
}
.status-card__image-image {
- border-radius: 8px;
display: block;
margin: 0;
width: 100%;
@@ -3693,7 +3732,6 @@ a.status-card {
}
.status-card__image-preview {
- border-radius: 8px;
display: block;
margin: 0;
width: 100%;
@@ -3710,6 +3748,37 @@ a.status-card {
}
}
+.status-card.expanded {
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+.status-card.expanded .status-card__image {
+ width: 100%;
+ aspect-ratio: auto;
+}
+
+.status-card__image,
+.status-card__image-image,
+.status-card__image-preview {
+ border-start-start-radius: 8px;
+ border-start-end-radius: 0;
+ border-end-end-radius: 0;
+ border-end-start-radius: 8px;
+}
+
+.status-card.expanded .status-card__image,
+.status-card.expanded .status-card__image-image,
+.status-card.expanded .status-card__image-preview {
+ border-start-end-radius: 8px;
+ border-end-end-radius: 0;
+ border-end-start-radius: 0;
+}
+
+.status-card.expanded > a {
+ width: 100%;
+}
+
.load-more {
display: block;
color: $dark-text-color;
@@ -3915,7 +3984,7 @@ a.status-card {
}
&:focus-visible {
- outline: $ui-button-icon-focus-outline;
+ outline: $ui-button-focus-outline;
}
&.active {
@@ -4925,7 +4994,7 @@ a.status-card {
width: 100%;
background: $ui-base-color;
border-radius: 0 0 4px 4px;
- box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
+ box-shadow: var(--dropdown-shadow);
z-index: 99;
font-size: 13px;
padding: 15px 5px;
@@ -8241,7 +8310,7 @@ noscript {
flex: 0 0 auto;
position: relative;
width: 120px;
- height: 120px;
+ aspect-ratio: 1;
.skeleton {
width: 100%;
diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss
index 073bb16e59..611c8bb5d1 100644
--- a/app/javascript/styles/mastodon/variables.scss
+++ b/app/javascript/styles/mastodon/variables.scss
@@ -43,6 +43,8 @@ $ui-highlight-color: $classic-highlight-color !default;
$ui-button-color: $white !default;
$ui-button-background-color: $blurple-500 !default;
$ui-button-focus-background-color: $blurple-600 !default;
+$ui-button-focus-outline-color: $blurple-400 !default;
+$ui-button-focus-outline: solid 2px $ui-button-focus-outline-color !default;
$ui-button-secondary-color: $grey-100 !default;
$ui-button-secondary-border-color: $grey-100 !default;
@@ -57,7 +59,7 @@ $ui-button-tertiary-focus-color: $white !default;
$ui-button-destructive-background-color: $red-500 !default;
$ui-button-destructive-focus-background-color: $red-600 !default;
-$ui-button-icon-focus-outline: solid 2px $blurple-400 !default;
+$ui-button-icon-focus-outline: $ui-button-focus-outline !default;
$ui-button-icon-hover-background-color: rgba(140, 141, 255, 40%) !default;
// Variables for texts
diff --git a/app/lib/importer/base_importer.rb b/app/lib/importer/base_importer.rb
index 0cd1d34227..07be4650ec 100644
--- a/app/lib/importer/base_importer.rb
+++ b/app/lib/importer/base_importer.rb
@@ -45,8 +45,11 @@ class Importer::BaseImporter
# Remove documents from the index that no longer exist in the database
def clean_up!
index.scroll_batches do |documents|
+ primary_key = index.adapter.target.primary_key
+ raise ActiveRecord::UnknownPrimaryKey, index.adapter.target if primary_key.nil?
+
ids = documents.pluck('_id')
- existence_map = index.adapter.target.where(id: ids).pluck(:id).each_with_object({}) { |id, map| map[id.to_s] = true }
+ existence_map = index.adapter.target.where(primary_key => ids).pluck(primary_key).each_with_object({}) { |id, map| map[id.to_s] = true }
tmp = ids.reject { |id| existence_map[id] }
next if tmp.empty?
diff --git a/app/lib/request.rb b/app/lib/request.rb
index c7aafbd4fc..e5a9476a8e 100644
--- a/app/lib/request.rb
+++ b/app/lib/request.rb
@@ -68,13 +68,26 @@ class Request
# about 15s in total
TIMEOUT = { connect_timeout: 5, read_timeout: 10, write_timeout: 10, read_deadline: 30 }.freeze
+ # Workaround for overly-eager decoding of percent-encoded characters in Addressable::URI#normalized_path
+ # https://github.com/sporkmonger/addressable/issues/366
+ URI_NORMALIZER = lambda do |uri|
+ uri = HTTP::URI.parse(uri)
+
+ HTTP::URI.new(
+ scheme: uri.normalized_scheme,
+ authority: uri.normalized_authority,
+ path: Addressable::URI.normalize_path(encode_non_ascii(uri.path)).presence || '/',
+ query: encode_non_ascii(uri.query)
+ )
+ end
+
include RoutingHelper
def initialize(verb, url, **options)
raise ArgumentError if url.blank?
@verb = verb
- @url = Addressable::URI.parse(url).normalize
+ @url = URI_NORMALIZER.call(url)
@http_client = options.delete(:http_client)
@allow_local = options.delete(:allow_local)
@options = options.merge(socket_class: use_proxy? || @allow_local ? ProxySocket : Socket)
@@ -138,8 +151,14 @@ class Request
%w(http https).include?(parsed_url.scheme) && parsed_url.host.present?
end
+ NON_ASCII_PATTERN = /[^\x00-\x7F]+/
+
+ def encode_non_ascii(str)
+ str&.gsub(NON_ASCII_PATTERN) { |substr| CGI.escape(substr.encode(Encoding::UTF_8)) }
+ end
+
def http_client
- HTTP.use(:auto_inflate).follow(max_hops: 3)
+ HTTP.use(:auto_inflate).use(normalize_uri: { normalizer: URI_NORMALIZER }).follow(max_hops: 3)
end
end
@@ -327,7 +346,7 @@ class Request
end
def private_address_exceptions
- @private_address_exceptions = (ENV['ALLOWED_PRIVATE_ADDRESSES'] || '').split(',').map { |addr| IPAddr.new(addr) }
+ @private_address_exceptions = (ENV['ALLOWED_PRIVATE_ADDRESSES'] || '').split(/(?:\s*,\s*|\s+)/).map { |addr| IPAddr.new(addr) }
end
end
end
diff --git a/app/lib/rss/builder.rb b/app/lib/rss/builder.rb
index a9b3f08c5a..2e3697a2ab 100644
--- a/app/lib/rss/builder.rb
+++ b/app/lib/rss/builder.rb
@@ -14,13 +14,14 @@ class RSS::Builder
end
def to_xml
- (''.dup << Ox.dump(wrap_in_document, effort: :tolerant)).force_encoding('UTF-8')
+ Ox.dump(wrap_in_document, effort: :tolerant).force_encoding('UTF-8')
end
private
def wrap_in_document
Ox::Document.new(version: '1.0').tap do |document|
+ document << xml_instruct
document << Ox::Element.new('rss').tap do |rss|
rss['version'] = '2.0'
rss['xmlns:webfeeds'] = 'http://webfeeds.org/rss/1.0'
@@ -30,4 +31,11 @@ class RSS::Builder
end
end
end
+
+ def xml_instruct
+ Ox::Instruct.new(:xml).tap do |instruct|
+ instruct[:version] = '1.0'
+ instruct[:encoding] = 'UTF-8'
+ end
+ end
end
diff --git a/app/lib/text_formatter.rb b/app/lib/text_formatter.rb
index f9205e7d47..581ee835b3 100644
--- a/app/lib/text_formatter.rb
+++ b/app/lib/text_formatter.rb
@@ -75,7 +75,7 @@ class TextFormatter
entity[:indices].first
end
- result = ''.dup
+ result = +''
last_index = entities.reduce(0) do |index, entity|
indices = entity[:indices]
diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb
index 277612366b..5eecfed104 100644
--- a/app/mailers/notification_mailer.rb
+++ b/app/mailers/notification_mailer.rb
@@ -8,6 +8,7 @@ class NotificationMailer < ApplicationMailer
before_action :process_params
before_action :set_status, only: [:mention, :favourite, :reblog]
before_action :set_account, only: [:follow, :favourite, :reblog, :follow_request]
+ after_action :set_list_headers!
default to: -> { email_address_with_name(@user.email, @me.username) }
@@ -61,6 +62,7 @@ class NotificationMailer < ApplicationMailer
@me = params[:recipient]
@user = @me.user
@type = action_name
+ @unsubscribe_url = unsubscribe_url(token: @user.to_sgid(for: 'unsubscribe').to_s, type: @type)
end
def set_status
@@ -71,6 +73,12 @@ class NotificationMailer < ApplicationMailer
@account = @notification.from_account
end
+ def set_list_headers!
+ headers['List-ID'] = "<#{@type}.#{@me.username}.#{Rails.configuration.x.local_domain}>"
+ headers['List-Unsubscribe'] = "<#{@unsubscribe_url}>"
+ headers['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click'
+ end
+
def thread_by_conversation(conversation)
return if conversation.nil?
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 249bf4a961..c373663aca 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -57,7 +57,7 @@ class MediaAttachment < ApplicationRecord
).freeze
IMAGE_MIME_TYPES = %w(image/jpeg image/png image/gif image/heic image/heif image/webp image/avif).freeze
- IMAGE_CONVERTIBLE_MIME_TYPES = %w(image/heic image/heif).freeze
+ IMAGE_CONVERTIBLE_MIME_TYPES = %w(image/heic image/heif image/avif).freeze
VIDEO_MIME_TYPES = %w(video/webm video/mp4 video/quicktime video/ogg audio/webm).freeze
VIDEO_CONVERTIBLE_MIME_TYPES = %w(video/quicktime).freeze
AUDIO_MIME_TYPES = %w(audio/wave audio/wav audio/x-wav audio/x-pn-wave audio/vnd.wave audio/ogg audio/vorbis audio/mpeg audio/mp3 audio/flac audio/aac audio/m4a audio/x-m4a audio/mp4 audio/3gpp video/x-ms-asf audio/opus).freeze
diff --git a/app/presenters/language_presenter.rb b/app/presenters/language_presenter.rb
new file mode 100644
index 0000000000..69ea991d54
--- /dev/null
+++ b/app/presenters/language_presenter.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class LanguagePresenter < ActiveModelSerializers::Model
+ attributes :code, :name, :native_name
+
+ def initialize(code)
+ super()
+
+ @code = code
+ @item = LanguagesHelper::SUPPORTED_LOCALES[code]
+ end
+
+ def name
+ @item[0]
+ end
+
+ def native_name
+ @item[1]
+ end
+end
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index cf3461d6e3..e9ff10a077 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -40,7 +40,7 @@ class InitialStateSerializer < ActiveModel::Serializer
repository: Mastodon::Version.repository,
source_url: instance_presenter.source_url,
version: instance_presenter.version,
- limited_federation_mode: Rails.configuration.x.whitelist_mode,
+ limited_federation_mode: Rails.configuration.x.limited_federation_mode,
mascot: instance_presenter.mascot&.file&.url,
profile_directory: Setting.profile_directory,
trends_enabled: Setting.trends,
diff --git a/app/serializers/rest/language_serializer.rb b/app/serializers/rest/language_serializer.rb
new file mode 100644
index 0000000000..e07956465c
--- /dev/null
+++ b/app/serializers/rest/language_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class REST::LanguageSerializer < ActiveModel::Serializer
+ attributes :code, :name
+end
diff --git a/app/services/concerns/payloadable.rb b/app/services/concerns/payloadable.rb
index 04c3798fe0..1389a42ed6 100644
--- a/app/services/concerns/payloadable.rb
+++ b/app/services/concerns/payloadable.rb
@@ -23,6 +23,6 @@ module Payloadable
end
def signing_enabled?
- ENV['AUTHORIZED_FETCH'] != 'true' && !Rails.configuration.x.whitelist_mode
+ ENV['AUTHORIZED_FETCH'] != 'true' && !Rails.configuration.x.limited_federation_mode
end
end
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index dccd86c1e6..13775e63c1 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -45,27 +45,29 @@ class FetchLinkCardService < BaseService
def html
return @html if defined?(@html)
- Request.new(:get, @url).add_headers('Accept' => 'text/html', 'User-Agent' => "#{Mastodon::Version.user_agent} Bot").perform do |res|
+ @html = Request.new(:get, @url).add_headers('Accept' => 'text/html', 'User-Agent' => "#{Mastodon::Version.user_agent} Bot").perform do |res|
+ next unless res.code == 200 && res.mime_type == 'text/html'
+
# We follow redirects, and ideally we want to save the preview card for
# the destination URL and not any link shortener in-between, so here
# we set the URL to the one of the last response in the redirect chain
@url = res.request.uri.to_s
@card = PreviewCard.find_or_initialize_by(url: @url) if @card.url != @url
- if res.code == 200 && res.mime_type == 'text/html'
- @html_charset = res.charset
- @html = res.body_with_limit
- else
- @html_charset = nil
- @html = nil
- end
+ @html_charset = res.charset
+
+ res.body_with_limit
end
end
def attach_card
- @status.preview_cards << @card
- Rails.cache.delete(@status)
- Trends.links.register(@status)
+ with_redis_lock("attach_card:#{@status.id}") do
+ return if @status.preview_cards.any?
+
+ @status.preview_cards << @card
+ Rails.cache.delete(@status)
+ Trends.links.register(@status)
+ end
end
def parse_urls
diff --git a/app/services/unallow_domain_service.rb b/app/services/unallow_domain_service.rb
index fc52607612..bdc71b1c08 100644
--- a/app/services/unallow_domain_service.rb
+++ b/app/services/unallow_domain_service.rb
@@ -4,7 +4,7 @@ class UnallowDomainService < BaseService
include DomainControlHelper
def call(domain_allow)
- suspend_accounts!(domain_allow.domain) if whitelist_mode?
+ suspend_accounts!(domain_allow.domain) if limited_federation_mode?
domain_allow.destroy
end
diff --git a/app/validators/language_validator.rb b/app/validators/language_validator.rb
index b723e1a408..088bbc0976 100644
--- a/app/validators/language_validator.rb
+++ b/app/validators/language_validator.rb
@@ -4,18 +4,20 @@ class LanguageValidator < ActiveModel::EachValidator
include LanguagesHelper
def validate_each(record, attribute, value)
- record.errors.add(attribute, :invalid) unless valid?(value)
+ @value = value
+
+ record.errors.add(attribute, :invalid) unless valid_locale_value?
end
private
- def valid?(str)
- if str.nil?
+ def valid_locale_value?
+ if @value.nil?
true
- elsif str.is_a?(Array)
- str.all? { |x| valid_locale?(x) }
+ elsif @value.is_a?(Array)
+ @value.all? { |x| valid_locale?(x) }
else
- valid_locale?(str)
+ valid_locale?(@value)
end
end
end
diff --git a/app/validators/status_length_validator.rb b/app/validators/status_length_validator.rb
index 102f2db601..f4725e05fc 100644
--- a/app/validators/status_length_validator.rb
+++ b/app/validators/status_length_validator.rb
@@ -45,7 +45,7 @@ class StatusLengthValidator < ActiveModel::Validator
def rewrite_entities(str, entities)
entities.sort_by! { |entity| entity[:indices].first }
- result = ''.dup
+ result = +''
last_index = entities.reduce(0) do |index, entity|
result << str[index...entity[:indices].first]
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
index a90fb6958a..f88a370308 100644
--- a/app/validators/url_validator.rb
+++ b/app/validators/url_validator.rb
@@ -1,16 +1,31 @@
# frozen_string_literal: true
class URLValidator < ActiveModel::EachValidator
+ VALID_SCHEMES = %w(http https).freeze
+
def validate_each(record, attribute, value)
- record.errors.add(attribute, :invalid) unless compliant?(value)
+ @value = value
+
+ record.errors.add(attribute, :invalid) unless compliant_url?
end
private
- def compliant?(url)
- parsed_url = Addressable::URI.parse(url)
- parsed_url && %w(http https).include?(parsed_url.scheme) && parsed_url.host
+ def compliant_url?
+ parsed_url.present? && valid_url_scheme? && valid_url_host?
+ end
+
+ def parsed_url
+ Addressable::URI.parse(@value)
rescue Addressable::URI::InvalidURIError
false
end
+
+ def valid_url_scheme?
+ VALID_SCHEMES.include?(parsed_url.scheme)
+ end
+
+ def valid_url_host?
+ parsed_url.host.present?
+ end
end
diff --git a/app/views/admin/instances/index.html.haml b/app/views/admin/instances/index.html.haml
index 3e70a51eec..7e43b4c538 100644
--- a/app/views/admin/instances/index.html.haml
+++ b/app/views/admin/instances/index.html.haml
@@ -2,7 +2,7 @@
= t('admin.instances.title')
- content_for :heading_actions do
- - if whitelist_mode?
+ - if limited_federation_mode?
= link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button', id: 'add-instance-button'
= link_to t('admin.domain_allows.export'), export_admin_export_domain_allows_path(format: :csv), class: 'button'
= link_to t('admin.domain_allows.import'), new_admin_export_domain_allow_path, class: 'button'
@@ -17,7 +17,7 @@
%ul
%li= filter_link_to t('admin.instances.moderation.all'), limited: nil
- - unless whitelist_mode?
+ - unless limited_federation_mode?
%li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
.filter-subset
@@ -27,7 +27,7 @@
%li= filter_link_to t('admin.instances.delivery.failing'), availability: 'failing'
%li= filter_link_to t('admin.instances.delivery.unavailable'), availability: 'unavailable'
-- unless whitelist_mode?
+- unless limited_federation_mode?
= form_tag admin_instances_url, method: 'GET', class: 'simple_form' do
.fields-group
- InstanceFilter::KEYS.each do |key|
diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml
index 1380b9a4d2..46b5c301b5 100644
--- a/app/views/admin/instances/show.html.haml
+++ b/app/views/admin/instances/show.html.haml
@@ -33,7 +33,7 @@
%h3= t('admin.instances.content_policies.title')
-- if whitelist_mode?
+- if limited_federation_mode?
%p= t('admin.instances.content_policies.limited_federation_mode_description_html')
- if @instance.domain_allow
diff --git a/app/views/auth/setup/show.html.haml b/app/views/auth/setup/show.html.haml
index 913b0c9132..ab9b00bed6 100644
--- a/app/views/auth/setup/show.html.haml
+++ b/app/views/auth/setup/show.html.haml
@@ -17,6 +17,6 @@
= f.input :email, required: true, hint: false, input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'off' }
.actions
- = f.submit t('auth.resend_confirmation'), class: 'button'
+ = f.button :button, t('auth.resend_confirmation'), type: :submit, class: 'button timer-button', disabled: true
.form-footer= render 'auth/shared/links'
diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml
index 40fea2cf8d..0b5e5c8655 100644
--- a/app/views/layouts/mailer.html.haml
+++ b/app/views/layouts/mailer.html.haml
@@ -46,9 +46,9 @@
%p= t 'about.hosted_on', domain: site_hostname
%p
= link_to t('application_mailer.notification_preferences'), settings_preferences_notifications_url
- - if defined?(@type)
+ - if defined?(@unsubscribe_url)
·
- = link_to t('application_mailer.unsubscribe'), unsubscribe_url(token: @user.to_sgid(for: 'unsubscribe').to_s, type: @type)
+ = link_to t('application_mailer.unsubscribe'), @unsubscribe_url
%td.column-cell.text-right
= link_to root_url do
= image_tag full_pack_url('media/images/mailer/logo.png'), alt: 'Mastodon', height: 24
diff --git a/app/views/well_known/host_meta/show.xml.ruby b/app/views/well_known/host_meta/show.xml.ruby
index 25c5cf3948..125bfc9e06 100644
--- a/app/views/well_known/host_meta/show.xml.ruby
+++ b/app/views/well_known/host_meta/show.xml.ruby
@@ -2,6 +2,13 @@
doc = Ox::Document.new(version: '1.0')
+ins = Ox::Instruct.new(:xml).tap do |instruct|
+ instruct[:version] = '1.0'
+ instruct[:encoding] = 'UTF-8'
+end
+
+doc << ins
+
doc << Ox::Element.new('XRD').tap do |xrd|
xrd['xmlns'] = 'http://docs.oasis-open.org/ns/xri/xrd-1.0'
@@ -11,4 +18,4 @@ doc << Ox::Element.new('XRD').tap do |xrd|
end
end
-"#{Ox.dump(doc, effort: :tolerant)}".force_encoding('UTF-8')
+Ox.dump(doc, effort: :tolerant).force_encoding('UTF-8')
diff --git a/app/workers/scheduler/follow_recommendations_scheduler.rb b/app/workers/scheduler/follow_recommendations_scheduler.rb
index 17cf3f2cc3..ea59ad986b 100644
--- a/app/workers/scheduler/follow_recommendations_scheduler.rb
+++ b/app/workers/scheduler/follow_recommendations_scheduler.rb
@@ -4,7 +4,7 @@ class Scheduler::FollowRecommendationsScheduler
include Sidekiq::Worker
include Redisable
- sidekiq_options retry: 0
+ sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.day.to_i
# The maximum number of accounts that can be requested in one page from the
# API is 80, and the suggestions API does not allow pagination. This number
diff --git a/app/workers/scheduler/indexing_scheduler.rb b/app/workers/scheduler/indexing_scheduler.rb
index d622f5586e..2868a3b715 100644
--- a/app/workers/scheduler/indexing_scheduler.rb
+++ b/app/workers/scheduler/indexing_scheduler.rb
@@ -4,7 +4,7 @@ class Scheduler::IndexingScheduler
include Sidekiq::Worker
include Redisable
- sidekiq_options retry: 0
+ sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.day.to_i
IMPORT_BATCH_SIZE = 1000
SCAN_BATCH_SIZE = 10 * IMPORT_BATCH_SIZE
@@ -16,9 +16,7 @@ class Scheduler::IndexingScheduler
with_redis do |redis|
redis.sscan_each("chewy:queue:#{type.name}", count: SCAN_BATCH_SIZE).each_slice(IMPORT_BATCH_SIZE) do |ids|
type.import!(ids)
- redis.pipelined do |pipeline|
- pipeline.srem("chewy:queue:#{type.name}", ids)
- end
+ redis.srem("chewy:queue:#{type.name}", ids)
end
end
end
diff --git a/app/workers/scheduler/instance_refresh_scheduler.rb b/app/workers/scheduler/instance_refresh_scheduler.rb
index 59f91dc623..818682a5d2 100644
--- a/app/workers/scheduler/instance_refresh_scheduler.rb
+++ b/app/workers/scheduler/instance_refresh_scheduler.rb
@@ -3,7 +3,7 @@
class Scheduler::InstanceRefreshScheduler
include Sidekiq::Worker
- sidekiq_options retry: 0
+ sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.day.to_i
def perform
Instance.refresh
diff --git a/app/workers/scheduler/ip_cleanup_scheduler.rb b/app/workers/scheduler/ip_cleanup_scheduler.rb
index 8f607db037..f78c0584d7 100644
--- a/app/workers/scheduler/ip_cleanup_scheduler.rb
+++ b/app/workers/scheduler/ip_cleanup_scheduler.rb
@@ -6,7 +6,7 @@ class Scheduler::IpCleanupScheduler
IP_RETENTION_PERIOD = ENV.fetch('IP_RETENTION_PERIOD', 1.year).to_i.seconds.freeze
SESSION_RETENTION_PERIOD = ENV.fetch('SESSION_RETENTION_PERIOD', 1.year).to_i.seconds.freeze
- sidekiq_options retry: 0
+ sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.day.to_i
def perform
clean_ip_columns!
diff --git a/app/workers/scheduler/pghero_scheduler.rb b/app/workers/scheduler/pghero_scheduler.rb
index a756b13b93..49bf2db7a6 100644
--- a/app/workers/scheduler/pghero_scheduler.rb
+++ b/app/workers/scheduler/pghero_scheduler.rb
@@ -3,7 +3,7 @@
class Scheduler::PgheroScheduler
include Sidekiq::Worker
- sidekiq_options retry: 0
+ sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.day.to_i
def perform
PgHero.capture_space_stats
diff --git a/app/workers/scheduler/scheduled_statuses_scheduler.rb b/app/workers/scheduler/scheduled_statuses_scheduler.rb
index 3bf6300b3c..b5801248f2 100644
--- a/app/workers/scheduler/scheduled_statuses_scheduler.rb
+++ b/app/workers/scheduler/scheduled_statuses_scheduler.rb
@@ -3,7 +3,7 @@
class Scheduler::ScheduledStatusesScheduler
include Sidekiq::Worker
- sidekiq_options retry: 0
+ sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.day.to_i
def perform
publish_scheduled_statuses!
diff --git a/app/workers/scheduler/suspended_user_cleanup_scheduler.rb b/app/workers/scheduler/suspended_user_cleanup_scheduler.rb
index 87e22161bc..90feead676 100644
--- a/app/workers/scheduler/suspended_user_cleanup_scheduler.rb
+++ b/app/workers/scheduler/suspended_user_cleanup_scheduler.rb
@@ -16,7 +16,7 @@ class Scheduler::SuspendedUserCleanupScheduler
# has the capacity for it.
MAX_DELETIONS_PER_JOB = 10
- sidekiq_options retry: 0
+ sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.day.to_i
def perform
return if Sidekiq::Queue.new('pull').size > MAX_PULL_SIZE
diff --git a/app/workers/scheduler/user_cleanup_scheduler.rb b/app/workers/scheduler/user_cleanup_scheduler.rb
index 4aee7935a2..2d2efc731a 100644
--- a/app/workers/scheduler/user_cleanup_scheduler.rb
+++ b/app/workers/scheduler/user_cleanup_scheduler.rb
@@ -3,7 +3,7 @@
class Scheduler::UserCleanupScheduler
include Sidekiq::Worker
- sidekiq_options retry: 0
+ sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.day.to_i
def perform
clean_unconfirmed_accounts!
diff --git a/app/workers/scheduler/vacuum_scheduler.rb b/app/workers/scheduler/vacuum_scheduler.rb
index 9c040f6e47..1c9a2aabe3 100644
--- a/app/workers/scheduler/vacuum_scheduler.rb
+++ b/app/workers/scheduler/vacuum_scheduler.rb
@@ -3,7 +3,7 @@
class Scheduler::VacuumScheduler
include Sidekiq::Worker
- sidekiq_options retry: 0, lock: :until_executed
+ sidekiq_options retry: 0, lock: :until_executed, lock_ttl: 1.day.to_i
def perform
vacuum_operations.each do |operation|
diff --git a/config/application.rb b/config/application.rb
index 6f21efa8db..a3bd3dfa4e 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -46,7 +46,6 @@ require_relative '../lib/chewy/strategy/bypass_with_warning'
require_relative '../lib/webpacker/manifest_extensions'
require_relative '../lib/webpacker/helper_extensions'
require_relative '../lib/rails/engine_extensions'
-require_relative '../lib/action_controller/conditional_get_extensions'
require_relative '../lib/active_record/database_tasks_extensions'
require_relative '../lib/active_record/batches'
require_relative '../lib/simple_navigation/item_extensions'
@@ -199,7 +198,7 @@ module Mastodon
# We use our own middleware for this
config.public_file_server.enabled = false
- config.middleware.use PublicFileServerMiddleware if Rails.env.development? || ENV['RAILS_SERVE_STATIC_FILES'] == 'true'
+ config.middleware.use PublicFileServerMiddleware if Rails.env.development? || Rails.env.test? || ENV['RAILS_SERVE_STATIC_FILES'] == 'true'
config.middleware.use Rack::Attack
config.middleware.use Mastodon::RackMiddleware
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
index 9b9330a758..02ce23a075 100644
--- a/config/brakeman.ignore
+++ b/config/brakeman.ignore
@@ -57,75 +57,6 @@
],
"note": ""
},
- {
- "warning_type": "Mass Assignment",
- "warning_code": 105,
- "fingerprint": "874be88fedf4c680926845e9a588d3197765a6ccbfdd76466b44cc00151c612e",
- "check_name": "PermitAttributes",
- "message": "Potentially dangerous key allowed for mass assignment",
- "file": "app/controllers/api/v1/admin/reports_controller.rb",
- "line": 88,
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
- "code": "params.permit(:resolved, :account_id, :target_account_id)",
- "render_path": null,
- "location": {
- "type": "method",
- "class": "Api::V1::Admin::ReportsController",
- "method": "filter_params"
- },
- "user_input": ":account_id",
- "confidence": "High",
- "cwe_id": [
- 915
- ],
- "note": ""
- },
- {
- "warning_type": "Mass Assignment",
- "warning_code": 105,
- "fingerprint": "ab5035dd1a9f8c3a8d92fb2c37e8fe86fede4f87c91b71aa32e89c9eede602fc",
- "check_name": "PermitAttributes",
- "message": "Potentially dangerous key allowed for mass assignment",
- "file": "app/controllers/api/v1/notifications_controller.rb",
- "line": 77,
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
- "code": "params.permit(:account_id, :types => ([]), :exclude_types => ([]))",
- "render_path": null,
- "location": {
- "type": "method",
- "class": "Api::V1::NotificationsController",
- "method": "browserable_params"
- },
- "user_input": ":account_id",
- "confidence": "High",
- "cwe_id": [
- 915
- ],
- "note": ""
- },
- {
- "warning_type": "Mass Assignment",
- "warning_code": 105,
- "fingerprint": "b0dd0a26d24f5ede9713fe49210e9638be5f5548af9eee0b5a16fe9dbc80ffcd",
- "check_name": "PermitAttributes",
- "message": "Potentially dangerous key allowed for mass assignment",
- "file": "app/controllers/api/v2/search_controller.rb",
- "line": 42,
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
- "code": "params.permit(:type, :offset, :min_id, :max_id, :account_id, :following)",
- "render_path": null,
- "location": {
- "type": "method",
- "class": "Api::V2::SearchController",
- "method": "search_params"
- },
- "user_input": ":account_id",
- "confidence": "High",
- "cwe_id": [
- 915
- ],
- "note": ""
- },
{
"warning_type": "Cross-Site Scripting",
"warning_code": 4,
@@ -158,29 +89,6 @@
79
],
"note": ""
- },
- {
- "warning_type": "Mass Assignment",
- "warning_code": 105,
- "fingerprint": "d0511f0287aea4ed9511f5a744f880cb15af77a8ec88f81b7365b00b642cf427",
- "check_name": "PermitAttributes",
- "message": "Potentially dangerous key allowed for mass assignment",
- "file": "app/controllers/api/v1/reports_controller.rb",
- "line": 26,
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
- "code": "params.permit(:account_id, :comment, :category, :forward, :forward_to_domains => ([]), :status_ids => ([]), :rule_ids => ([]))",
- "render_path": null,
- "location": {
- "type": "method",
- "class": "Api::V1::ReportsController",
- "method": "report_params"
- },
- "user_input": ":account_id",
- "confidence": "High",
- "cwe_id": [
- 915
- ],
- "note": ""
}
],
"updated": "2023-07-12 11:20:51 -0400",
diff --git a/config/brakeman.yml b/config/brakeman.yml
new file mode 100644
index 0000000000..95ebc13865
--- /dev/null
+++ b/config/brakeman.yml
@@ -0,0 +1,3 @@
+---
+:skip_checks:
+ - CheckPermitAttributes
diff --git a/config/imagemagick/policy.xml b/config/imagemagick/policy.xml
index 1052476b31..e2aa202f27 100644
--- a/config/imagemagick/policy.xml
+++ b/config/imagemagick/policy.xml
@@ -22,6 +22,6 @@
-
+
diff --git a/config/initializers/2_limited_federation_mode.rb b/config/initializers/2_limited_federation_mode.rb
new file mode 100644
index 0000000000..d5f7652d58
--- /dev/null
+++ b/config/initializers/2_limited_federation_mode.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+Rails.application.configure do
+ config.x.limited_federation_mode = (ENV['LIMITED_FEDERATION_MODE'] || ENV['WHITELIST_MODE']) == 'true'
+
+ warn 'WARN: The environment variable WHITELIST_MODE has been replaced with LIMITED_FEDERATION_MODE, you should rename this environment variable in your configuration.' if ENV.key?('WHITELIST_MODE')
+end
diff --git a/config/initializers/2_whitelist_mode.rb b/config/initializers/2_whitelist_mode.rb
deleted file mode 100644
index 1cc6a8e724..0000000000
--- a/config/initializers/2_whitelist_mode.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# frozen_string_literal: true
-
-Rails.application.configure do
- config.x.whitelist_mode = (ENV['LIMITED_FEDERATION_MODE'] || ENV['WHITELIST_MODE']) == 'true'
-end
diff --git a/config/initializers/twitter_regex.rb b/config/initializers/twitter_regex.rb
index 94db57b66d..9ca4eb0da9 100644
--- a/config/initializers/twitter_regex.rb
+++ b/config/initializers/twitter_regex.rb
@@ -26,9 +26,9 @@ module Twitter::TwitterText
)
\)
/iox
- # rubocop:disable
+ # rubocop:disable Layout/LineLength
UCHARS = '\u{A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}\u{10000}-\u{1FFFD}\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}\u{40000}-\u{4FFFD}\u{50000}-\u{5FFFD}\u{60000}-\u{6FFFD}\u{70000}-\u{7FFFD}\u{80000}-\u{8FFFD}\u{90000}-\u{9FFFD}\u{A0000}-\u{AFFFD}\u{B0000}-\u{BFFFD}\u{C0000}-\u{CFFFD}\u{D0000}-\u{DFFFD}\u{E1000}-\u{EFFFD}\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}'
- # rubocop:enable
+ # rubocop:enable Layout/LineLength
REGEXEN[:valid_url_query_chars] = %r{[a-z0-9!?*'();:&=+$/%#\[\]\-_.,~|@\^#{UCHARS}]}iou
REGEXEN[:valid_url_query_ending_chars] = %r{[a-z0-9_&=#/\-#{UCHARS}]}iou
REGEXEN[:valid_url_path] = %r{(?:
diff --git a/config/navigation.rb b/config/navigation.rb
index 84532dec4c..6975653067 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -46,7 +46,7 @@ SimpleNavigation::Configuration.run do |navigation|
s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_path(origin: 'local'), highlights_on: %r{/admin/accounts|/admin/pending_accounts|/admin/disputes|/admin/users}, if: -> { current_user.can?(:manage_users) }
s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path, if: -> { current_user.can?(:manage_invites) }
s.item :follow_recommendations, safe_join([fa_icon('user-plus fw'), t('admin.follow_recommendations.title')]), admin_follow_recommendations_path, highlights_on: %r{/admin/follow_recommendations}, if: -> { current_user.can?(:manage_taxonomies) }
- s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_path(limited: whitelist_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.can?(:manage_federation) }
+ s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_path(limited: limited_federation_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.can?(:manage_federation) }
s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_path, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.can?(:manage_blocks) }
s.item :ip_blocks, safe_join([fa_icon('ban fw'), t('admin.ip_blocks.title')]), admin_ip_blocks_path, highlights_on: %r{/admin/ip_blocks}, if: -> { current_user.can?(:manage_blocks) }
s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_path, if: -> { current_user.can?(:view_audit_log) }
@@ -60,7 +60,7 @@ SimpleNavigation::Configuration.run do |navigation|
s.item :announcements, safe_join([fa_icon('bullhorn fw'), t('admin.announcements.title')]), admin_announcements_path, highlights_on: %r{/admin/announcements}, if: -> { current_user.can?(:manage_announcements) }
s.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_path, highlights_on: %r{/admin/custom_emojis}, if: -> { current_user.can?(:manage_custom_emojis) }
s.item :webhooks, safe_join([fa_icon('inbox fw'), t('admin.webhooks.title')]), admin_webhooks_path, highlights_on: %r{/admin/webhooks}, if: -> { current_user.can?(:manage_webhooks) }
- s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_path, highlights_on: %r{/admin/relays}, if: -> { !whitelist_mode? && current_user.can?(:manage_federation) }
+ s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_path, highlights_on: %r{/admin/relays}, if: -> { !limited_federation_mode? && current_user.can?(:manage_federation) }
end
n.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_path, link_html: { target: 'sidekiq' }, if: -> { current_user.can?(:view_devops) }
diff --git a/config/routes/api.rb b/config/routes/api.rb
index f3d414bdcc..4cce3ab2e8 100644
--- a/config/routes/api.rb
+++ b/config/routes/api.rb
@@ -126,6 +126,7 @@ namespace :api, format: false do
resource :privacy_policy, only: [:show], controller: 'instances/privacy_policies'
resource :extended_description, only: [:show], controller: 'instances/extended_descriptions'
resource :translation_languages, only: [:show], controller: 'instances/translation_languages'
+ resource :languages, only: [:show], controller: 'instances/languages'
resource :activity, only: [:show], controller: 'instances/activity'
end
diff --git a/config/sidekiq.yml b/config/sidekiq.yml
index 2278329a56..12c45c22a1 100644
--- a/config/sidekiq.yml
+++ b/config/sidekiq.yml
@@ -23,7 +23,7 @@
class: Scheduler::Trends::ReviewNotificationsScheduler
queue: scheduler
indexing_scheduler:
- every: '5m'
+ interval: 1 minute
class: Scheduler::IndexingScheduler
queue: scheduler
vacuum_scheduler:
diff --git a/config/webpack/rules/index.js b/config/webpack/rules/index.js
index 7e1857341c..b026857887 100644
--- a/config/webpack/rules/index.js
+++ b/config/webpack/rules/index.js
@@ -1,6 +1,7 @@
const babel = require('./babel');
const css = require('./css');
const file = require('./file');
+const materialIcons = require('./material_icons');
const nodeModules = require('./node_modules');
const tesseract = require('./tesseract');
@@ -8,6 +9,7 @@ const tesseract = require('./tesseract');
// https://webpack.js.org/concepts/loaders/#loader-features
// Lastly, process static files using file loader
module.exports = {
+ materialIcons,
file,
tesseract,
css,
diff --git a/config/webpack/rules/material_icons.js b/config/webpack/rules/material_icons.js
new file mode 100644
index 0000000000..f53445ef79
--- /dev/null
+++ b/config/webpack/rules/material_icons.js
@@ -0,0 +1,13 @@
+module.exports = {
+ test: /\.svg$/,
+ include: /node_modules\/@material-design-icons/,
+ issuer: /\.[jt]sx?$/,
+ use: [
+ {
+ loader: '@svgr/webpack',
+ options: {
+ svgo: false,
+ },
+ },
+ ],
+};
diff --git a/config/webpack/tests.js b/config/webpack/tests.js
index 1f7bdea9da..e6a8f1c2a9 100644
--- a/config/webpack/tests.js
+++ b/config/webpack/tests.js
@@ -5,5 +5,5 @@ const { merge } = require('webpack-merge');
const sharedConfig = require('./shared');
module.exports = merge(sharedConfig, {
- mode: 'development',
+ mode: 'production',
});
diff --git a/db/migrate/20180812173710_copy_status_stats.rb b/db/migrate/20180812173710_copy_status_stats.rb
index 20baeeca6b..52ab43b762 100644
--- a/db/migrate/20180812173710_copy_status_stats.rb
+++ b/db/migrate/20180812173710_copy_status_stats.rb
@@ -45,7 +45,7 @@ class CopyStatusStats < ActiveRecord::Migration[5.2]
# We cannot use bulk INSERT or overarching transactions here because of possible
# uniqueness violations that we need to skip over
Status.unscoped.select('id, reblogs_count, favourites_count, created_at, updated_at').find_each do |status|
- params = [[nil, status.id], [nil, status.reblogs_count], [nil, status.favourites_count], [nil, status.created_at], [nil, status.updated_at]]
+ params = [status.id, status.reblogs_count, status.favourites_count, status.created_at, status.updated_at]
exec_insert('INSERT INTO status_stats (status_id, reblogs_count, favourites_count, created_at, updated_at) VALUES ($1, $2, $3, $4, $5)', nil, params)
rescue ActiveRecord::RecordNotUnique
next
diff --git a/db/migrate/20181116173541_copy_account_stats.rb b/db/migrate/20181116173541_copy_account_stats.rb
index 88dc0c1fe9..30d07764ef 100644
--- a/db/migrate/20181116173541_copy_account_stats.rb
+++ b/db/migrate/20181116173541_copy_account_stats.rb
@@ -45,7 +45,7 @@ class CopyAccountStats < ActiveRecord::Migration[5.2]
# We cannot use bulk INSERT or overarching transactions here because of possible
# uniqueness violations that we need to skip over
Account.unscoped.select('id, statuses_count, following_count, followers_count, created_at, updated_at').find_each do |account|
- params = [[nil, account.id], [nil, account[:statuses_count]], [nil, account[:following_count]], [nil, account[:followers_count]], [nil, account.created_at], [nil, account.updated_at]]
+ params = [account.id, account[:statuses_count], account[:following_count], account[:followers_count], account.created_at, account.updated_at]
exec_insert('INSERT INTO account_stats (account_id, statuses_count, following_count, followers_count, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6)', nil, params)
rescue ActiveRecord::RecordNotUnique
next
diff --git a/db/post_migrate/20230803082451_add_unique_index_on_preview_cards_statuses.rb b/db/post_migrate/20230803082451_add_unique_index_on_preview_cards_statuses.rb
new file mode 100644
index 0000000000..936d7840eb
--- /dev/null
+++ b/db/post_migrate/20230803082451_add_unique_index_on_preview_cards_statuses.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+class AddUniqueIndexOnPreviewCardsStatuses < ActiveRecord::Migration[6.1]
+ disable_ddl_transaction!
+
+ def up
+ add_index :preview_cards_statuses, [:status_id, :preview_card_id], name: :preview_cards_statuses_pkey, algorithm: :concurrently, unique: true
+ rescue ActiveRecord::RecordNotUnique
+ deduplicate_and_reindex!
+ end
+
+ def down
+ remove_index :preview_cards_statuses, name: :preview_cards_statuses_pkey
+ end
+
+ private
+
+ def deduplicate_and_reindex!
+ deduplicate_preview_cards!
+
+ safety_assured { execute 'REINDEX INDEX preview_cards_statuses_pkey' }
+ rescue ActiveRecord::RecordNotUnique
+ retry
+ end
+
+ def deduplicate_preview_cards!
+ # Statuses should have only one preview card at most, even if that's not the database
+ # constraint we will end up with
+ duplicate_ids = select_all('SELECT status_id FROM preview_cards_statuses GROUP BY status_id HAVING count(*) > 1;').rows
+
+ duplicate_ids.each_slice(1000) do |ids|
+ # This one is tricky: since we don't have primary keys to keep only one record,
+ # use the physical `ctid`
+ safety_assured do
+ execute "DELETE FROM preview_cards_statuses p WHERE p.status_id IN (#{ids.join(', ')}) AND p.ctid NOT IN (SELECT q.ctid FROM preview_cards_statuses q WHERE q.status_id = p.status_id LIMIT 1)"
+ end
+ end
+ end
+end
diff --git a/db/post_migrate/20230803112520_add_primary_key_to_preview_cards_statuses_join_table.rb b/db/post_migrate/20230803112520_add_primary_key_to_preview_cards_statuses_join_table.rb
new file mode 100644
index 0000000000..34877ac672
--- /dev/null
+++ b/db/post_migrate/20230803112520_add_primary_key_to_preview_cards_statuses_join_table.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class AddPrimaryKeyToPreviewCardsStatusesJoinTable < ActiveRecord::Migration[6.1]
+ disable_ddl_transaction!
+
+ def up
+ safety_assured do
+ execute 'ALTER TABLE preview_cards_statuses ADD PRIMARY KEY USING INDEX preview_cards_statuses_pkey'
+ end
+ end
+
+ def down
+ safety_assured do
+ # I have found no way to demote the primary key to an index, instead, re-create the index
+ execute 'CREATE UNIQUE INDEX CONCURRENTLY preview_cards_statuses_pkey_tmp ON preview_cards_statuses (status_id, preview_card_id)'
+ execute 'ALTER TABLE preview_cards_statuses DROP CONSTRAINT preview_cards_statuses_pkey'
+ execute 'ALTER INDEX preview_cards_statuses_pkey_tmp RENAME TO preview_cards_statuses_pkey'
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 1a6db3fd21..bfcc97b37d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2023_07_24_160715) do
+ActiveRecord::Schema[7.0].define(version: 2023_08_03_112520) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -805,7 +805,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_24_160715) do
t.index ["url"], name: "index_preview_cards_on_url", unique: true
end
- create_table "preview_cards_statuses", id: false, force: :cascade do |t|
+ create_table "preview_cards_statuses", primary_key: ["status_id", "preview_card_id"], force: :cascade do |t|
t.bigint "preview_card_id", null: false
t.bigint "status_id", null: false
t.index ["status_id", "preview_card_id"], name: "index_preview_cards_statuses_on_status_id_and_preview_card_id"
diff --git a/docker-compose.yml b/docker-compose.yml
index e3fa9ae1e2..ae6aefb37f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -56,7 +56,7 @@ services:
web:
build: .
- image: ghcr.io/mastodon/mastodon:v4.1.5
+ image: ghcr.io/mastodon/mastodon:v4.1.6
restart: always
env_file: .env.production
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
@@ -77,7 +77,7 @@ services:
streaming:
build: .
- image: ghcr.io/mastodon/mastodon:v4.1.5
+ image: ghcr.io/mastodon/mastodon:v4.1.6
restart: always
env_file: .env.production
command: node ./streaming
@@ -95,7 +95,7 @@ services:
sidekiq:
build: .
- image: ghcr.io/mastodon/mastodon:v4.1.5
+ image: ghcr.io/mastodon/mastodon:v4.1.6
restart: always
env_file: .env.production
command: bundle exec sidekiq
diff --git a/lib/action_controller/conditional_get_extensions.rb b/lib/action_controller/conditional_get_extensions.rb
deleted file mode 100644
index 192ed341ea..0000000000
--- a/lib/action_controller/conditional_get_extensions.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-module ActionController
- module ConditionalGetExtensions
- def expires_in(*)
- # This backports a fix from Rails 7 so that a more private Cache-Control
- # can be overriden by calling expires_in on a specific controller action
- response.cache_control.delete(:no_store)
-
- super
- end
- end
-end
-
-ActionController::ConditionalGet.prepend(ActionController::ConditionalGetExtensions)
diff --git a/lib/mastodon/cli/statuses.rb b/lib/mastodon/cli/statuses.rb
index bd5b047077..0d6018a2b9 100644
--- a/lib/mastodon/cli/statuses.rb
+++ b/lib/mastodon/cli/statuses.rb
@@ -61,7 +61,7 @@ module Mastodon::CLI
# Skip accounts followed by local accounts
clean_followed_sql = 'AND NOT EXISTS (SELECT 1 FROM follows WHERE statuses.account_id = follows.target_account_id)' unless options[:clean_followed]
- ActiveRecord::Base.connection.exec_insert(<<-SQL.squish, 'SQL', [[nil, max_id]])
+ ActiveRecord::Base.connection.exec_insert(<<-SQL.squish, 'SQL', [max_id])
INSERT INTO statuses_to_be_deleted (id)
SELECT statuses.id FROM statuses WHERE deleted_at IS NULL AND NOT local AND uri IS NOT NULL AND (id < $1)
AND NOT EXISTS (SELECT 1 FROM statuses AS statuses1 WHERE statuses.id = statuses1.in_reply_to_id)
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index af2bb3ca54..58b09935be 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -13,7 +13,7 @@ module Mastodon
end
def patch
- 5
+ 6
end
def flags
diff --git a/lib/paperclip/media_type_spoof_detector_extensions.rb b/lib/paperclip/media_type_spoof_detector_extensions.rb
index a406ef312f..c8868d5e95 100644
--- a/lib/paperclip/media_type_spoof_detector_extensions.rb
+++ b/lib/paperclip/media_type_spoof_detector_extensions.rb
@@ -2,13 +2,15 @@
module Paperclip
module MediaTypeSpoofDetectorExtensions
+ MARCEL_MIME_TYPES = %w(audio/mpeg image/avif).freeze
+
def calculated_content_type
return @calculated_content_type if defined?(@calculated_content_type)
@calculated_content_type = type_from_file_command.chomp
# The `file` command fails to recognize some MP3 files as such
- @calculated_content_type = type_from_marcel if @calculated_content_type == 'application/octet-stream' && type_from_marcel == 'audio/mpeg'
+ @calculated_content_type = type_from_marcel if @calculated_content_type == 'application/octet-stream' && type_from_marcel.in?(MARCEL_MIME_TYPES)
@calculated_content_type
end
diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake
index 19a369079e..010caaf8ea 100644
--- a/lib/tasks/mastodon.rake
+++ b/lib/tasks/mastodon.rake
@@ -438,12 +438,7 @@ namespace :mastodon do
"#{key}=#{escaped}"
end.join("\n")
- generated_header = "# Generated with mastodon:setup on #{Time.now.utc}\n\n".dup
-
- if incompatible_syntax
- generated_header << "# Some variables in this file will be interpreted differently whether you are\n"
- generated_header << "# using docker-compose or not.\n\n"
- end
+ generated_header = generate_header(incompatible_syntax)
Rails.root.join('.env.production').write("#{generated_header}#{env_contents}\n")
@@ -538,6 +533,19 @@ namespace :mastodon do
puts "VAPID_PUBLIC_KEY=#{vapid_key.public_key}"
end
end
+
+ private
+
+ def generate_header(include_warning)
+ default_message = "# Generated with mastodon:setup on #{Time.now.utc}\n\n"
+
+ default_message.tap do |string|
+ if include_warning
+ string << "# Some variables in this file will be interpreted differently whether you are\n"
+ string << "# using docker-compose or not.\n\n"
+ end
+ end
+ end
end
def disable_log_stdout!
diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake
new file mode 100644
index 0000000000..8f2cbeea35
--- /dev/null
+++ b/lib/tasks/spec.rake
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+if Rake::Task.task_defined?('spec:system')
+ namespace :spec do
+ task :enable_system_specs do # rubocop:disable Rails/RakeEnvironment
+ ENV['RUN_SYSTEM_SPECS'] = 'true'
+ end
+ end
+
+ Rake::Task['spec:system'].enhance ['spec:enable_system_specs']
+end
diff --git a/lib/tasks/tests.rake b/lib/tasks/tests.rake
index 3c88ce4509..ef4d46fe09 100644
--- a/lib/tasks/tests.rake
+++ b/lib/tasks/tests.rake
@@ -63,6 +63,11 @@ namespace :tests do
puts 'Account domains not properly normalized'
exit(1)
end
+
+ unless Status.find(12).preview_cards.pluck(:url) == ['https://joinmastodon.org/']
+ puts 'Preview cards not deduplicated as expected'
+ exit(1)
+ end
end
desc 'Populate the database with test data for 2.4.3'
@@ -238,6 +243,11 @@ namespace :tests do
(10, 2, '@admin hey!', NULL, 1, 3, now(), now()),
(11, 1, '@user hey!', 10, 1, 3, now(), now());
+ INSERT INTO "statuses"
+ (id, account_id, text, created_at, updated_at)
+ VALUES
+ (12, 1, 'check out https://joinmastodon.org/', now(), now());
+
-- mentions (from previous statuses)
INSERT INTO "mentions"
@@ -326,6 +336,21 @@ namespace :tests do
(1, 6, 2, 'Follow', 2, now(), now()),
(2, 2, 1, 'Mention', 4, now(), now()),
(3, 1, 2, 'Mention', 5, now(), now());
+
+ -- preview cards
+
+ INSERT INTO "preview_cards"
+ (id, url, title, created_at, updated_at)
+ VALUES
+ (1, 'https://joinmastodon.org/', 'Mastodon - Decentralized social media', now(), now());
+
+ -- many-to-many association between preview cards and statuses
+
+ INSERT INTO "preview_cards_statuses"
+ (status_id, preview_card_id)
+ VALUES
+ (12, 1),
+ (12, 1);
SQL
end
end
diff --git a/package.json b/package.json
index 6009f7484b..be6c63ffa1 100644
--- a/package.json
+++ b/package.json
@@ -44,8 +44,10 @@
"@formatjs/intl-pluralrules": "^5.2.2",
"@gamestdio/websocket": "^0.3.2",
"@github/webauthn-json": "^2.1.1",
+ "@material-design-icons/svg": "^0.14.10",
"@rails/ujs": "^7.0.6",
"@reduxjs/toolkit": "^1.9.5",
+ "@svgr/webpack": "^5.5.0",
"abortcontroller-polyfill": "^1.7.5",
"array-includes": "^3.1.6",
"arrow-key-navigation": "^1.2.0",
diff --git a/spec/controllers/concerns/signature_verification_spec.rb b/spec/controllers/concerns/signature_verification_spec.rb
index 650cd21eaf..f8e44845d7 100644
--- a/spec/controllers/concerns/signature_verification_spec.rb
+++ b/spec/controllers/concerns/signature_verification_spec.rb
@@ -129,6 +129,37 @@ describe SignatureVerification do
end
end
+ context 'with non-normalized URL' do
+ before do
+ get :success
+
+ fake_request = Request.new(:get, 'http://test.host/subdir/../success')
+ fake_request.on_behalf_of(author)
+
+ request.headers.merge!(fake_request.headers)
+
+ allow(controller).to receive(:actor_refresh_key!).and_return(author)
+ end
+
+ describe '#build_signed_string' do
+ it 'includes the normalized request path' do
+ expect(controller.send(:build_signed_string)).to start_with "(request-target): get /success\n"
+ end
+ end
+
+ describe '#signed_request?' do
+ it 'returns true' do
+ expect(controller.signed_request?).to be true
+ end
+ end
+
+ describe '#signed_request_actor' do
+ it 'returns an account' do
+ expect(controller.signed_request_account).to eq author
+ end
+ end
+ end
+
context 'with request with unparsable Date header' do
before do
get :success
@@ -202,7 +233,7 @@ describe SignatureVerification do
request.headers.merge!(fake_request.headers)
- stub_request(:get, 'http://localhost:5000/actor#main-key').to_raise(Mastodon::HostValidationError)
+ stub_request(:get, 'http://localhost:5000/actor').to_raise(Mastodon::HostValidationError)
end
describe '#signed_request?' do
diff --git a/spec/fabricators_spec.rb b/spec/fabricators/fabricators_spec.rb
similarity index 100%
rename from spec/fabricators_spec.rb
rename to spec/fabricators/fabricators_spec.rb
diff --git a/spec/fixtures/files/600x400.avif b/spec/fixtures/files/600x400.avif
new file mode 100644
index 0000000000..f306942dbe
Binary files /dev/null and b/spec/fixtures/files/600x400.avif differ
diff --git a/spec/fixtures/files/600x400.heic b/spec/fixtures/files/600x400.heic
new file mode 100644
index 0000000000..1add5832a8
Binary files /dev/null and b/spec/fixtures/files/600x400.heic differ
diff --git a/spec/fixtures/files/600x400.jpeg b/spec/fixtures/files/600x400.jpeg
new file mode 100644
index 0000000000..1c20bea515
Binary files /dev/null and b/spec/fixtures/files/600x400.jpeg differ
diff --git a/spec/fixtures/files/600x400.png b/spec/fixtures/files/600x400.png
new file mode 100644
index 0000000000..3b8a2dee35
Binary files /dev/null and b/spec/fixtures/files/600x400.png differ
diff --git a/spec/fixtures/files/600x400.webp b/spec/fixtures/files/600x400.webp
new file mode 100644
index 0000000000..56fa5151c2
Binary files /dev/null and b/spec/fixtures/files/600x400.webp differ
diff --git a/spec/fixtures/requests/idn.txt b/spec/fixtures/requests/idn.txt
deleted file mode 100644
index 5d07f2b796..0000000000
--- a/spec/fixtures/requests/idn.txt
+++ /dev/null
@@ -1,483 +0,0 @@
-HTTP/1.1 200 OK
-Server: nginx
-Date: Sun, 23 Apr 2017 19:37:13 GMT
-Content-Type: text/html
-Content-Length: 38111
-Last-Modified: Wed, 20 Jul 2016 02:50:52 GMT
-Connection: keep-alive
-Accept-Ranges: bytes
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
中国域名网站
-
-
-
-
-
-
-
-
-
-
-
-
-
名站导航
-
-
-
-
-
-
-
-
-
-
-
-
-
-
中文域名简介
-
- “中国域名”是中文域名的一种,特指以“中国”为后缀的中文域名,是我国域名体系和全球互联网域名体系的重要组成部分。“中国”是在全球互联网上代表中国的中文顶级域名,于2010年7月正式纳入全球互联网域名体系,全球互联网域名体系,全球网民可通过联网计算机在世界任何国家和地区实现无障碍访问。“中国”域名在使用上和 .CN,相似属于互联网上的基础服务,基于域名可以提供WWW.EMAIL FTP等应用服务。
-
-
-
-
-
-
diff --git a/spec/helpers/admin/action_logs_helper_spec.rb b/spec/helpers/admin/action_logs_helper_spec.rb
deleted file mode 100644
index 4e9d08f09d..0000000000
--- a/spec/helpers/admin/action_logs_helper_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Admin::ActionLogsHelper do
-end
diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb
index f0861376b9..8ccfcacef2 100644
--- a/spec/lib/request_spec.rb
+++ b/spec/lib/request_spec.rb
@@ -4,7 +4,9 @@ require 'rails_helper'
require 'securerandom'
describe Request do
- subject { described_class.new(:get, 'http://example.com') }
+ subject { described_class.new(:get, url) }
+
+ let(:url) { 'http://example.com' }
describe '#headers' do
it 'returns user agent' do
@@ -92,6 +94,152 @@ describe Request do
expect { subject.perform }.to raise_error Mastodon::ValidationError
end
end
+
+ context 'with bare domain URL' do
+ let(:url) { 'http://example.com' }
+
+ before do
+ stub_request(:get, 'http://example.com')
+ end
+
+ it 'normalizes path' do
+ subject.perform do |response|
+ expect(response.request.uri.path).to eq '/'
+ end
+ end
+
+ it 'normalizes path used for request signing' do
+ subject.perform
+
+ headers = subject.instance_variable_get(:@headers)
+ expect(headers[Request::REQUEST_TARGET]).to eq 'get /'
+ end
+
+ it 'normalizes path used in request line' do
+ subject.perform do |response|
+ expect(response.request.headline).to eq 'GET / HTTP/1.1'
+ end
+ end
+ end
+
+ context 'with unnormalized URL' do
+ let(:url) { 'HTTP://EXAMPLE.com:80/foo%41%3A?bar=%41%3A#baz' }
+
+ before do
+ stub_request(:get, 'http://example.com/foo%41%3A?bar=%41%3A')
+ end
+
+ it 'normalizes scheme' do
+ subject.perform do |response|
+ expect(response.request.uri.scheme).to eq 'http'
+ end
+ end
+
+ it 'normalizes host' do
+ subject.perform do |response|
+ expect(response.request.uri.authority).to eq 'example.com'
+ end
+ end
+
+ it 'does not modify path' do
+ subject.perform do |response|
+ expect(response.request.uri.path).to eq '/foo%41%3A'
+ end
+ end
+
+ it 'does not modify query string' do
+ subject.perform do |response|
+ expect(response.request.uri.query).to eq 'bar=%41%3A'
+ end
+ end
+
+ it 'does not modify path used for request signing' do
+ subject.perform
+
+ headers = subject.instance_variable_get(:@headers)
+ expect(headers[Request::REQUEST_TARGET]).to eq 'get /foo%41%3A'
+ end
+
+ it 'does not modify path used in request line' do
+ subject.perform do |response|
+ expect(response.request.headline).to eq 'GET /foo%41%3A?bar=%41%3A HTTP/1.1'
+ end
+ end
+
+ it 'strips fragment' do
+ subject.perform do |response|
+ expect(response.request.uri.fragment).to be_nil
+ end
+ end
+ end
+
+ context 'with non-ASCII URL' do
+ let(:url) { 'http://éxample.com:81/föo?bär=1' }
+
+ before do
+ stub_request(:get, 'http://xn--xample-9ua.com:81/f%C3%B6o?b%C3%A4r=1')
+ end
+
+ it 'IDN-encodes host' do
+ subject.perform do |response|
+ expect(response.request.uri.authority).to eq 'xn--xample-9ua.com:81'
+ end
+ end
+
+ it 'IDN-encodes host in Host header' do
+ subject.perform do |response|
+ expect(response.request.headers['Host']).to eq 'xn--xample-9ua.com'
+ end
+ end
+
+ it 'percent-escapes path used for request signing' do
+ subject.perform
+
+ headers = subject.instance_variable_get(:@headers)
+ expect(headers[Request::REQUEST_TARGET]).to eq 'get /f%C3%B6o'
+ end
+
+ it 'normalizes path used in request line' do
+ subject.perform do |response|
+ expect(response.request.headline).to eq 'GET /f%C3%B6o?b%C3%A4r=1 HTTP/1.1'
+ end
+ end
+ end
+
+ context 'with redirecting URL' do
+ let(:url) { 'http://example.com/foo' }
+
+ before do
+ stub_request(:get, 'http://example.com/foo').to_return(status: 302, headers: { 'Location' => 'HTTPS://EXAMPLE.net/Bar' })
+ stub_request(:get, 'https://example.net/Bar').to_return(body: 'Lorem ipsum')
+ end
+
+ it 'resolves redirect' do
+ subject.perform do |response|
+ expect(response.body.to_s).to eq 'Lorem ipsum'
+ end
+
+ expect(a_request(:get, 'https://example.net/Bar')).to have_been_made
+ end
+
+ it 'normalizes destination scheme' do
+ subject.perform do |response|
+ expect(response.request.uri.scheme).to eq 'https'
+ end
+ end
+
+ it 'normalizes destination host' do
+ subject.perform do |response|
+ expect(response.request.uri.authority).to eq 'example.net'
+ end
+ end
+
+ it 'does modify path' do
+ subject.perform do |response|
+ expect(response.request.uri.path).to eq '/Bar'
+ end
+ end
+ end
end
describe "response's body_with_limit method" do
diff --git a/spec/mailers/notification_mailer_spec.rb b/spec/mailers/notification_mailer_spec.rb
index 636c2d4257..78a497c06b 100644
--- a/spec/mailers/notification_mailer_spec.rb
+++ b/spec/mailers/notification_mailer_spec.rb
@@ -3,21 +3,42 @@
require 'rails_helper'
RSpec.describe NotificationMailer do
- let(:receiver) { Fabricate(:user) }
+ 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 '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 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
let(:mention) { Mention.create!(account: receiver.account, status: foreign_status) }
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
+ it 'renders the subject' do
expect(mail.subject).to eq('You were mentioned by bob')
- expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
end
it 'renders the body' do
@@ -32,10 +53,10 @@ RSpec.describe NotificationMailer do
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
+ it 'renders the subject' do
expect(mail.subject).to eq('bob is now following you')
- expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
end
it 'renders the body' do
@@ -49,10 +70,10 @@ RSpec.describe NotificationMailer do
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
+ it 'renders the subject' do
expect(mail.subject).to eq('bob favorited your post')
- expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
end
it 'renders the body' do
@@ -67,10 +88,10 @@ RSpec.describe NotificationMailer do
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
+ it 'renders the subject' do
expect(mail.subject).to eq('bob boosted your post')
- expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
end
it 'renders the body' do
@@ -85,10 +106,10 @@ RSpec.describe NotificationMailer do
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].value).to eq("#{receiver.account.username} <#{receiver.email}>")
end
it 'renders the body' do
diff --git a/spec/models/account_alias_spec.rb b/spec/models/account_alias_spec.rb
deleted file mode 100644
index f0cd274ea9..0000000000
--- a/spec/models/account_alias_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe AccountAlias do
-end
diff --git a/spec/models/account_deletion_request_spec.rb b/spec/models/account_deletion_request_spec.rb
deleted file mode 100644
index 8bbfb695d3..0000000000
--- a/spec/models/account_deletion_request_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe AccountDeletionRequest do
-end
diff --git a/spec/models/account_moderation_note_spec.rb b/spec/models/account_moderation_note_spec.rb
deleted file mode 100644
index 9d683ca528..0000000000
--- a/spec/models/account_moderation_note_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe AccountModerationNote do
-end
diff --git a/spec/models/announcement_mute_spec.rb b/spec/models/announcement_mute_spec.rb
deleted file mode 100644
index 1937da3aa7..0000000000
--- a/spec/models/announcement_mute_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe AnnouncementMute do
-end
diff --git a/spec/models/announcement_reaction_spec.rb b/spec/models/announcement_reaction_spec.rb
deleted file mode 100644
index 43cc0e1489..0000000000
--- a/spec/models/announcement_reaction_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe AnnouncementReaction do
-end
diff --git a/spec/models/announcement_spec.rb b/spec/models/announcement_spec.rb
deleted file mode 100644
index 32d398213c..0000000000
--- a/spec/models/announcement_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Announcement do
-end
diff --git a/spec/models/backup_spec.rb b/spec/models/backup_spec.rb
deleted file mode 100644
index 1303117528..0000000000
--- a/spec/models/backup_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Backup do
-end
diff --git a/spec/models/conversation_mute_spec.rb b/spec/models/conversation_mute_spec.rb
deleted file mode 100644
index 3d5504a65c..0000000000
--- a/spec/models/conversation_mute_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ConversationMute do
-end
diff --git a/spec/models/custom_filter_keyword_spec.rb b/spec/models/custom_filter_keyword_spec.rb
deleted file mode 100644
index 09a36fa3ed..0000000000
--- a/spec/models/custom_filter_keyword_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe CustomFilterKeyword do
-end
diff --git a/spec/models/custom_filter_spec.rb b/spec/models/custom_filter_spec.rb
deleted file mode 100644
index f5e9b7ea96..0000000000
--- a/spec/models/custom_filter_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe CustomFilter do
-end
diff --git a/spec/models/device_spec.rb b/spec/models/device_spec.rb
deleted file mode 100644
index 4e7b8f2e20..0000000000
--- a/spec/models/device_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Device do
-end
diff --git a/spec/models/encrypted_message_spec.rb b/spec/models/encrypted_message_spec.rb
deleted file mode 100644
index c38142be1e..0000000000
--- a/spec/models/encrypted_message_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe EncryptedMessage do
-end
diff --git a/spec/models/featured_tag_spec.rb b/spec/models/featured_tag_spec.rb
deleted file mode 100644
index 58865f94a5..0000000000
--- a/spec/models/featured_tag_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe FeaturedTag 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 d437d170d3..0000000000
--- a/spec/models/follow_recommendation_suppression_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe FollowRecommendationSuppression do
-end
diff --git a/spec/models/list_account_spec.rb b/spec/models/list_account_spec.rb
deleted file mode 100644
index c9853d4a04..0000000000
--- a/spec/models/list_account_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ListAccount do
-end
diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb
deleted file mode 100644
index 621ad3968d..0000000000
--- a/spec/models/list_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe List do
-end
diff --git a/spec/models/login_activity_spec.rb b/spec/models/login_activity_spec.rb
deleted file mode 100644
index 2214d3ef39..0000000000
--- a/spec/models/login_activity_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe LoginActivity do
-end
diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb
index 90e4f2f47b..d142875aa9 100644
--- a/spec/models/media_attachment_spec.rb
+++ b/spec/models/media_attachment_spec.rb
@@ -84,7 +84,87 @@ RSpec.describe MediaAttachment, paperclip_processing: true do
end
end
- describe 'animated gif conversion' do
+ shared_examples 'static 600x400 image' do |content_type, extension|
+ after do
+ media.destroy
+ end
+
+ it 'saves media attachment' do
+ expect(media.persisted?).to be true
+ expect(media.file).to_not be_nil
+ end
+
+ it 'completes processing' do
+ expect(media.processing_complete?).to be true
+ end
+
+ it 'sets type' do
+ expect(media.type).to eq 'image'
+ end
+
+ it 'sets content type' do
+ expect(media.file_content_type).to eq content_type
+ end
+
+ it 'sets file extension' do
+ expect(media.file_file_name).to end_with extension
+ end
+
+ it 'strips original file name' do
+ expect(media.file_file_name).to_not start_with '600x400'
+ end
+
+ it 'sets meta for original' 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
+ end
+
+ it 'sets meta for thumbnail' do
+ 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 'jpeg' do
+ let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('600x400.jpeg')) }
+
+ it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg'
+ end
+
+ describe 'png' do
+ let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('600x400.png')) }
+
+ it_behaves_like 'static 600x400 image', 'image/png', '.png'
+ end
+
+ describe 'webp' do
+ let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('600x400.webp')) }
+
+ it_behaves_like 'static 600x400 image', 'image/webp', '.webp'
+ end
+
+ describe 'avif' do
+ let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('600x400.avif')) }
+
+ it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg'
+ end
+
+ describe 'heic' do
+ let(:media) { described_class.create(account: Fabricate(:account), 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) { described_class.create(account: Fabricate(:account), file: base64_attachment) }
+
+ it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg'
+ end
+
+ describe 'animated gif' do
let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('avatar.gif')) }
it 'sets type to gifv' do
@@ -101,7 +181,7 @@ RSpec.describe MediaAttachment, paperclip_processing: true do
end
end
- describe 'non-animated gif non-conversion' do
+ 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 },
@@ -172,37 +252,6 @@ RSpec.describe MediaAttachment, paperclip_processing: true do
end
end
- describe 'jpeg' do
- let(:media) { described_class.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) { described_class.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 = described_class.new(account: Fabricate(:account))
expect(media.valid?).to be false
diff --git a/spec/models/mute_spec.rb b/spec/models/mute_spec.rb
deleted file mode 100644
index 050083d0f8..0000000000
--- a/spec/models/mute_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Mute do
-end
diff --git a/spec/models/preview_card_spec.rb b/spec/models/preview_card_spec.rb
deleted file mode 100644
index 7d687d16ff..0000000000
--- a/spec/models/preview_card_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe PreviewCard 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 a31bf71cc0..0000000000
--- a/spec/models/preview_card_trend_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe PreviewCardTrend do
-end
diff --git a/spec/models/relay_spec.rb b/spec/models/relay_spec.rb
deleted file mode 100644
index 7ed49e7334..0000000000
--- a/spec/models/relay_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Relay do
-end
diff --git a/spec/models/scheduled_status_spec.rb b/spec/models/scheduled_status_spec.rb
deleted file mode 100644
index 286c17e696..0000000000
--- a/spec/models/scheduled_status_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe ScheduledStatus do
-end
diff --git a/spec/models/status_stat_spec.rb b/spec/models/status_stat_spec.rb
deleted file mode 100644
index 9679c836a7..0000000000
--- a/spec/models/status_stat_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe StatusStat do
-end
diff --git a/spec/models/status_trend_spec.rb b/spec/models/status_trend_spec.rb
deleted file mode 100644
index dbb3d4bb3f..0000000000
--- a/spec/models/status_trend_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe StatusTrend do
-end
diff --git a/spec/models/system_key_spec.rb b/spec/models/system_key_spec.rb
deleted file mode 100644
index 5bd630aaa5..0000000000
--- a/spec/models/system_key_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe SystemKey do
-end
diff --git a/spec/models/tag_follow_spec.rb b/spec/models/tag_follow_spec.rb
deleted file mode 100644
index 240147ecc8..0000000000
--- a/spec/models/tag_follow_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe TagFollow do
-end
diff --git a/spec/models/unavailable_domain_spec.rb b/spec/models/unavailable_domain_spec.rb
deleted file mode 100644
index b868779f2b..0000000000
--- a/spec/models/unavailable_domain_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe UnavailableDomain 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 ee0efbb431..0000000000
--- a/spec/models/user_invite_request_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe UserInviteRequest do
-end
diff --git a/spec/models/web/setting_spec.rb b/spec/models/web/setting_spec.rb
deleted file mode 100644
index 3182c67217..0000000000
--- a/spec/models/web/setting_spec.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe Web::Setting do
-end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 2645f74e40..d4ff79c51c 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -1,6 +1,14 @@
# frozen_string_literal: true
ENV['RAILS_ENV'] ||= 'test'
+
+# This needs to be defined before Rails is initialized
+RUN_SYSTEM_SPECS = ENV.fetch('RUN_SYSTEM_SPECS', false)
+
+if RUN_SYSTEM_SPECS
+ STREAMING_PORT = ENV.fetch('TEST_STREAMING_PORT', '4020')
+ ENV['STREAMING_API_BASE_URL'] = "http://localhost:#{STREAMING_PORT}"
+end
require File.expand_path('../config/environment', __dir__)
abort('The Rails environment is running in production mode!') if Rails.env.production?
@@ -15,10 +23,14 @@ require 'chewy/rspec'
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
+
Devise::Test::ControllerHelpers.module_eval do
alias_method :original_sign_in, :sign_in
@@ -56,6 +68,8 @@ module SignedRequestHelpers
end
RSpec.configure do |config|
+ # This is set before running spec:system, see lib/tasks/tests.rake
+ config.filter_run_excluding type: :system unless RUN_SYSTEM_SPECS
config.fixture_path = Rails.root.join('spec', 'fixtures')
config.use_transactional_fixtures = true
config.order = 'random'
@@ -83,8 +97,7 @@ RSpec.configure do |config|
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
@@ -95,6 +108,35 @@ 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
+ end
+
+ config.after :suite do
+ streaming_server_manager.stop
+ 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
+ example.run
+ end
+
+ self.use_transactional_tests = true
+ 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
@@ -105,6 +147,14 @@ RSpec.configure do |config|
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|
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 0000000000..8ab8bf99ce
--- /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/cache_spec.rb b/spec/requests/cache_spec.rb
index 902f21db4b..178d19ed0d 100644
--- a/spec/requests/cache_spec.rb
+++ b/spec/requests/cache_spec.rb
@@ -508,12 +508,12 @@ describe 'Caching behavior' do
context 'when enabling LIMITED_FEDERATION_MODE mode' do
around do |example|
ClimateControl.modify LIMITED_FEDERATION_MODE: 'true' do
- old_whitelist_mode = Rails.configuration.x.whitelist_mode
- Rails.configuration.x.whitelist_mode = true
+ old_limited_federation_mode = Rails.configuration.x.limited_federation_mode
+ Rails.configuration.x.limited_federation_mode = true
example.run
- Rails.configuration.x.whitelist_mode = old_whitelist_mode
+ Rails.configuration.x.limited_federation_mode = old_limited_federation_mode
end
end
diff --git a/spec/requests/mail_subscriptions_spec.rb b/spec/requests/mail_subscriptions_spec.rb
new file mode 100644
index 0000000000..cc6557cab0
--- /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/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb
index eba38b6998..f44cbb750c 100644
--- a/spec/services/fetch_link_card_service_spec.rb
+++ b/spec/services/fetch_link_card_service_spec.rb
@@ -5,96 +5,233 @@ 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'))
+ Rails.cache.write('oembed_endpoint:example.com', oembed_cache) if oembed_cache
+
subject.call(status)
end
context 'with a local status' do
- context 'with an IDN url' 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 'with an SJIS url' 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
+ it 'decodes the HTML' do
expect(status.preview_cards.first.title).to eq('SJISのページ')
end
end
- context 'with invalid SJIS url' 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
+ it 'decodes the HTML despite the wrong charset header' do
expect(status.preview_cards.first.title).to eq('SJISのページ')
end
end
- context 'with an koi8-r url' 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
+ it 'decodes the HTML' do
expect(status.preview_cards.first.title).to eq('Московя начинаетъ только въ XVI ст. привлекать внимане иностранцевъ.')
end
end
- context 'with a windows-1251 url' 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 'with a japanese path url' do
+ 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 'with a hyphen-suffixed url' 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 'with an isolated url' 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 'with a url that has a caret' do
- let(:status) { Fabricate(:status, text: 'test http://example.com/test?data=file.gpx^1') }
+ 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 '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 '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
@@ -104,13 +241,13 @@ RSpec.describe FetchLinkCardService, type: :service 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
https://github.com/qbi/WannaCry was sammeln. !
+ 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/unallow_domain_service_spec.rb b/spec/services/unallow_domain_service_spec.rb
index f27b6fdf39..19d40e7e86 100644
--- a/spec/services/unallow_domain_service_spec.rb
+++ b/spec/services/unallow_domain_service_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe UnallowDomainService, type: :service do
context 'with limited federation mode' do
before do
- allow(Rails.configuration.x).to receive(:whitelist_mode).and_return(true)
+ allow(Rails.configuration.x).to receive(:limited_federation_mode).and_return(true)
end
describe '#call' do
@@ -40,7 +40,7 @@ RSpec.describe UnallowDomainService, type: :service do
context 'without limited federation mode' do
before do
- allow(Rails.configuration.x).to receive(:whitelist_mode).and_return(false)
+ allow(Rails.configuration.x).to receive(:limited_federation_mode).and_return(false)
end
describe '#call' do
diff --git a/spec/services/unmute_service_spec.rb b/spec/services/unmute_service_spec.rb
deleted file mode 100644
index 236f837e2d..0000000000
--- a/spec/services/unmute_service_spec.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-RSpec.describe UnmuteService, type: :service do
- subject { described_class.new }
-end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 7b3af0f90b..dcbcad48e6 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -52,3 +52,80 @@ def expect_push_bulk_to_match(klass, matcher)
'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
diff --git a/spec/support/stories/profile_stories.rb b/spec/support/stories/profile_stories.rb
index de7ae17e63..2b345ddef1 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
@@ -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 0000000000..6faed6c808
--- /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/language_validator_spec.rb b/spec/validators/language_validator_spec.rb
new file mode 100644
index 0000000000..cb693dcd81
--- /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/url_validator_spec.rb b/spec/validators/url_validator_spec.rb
index f2220e32b0..4f32b7b399 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) { instance_double(Webhook, errors: errors) }
- let(:errors) { instance_double(ActiveModel::Errors, 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 'when not 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 'when 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).to_not 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/yarn.lock b/yarn.lock
index 2d794c43d0..dbe0d01ff3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -786,6 +786,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.22.5"
+"@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.22.5"
+
"@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"
@@ -931,7 +938,7 @@
"@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.22.4":
+"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.22.4":
version "7.22.9"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.9.tgz#57f17108eb5dfd4c5c25a44c1977eba1df310ac7"
integrity sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g==
@@ -1028,7 +1035,7 @@
"@babel/types" "^7.4.4"
esutils "^2.0.2"
-"@babel/preset-react@^7.22.3":
+"@babel/preset-react@^7.12.5", "@babel/preset-react@^7.22.3":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.22.5.tgz#c4d6058fbf80bccad02dd8c313a9aaa67e3c3dd6"
integrity sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ==
@@ -1111,7 +1118,7 @@
debug "^4.1.0"
globals "^11.1.0"
-"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.12.11", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
+"@babel/types@^7.0.0", "@babel/types@^7.0.0-beta.49", "@babel/types@^7.12.11", "@babel/types@^7.12.6", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe"
integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==
@@ -1687,6 +1694,11 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
+"@material-design-icons/svg@^0.14.10":
+ version "0.14.10"
+ resolved "https://registry.yarnpkg.com/@material-design-icons/svg/-/svg-0.14.10.tgz#25804b66d0740b0bf8d6841fa343dfdd60f22e82"
+ integrity sha512-rXxfqj5Su8i51aG8s8QRIe7mX1gB+C/ZCroLu3JvIsO3+Vx6PcWP97HLwIl7AQH/jYIHQlKq0E6OMqU91u5fCg==
+
"@nicolo-ribaudo/semver-v6@^6.3.3":
version "6.3.3"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz#ea6d23ade78a325f7a52750aab1526b02b628c29"
@@ -1878,6 +1890,109 @@
magic-string "^0.25.0"
string.prototype.matchall "^4.0.6"
+"@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"
@@ -2216,6 +2331,11 @@
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.5"
+ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df"
+ integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==
+
"@types/qs@*":
version "6.9.7"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
@@ -3044,6 +3164,17 @@ array.prototype.flatmap@^1.3.1:
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"
@@ -3055,6 +3186,18 @@ array.prototype.tosorted@^1.1.1:
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"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
@@ -3446,7 +3589,7 @@ bonjour@^3.5.0:
multicast-dns "^6.0.1"
multicast-dns-service-types "^1.1.0"
-boolbase@^1.0.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 sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
@@ -3762,7 +3905,7 @@ chalk@5.2.0:
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3"
integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==
-chalk@^2.0.0, chalk@^2.4.2:
+chalk@^2.0.0, 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==
@@ -3934,6 +4077,15 @@ co@^4.6.0:
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==
+coa@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3"
+ integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==
+ dependencies:
+ "@types/q" "^1.5.1"
+ chalk "^2.4.1"
+ q "^1.1.2"
+
cocoon-js-vanilla@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/cocoon-js-vanilla/-/cocoon-js-vanilla-1.3.0.tgz#1e53663f5d314e5e9b315b63eaf8ae701df113c0"
@@ -4285,6 +4437,21 @@ css-loader@^5.2.7:
schema-utils "^3.0.0"
semver "^7.3.5"
+css-select-base-adapter@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7"
+ integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==
+
+css-select@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef"
+ integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==
+ dependencies:
+ boolbase "^1.0.0"
+ css-what "^3.2.1"
+ domutils "^1.7.0"
+ nth-check "^1.0.2"
+
css-select@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
@@ -4296,6 +4463,22 @@ css-select@^5.1.0:
domutils "^3.0.1"
nth-check "^2.0.1"
+css-tree@1.0.0-alpha.37:
+ version "1.0.0-alpha.37"
+ resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22"
+ integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==
+ dependencies:
+ mdn-data "2.0.4"
+ source-map "^0.6.1"
+
+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.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"
@@ -4312,6 +4495,11 @@ css-tree@~2.2.0:
mdn-data "2.0.28"
source-map-js "^1.0.1"
+css-what@^3.2.1:
+ 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"
@@ -4375,6 +4563,13 @@ cssnano@^6.0.1:
cssnano-preset-default "^6.0.1"
lilconfig "^2.1.0"
+csso@^4.0.2:
+ 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.1.2"
+
csso@^5.0.5:
version "5.0.5"
resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6"
@@ -4748,6 +4943,14 @@ dom-helpers@^5.0.1, dom-helpers@^5.2.0:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
+dom-serializer@0:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
+ integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
+ dependencies:
+ 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"
@@ -4762,7 +4965,12 @@ domain-browser@^1.1.1:
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
-domelementtype@^2.3.0:
+domelementtype@1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
+ integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
+
+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==
@@ -4781,6 +4989,14 @@ domhandler@^5.0.2, domhandler@^5.0.3:
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"
+ integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
+ dependencies:
+ dom-serializer "0"
+ domelementtype "1"
+
domutils@^3.0.1:
version "3.1.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e"
@@ -4908,6 +5124,11 @@ enhanced-resolve@^5.12.0:
graceful-fs "^4.2.4"
tapable "^2.2.0"
+entities@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
+ integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
+
entities@^4.2.0, entities@^4.4.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
@@ -4934,6 +5155,51 @@ error-stack-parser@^2.0.6:
dependencies:
stackframe "^1.3.4"
+es-abstract@^1.17.2, es-abstract@^1.21.2:
+ 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.prototype.name "^1.1.5"
+ 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.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.3"
+ object-keys "^1.1.1"
+ 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.0, es-abstract@^1.20.4:
version "1.21.2"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff"
@@ -4974,6 +5240,11 @@ es-abstract@^1.19.0, es-abstract@^1.20.4:
unbox-primitive "^1.0.2"
which-typed-array "^1.1.9"
+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"
@@ -5850,7 +6121,7 @@ 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.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0:
+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==
@@ -6107,7 +6378,7 @@ has-proto@^1.0.1:
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, 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==
@@ -7946,6 +8217,11 @@ 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"
@@ -7956,6 +8232,11 @@ mdn-data@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==
+
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -8200,7 +8481,7 @@ 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.6:
+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==
@@ -8421,6 +8702,13 @@ npmlog@^7.0.1:
gauge "^5.0.0"
set-blocking "^2.0.0"
+nth-check@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
+ integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==
+ dependencies:
+ boolbase "~1.0.0"
+
nth-check@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
@@ -8500,6 +8788,17 @@ object.fromentries@^2.0.6:
define-properties "^1.1.4"
es-abstract "^1.20.4"
+object.getownpropertydescriptors@^2.1.0:
+ 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:
+ 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.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.2.tgz#f919e21fad4eb38a57bc6345b3afd496515c3f92"
@@ -8515,7 +8814,7 @@ object.pick@^1.3.0:
dependencies:
isobject "^3.0.1"
-object.values@^1.1.6:
+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==
@@ -9455,6 +9754,11 @@ pure-rand@^6.0.0:
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 sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==
+
qs@6.11.0:
version "6.11.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
@@ -10215,6 +10519,16 @@ rxjs@^7.8.0:
dependencies:
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"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
@@ -10266,6 +10580,11 @@ sass@^1.62.1:
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
+sax@~1.2.4:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
+ integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
+
saxes@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5"
@@ -10752,6 +11071,11 @@ ssri@^8.0.1:
dependencies:
minipass "^3.1.1"
+stable@^0.1.8:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
+ integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
+
stack-generator@^2.0.5:
version "2.0.10"
resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.10.tgz#8ae171e985ed62287d4f1ed55a1633b3fb53bb4d"
@@ -11171,11 +11495,35 @@ 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 sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==
+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==
+ dependencies:
+ chalk "^2.4.1"
+ coa "^2.0.2"
+ css-select "^2.0.0"
+ css-select-base-adapter "^0.1.1"
+ css-tree "1.0.0-alpha.37"
+ csso "^4.0.2"
+ js-yaml "^3.13.1"
+ mkdirp "~0.5.1"
+ object.values "^1.1.0"
+ sax "~1.2.4"
+ stable "^0.1.8"
+ 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"
@@ -11551,6 +11899,36 @@ type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
+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"
+
+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"
+
+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"
@@ -11661,6 +12039,11 @@ unpipe@1.0.0, unpipe@~1.0.0:
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
+unquote@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544"
+ 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"
@@ -11754,6 +12137,16 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+util.promisify@~1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee"
+ integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==
+ dependencies:
+ define-properties "^1.1.3"
+ es-abstract "^1.17.2"
+ has-symbols "^1.0.1"
+ object.getownpropertydescriptors "^2.1.0"
+
util@0.10.3:
version "0.10.3"
resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
@@ -12129,6 +12522,17 @@ which-module@^2.0.0:
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409"
integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
+which-typed-array@^1.1.10:
+ 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-typed-array@^1.1.9:
version "1.1.9"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6"