Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge upstream changes up to b1a584d252f4df4c2a1a9400d6588b4f36768216 #2976

Merged
merged 37 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f24b0e9
Fix exclusive lists interfering with notifications (#28162)
ClearlyClaire Feb 12, 2025
1005b2f
Fix accounts table long display name (#29316)
WebCoder49 Nov 22, 2024
fa2625a
Fix notification polling showing a loading bar in web UI (#32960)
Gargron Nov 19, 2024
17695ac
Fix featured tags for remote accounts not being kept up to date (#33372)
ClearlyClaire Feb 12, 2025
4da31b8
Fix intermittent failure on ap/activity/create spec timestamp check (…
mjankowski Jan 2, 2025
e63d0cf
Fix intermittent failure on ap/activity/update spec timestamp check (…
ClearlyClaire Jan 2, 2025
68eb62f
Fix processing of incoming notifications for unfilterable types (#33429)
ClearlyClaire Jan 2, 2025
e4f2a05
Fix preview card sizing in “Author attribution” in profile settings (…
ClearlyClaire Jan 7, 2025
7ad9581
Fix media preview height in compose form when 3 or more images are at…
ClearlyClaire Jan 13, 2025
ca39069
Further harden the warnings against changing encryption secrets (#33631)
ClearlyClaire Jan 17, 2025
7449683
Add `UserRole#bypass_block?` method for notification check (#32974)
mjankowski Nov 26, 2024
c7172b5
Change notifications from moderators to not be filtered (#33654)
ClearlyClaire Jan 21, 2025
11baa26
Collect errors in setup rake task (#33603)
mjankowski Jan 16, 2025
37b029d
Move clear environment portion of `mastodon:setup` to private method …
mjankowski Jan 16, 2025
94fed6e
Change `mastodon:setup` to prevent overwriting already-configured ser…
ClearlyClaire Jan 22, 2025
227d48d
Fix LDSignature tests (#33705)
ClearlyClaire Jan 23, 2025
2b148d3
Fix polls not being validated on edition (#33755)
ClearlyClaire Jan 28, 2025
3177152
Fix incorrect signature after HTTP redirect (#33757)
ClearlyClaire Jan 28, 2025
6e90688
Fix missing timeout options in `Request` class (#33769)
ClearlyClaire Jan 29, 2025
4f33b04
Fix flaky test in `/api/v2/notifications` tests (#33773)
ClearlyClaire Jan 29, 2025
b32a67f
Fix `tootctl feeds build` not building list timelines (#33783)
ClearlyClaire Jan 30, 2025
44e38b7
Fix emoji rewrite adding unnecessary curft to the DOM for most emoji …
ClearlyClaire Feb 3, 2025
2954c2f
Change preview cards to be shown when Content Warnings are expanded (…
ClearlyClaire Feb 4, 2025
452153d
Optimize timeline generation (#33839)
ClearlyClaire Feb 5, 2025
679e755
Fix filtering for lists (#33842)
ClearlyClaire Feb 5, 2025
08d2250
Fix handling of duplicate mentions in incoming status `Update` (#33911)
ClearlyClaire Feb 12, 2025
018b85e
Update dependency ruby-vips
ClearlyClaire Feb 24, 2025
b0f88be
Update dependencies `net-imap`, `net-smtp` and `timeout`
ClearlyClaire Feb 24, 2025
fb29ac0
Update dependency `rack`
ClearlyClaire Feb 24, 2025
10bcbf1
Update dependency `nokogiri`
ClearlyClaire Feb 24, 2025
8787077
Fix `GET /api/v2/notifications/:id` and `POST /api/v2/notifications/:…
ClearlyClaire Feb 25, 2025
b1a584d
New Crowdin Translations for stable-4.3 (automated) (#33999)
github-actions[bot] Feb 25, 2025
baac429
Merge commit 'b1a584d252f4df4c2a1a9400d6588b4f36768216' into glitch-s…
ClearlyClaire Feb 25, 2025
e3afbab
[Glitch] Fix accounts table long display name
WebCoder49 Nov 22, 2024
ccc4fcb
[Glitch] Fix notification polling showing a loading bar in web UI
Gargron Nov 19, 2024
bc95675
[Glitch] Fix preview card sizing in “Author attribution” in profile s…
ClearlyClaire Jan 7, 2025
fba7e85
[Glitch] Fix emoji rewrite adding unnecessary curft to the DOM for mo…
ClearlyClaire Feb 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.production.sample
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ OTP_SECRET=
# Must be available (and set to same values) for all server processes
# These are private/secret values, do not share outside hosting environment
# Use `bin/rails db:encryption:init` to generate fresh secrets
# Do not change these secrets once in use, as this would cause data loss and other issues
# Do NOT change these secrets once in use, as this would cause data loss and other issues
# ------------------
# ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=
# ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=
Expand Down
18 changes: 9 additions & 9 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ GEM
faraday (~> 1.0)
fast_blank (1.0.1)
fastimage (2.3.1)
ffi (1.16.3)
ffi (1.17.1)
ffi-compiler (1.3.2)
ffi (>= 1.15.5)
rake
Expand Down Expand Up @@ -410,7 +410,7 @@ GEM
llhttp-ffi (0.5.0)
ffi-compiler (~> 1.0)
rake (~> 13.0)
logger (1.6.1)
logger (1.6.6)
lograge (0.14.0)
actionpack (>= 4)
activesupport (>= 4)
Expand All @@ -437,7 +437,7 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2024.0820)
mini_mime (1.1.5)
mini_portile2 (2.8.7)
mini_portile2 (2.8.8)
minitest (5.25.1)
msgpack (1.7.2)
multi_json (1.15.0)
Expand All @@ -447,18 +447,18 @@ GEM
uri
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.4.15)
net-imap (0.4.19)
date
net-protocol
net-ldap (0.19.0)
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
timeout
net-smtp (0.5.0)
net-smtp (0.5.1)
net-protocol
nio4r (2.7.3)
nokogiri (1.16.8)
nokogiri (1.18.3)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
oj (3.16.6)
Expand Down Expand Up @@ -619,7 +619,7 @@ GEM
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.8.1)
rack (2.2.9)
rack (2.2.11)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-cors (2.0.2)
Expand Down Expand Up @@ -770,7 +770,7 @@ GEM
ruby-saml (1.17.0)
nokogiri (>= 1.13.10)
rexml
ruby-vips (2.2.2)
ruby-vips (2.2.3)
ffi (~> 1.12)
logger
ruby2_keywords (0.0.5)
Expand Down Expand Up @@ -842,7 +842,7 @@ GEM
test-prof (1.4.2)
thor (1.3.2)
tilt (2.4.0)
timeout (0.4.1)
timeout (0.4.3)
tpm-key_attestation (0.12.1)
bindata (~> 2.4)
openssl (> 2.0)
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/api/v2/notifications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def unread_count
end

def show
@notification = current_account.notifications.without_suspended.find_by!(group_key: params[:group_key])
@notification = current_account.notifications.without_suspended.by_group_key(params[:group_key]).take!
presenter = GroupedNotificationsPresenter.new(NotificationGroup.from_notifications([@notification]))
render json: presenter, serializer: REST::DedupNotificationGroupSerializer
end
Expand All @@ -57,7 +57,7 @@ def clear
end

def dismiss
current_account.notifications.where(group_key: params[:group_key]).destroy_all
current_account.notifications.by_group_key(params[:group_key]).destroy_all
render_empty
end

Expand Down
8 changes: 4 additions & 4 deletions app/controllers/concerns/signature_verification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def signed_headers

def verify_signature_strength!
raise SignatureVerificationError, 'Mastodon requires the Date header or (created) pseudo-header to be signed' unless signed_headers.include?('date') || signed_headers.include?('(created)')
raise SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(Request::REQUEST_TARGET) || signed_headers.include?('digest')
raise SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(HttpSignatureDraft::REQUEST_TARGET) || signed_headers.include?('digest')
raise SignatureVerificationError, 'Mastodon requires the Host header to be signed when doing a GET request' if request.get? && !signed_headers.include?('host')
raise SignatureVerificationError, 'Mastodon requires the Digest header to be signed when doing a POST request' if request.post? && !signed_headers.include?('digest')
end
Expand Down Expand Up @@ -155,14 +155,14 @@ def verify_signature(actor, signature, compare_signed_string)
def build_signed_string(include_query_string: true)
signed_headers.map do |signed_header|
case signed_header
when Request::REQUEST_TARGET
when HttpSignatureDraft::REQUEST_TARGET
if include_query_string
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.original_fullpath}"
"#{HttpSignatureDraft::REQUEST_TARGET}: #{request.method.downcase} #{request.original_fullpath}"
else
# Current versions of Mastodon incorrectly omit the query string from the (request-target) pseudo-header.
# Therefore, temporarily support such incorrect signatures for compatibility.
# TODO: remove eventually some time after release of the fixed version
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
"#{HttpSignatureDraft::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
end
when '(created)'
raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019'
Expand Down
3 changes: 3 additions & 0 deletions app/javascript/flavours/glitch/actions/notification_groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ export const pollRecentNotifications = createDataLoadingThunk(

return { notifications };
},
{
useLoadingBar: false,
},
);

export const processNewNotificationForGroups = createAppAsyncThunk(
Expand Down
32 changes: 16 additions & 16 deletions app/javascript/flavours/glitch/features/emoji/emoji.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,30 +97,30 @@ const emojifyTextNode = (node, customEmojis) => {
const { filename, shortCode } = unicodeMapping[unicode_emoji];
const title = shortCode ? `:${shortCode}:` : '';

replacement = document.createElement('picture');

const isSystemTheme = !!document.body?.classList.contains('theme-system');

if(isSystemTheme) {
let source = document.createElement('source');
source.setAttribute('media', '(prefers-color-scheme: dark)');
source.setAttribute('srcset', `${assetHost}/emoji/${emojiFilename(filename, "dark")}.svg`);
replacement.appendChild(source);
}
const theme = (isSystemTheme || document.body?.classList.contains('theme-mastodon-light')) ? 'light' : 'dark';

const imageFilename = emojiFilename(filename, theme);

let img = document.createElement('img');
const img = document.createElement('img');
img.setAttribute('draggable', 'false');
img.setAttribute('class', 'emojione');
img.setAttribute('alt', unicode_emoji);
img.setAttribute('title', title);
img.setAttribute('src', `${assetHost}/emoji/${imageFilename}.svg`);

let theme = "light";
if (isSystemTheme && imageFilename !== emojiFilename(filename, 'dark')) {
replacement = document.createElement('picture');

if(!isSystemTheme && !document.body?.classList.contains('skin-mastodon-light'))
theme = "dark";

img.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename, theme)}.svg`);
replacement.appendChild(img);
const source = document.createElement('source');
source.setAttribute('media', '(prefers-color-scheme: dark)');
source.setAttribute('srcset', `${assetHost}/emoji/${emojiFilename(filename, 'dark')}.svg`);
replacement.appendChild(source);
replacement.appendChild(img);
} else {
replacement = img;
}
}

// Add the processed-up-to-now string and the emoji replacement
Expand All @@ -135,7 +135,7 @@ const emojifyTextNode = (node, customEmojis) => {
};

const emojifyNode = (node, customEmojis) => {
for (const child of node.childNodes) {
for (const child of Array.from(node.childNodes)) {
switch(child.nodeType) {
case Node.TEXT_NODE:
emojifyTextNode(child, customEmojis);
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/flavours/glitch/styles/forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,10 @@ code {
}
}
}

.status-card {
contain: unset;
}
}

.block-icon {
Expand Down
1 change: 1 addition & 0 deletions app/javascript/flavours/glitch/styles/widgets.scss
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@

.accounts-table {
width: 100%;
table-layout: fixed;

.account {
padding: 0;
Expand Down
5 changes: 4 additions & 1 deletion app/javascript/mastodon/actions/notification_groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ export const pollRecentNotifications = createDataLoadingThunk(

return { notifications };
},
{
useLoadingBar: false,
},
);

export const processNewNotificationForGroups = createAppAsyncThunk(
Expand All @@ -152,7 +155,7 @@ export const processNewNotificationForGroups = createAppAsyncThunk(

const showInColumn =
activeFilter === 'all'
? notificationShows[notification.type]
? notificationShows[notification.type] !== false
: activeFilter === notification.type;

if (!showInColumn) return;
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/mastodon/components/status.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ class Status extends ImmutablePureComponent {
</Bundle>
);
}
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
} else if (status.get('card')) {
media = (
<Card
onOpenMedia={this.handleOpenMedia}
Expand Down
38 changes: 19 additions & 19 deletions app/javascript/mastodon/features/emoji/__tests__/emoji-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,40 +22,40 @@ describe('emoji', () => {

it('does unicode', () => {
expect(emojify('\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66')).toEqual(
'<picture><img draggable="false" class="emojione" alt="👩‍👩‍👦‍👦" title=":woman-woman-boy-boy:" src="/emoji/1f469-200d-1f469-200d-1f466-200d-1f466.svg"></picture>');
'<img draggable="false" class="emojione" alt="👩‍👩‍👦‍👦" title=":woman-woman-boy-boy:" src="/emoji/1f469-200d-1f469-200d-1f466-200d-1f466.svg">');
expect(emojify('👨‍👩‍👧‍👧')).toEqual(
'<picture><img draggable="false" class="emojione" alt="👨‍👩‍👧‍👧" title=":man-woman-girl-girl:" src="/emoji/1f468-200d-1f469-200d-1f467-200d-1f467.svg"></picture>');
expect(emojify('👩‍👩‍👦')).toEqual('<picture><img draggable="false" class="emojione" alt="👩‍👩‍👦" title=":woman-woman-boy:" src="/emoji/1f469-200d-1f469-200d-1f466.svg"></picture>');
'<img draggable="false" class="emojione" alt="👨‍👩‍👧‍👧" title=":man-woman-girl-girl:" src="/emoji/1f468-200d-1f469-200d-1f467-200d-1f467.svg">');
expect(emojify('👩‍👩‍👦')).toEqual('<img draggable="false" class="emojione" alt="👩‍👩‍👦" title=":woman-woman-boy:" src="/emoji/1f469-200d-1f469-200d-1f466.svg">');
expect(emojify('\u2757')).toEqual(
'<picture><img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"></picture>');
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg">');
});

it('does multiple unicode', () => {
expect(emojify('\u2757 #\uFE0F\u20E3')).toEqual(
'<picture><img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"></picture> <picture><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"></picture>');
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg">');
expect(emojify('\u2757#\uFE0F\u20E3')).toEqual(
'<picture><img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"></picture><picture><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"></picture>');
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg">');
expect(emojify('\u2757 #\uFE0F\u20E3 \u2757')).toEqual(
'<picture><img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"></picture> <picture><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"></picture> <picture><img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"></picture>');
'<img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"> <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg">');
expect(emojify('foo \u2757 #\uFE0F\u20E3 bar')).toEqual(
'foo <picture><img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"></picture> <picture><img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"></picture> bar');
'foo <img draggable="false" class="emojione" alt="❗" title=":exclamation:" src="/emoji/2757.svg"> <img draggable="false" class="emojione" alt="#️⃣" title=":hash:" src="/emoji/23-20e3.svg"> bar');
});

it('ignores unicode inside of tags', () => {
expect(emojify('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>')).toEqual('<p data-foo="\uD83D\uDC69\uD83D\uDC69\uD83D\uDC66"></p>');
});

it('does multiple emoji properly (issue 5188)', () => {
expect(emojify('👌🌈💕')).toEqual('<picture><img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg"></picture><picture><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg"></picture><picture><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg"></picture>');
expect(emojify('👌 🌈 💕')).toEqual('<picture><img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg"></picture> <picture><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg"></picture> <picture><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg"></picture>');
expect(emojify('👌🌈💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg"><img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg"><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg">');
expect(emojify('👌 🌈 💕')).toEqual('<img draggable="false" class="emojione" alt="👌" title=":ok_hand:" src="/emoji/1f44c.svg"> <img draggable="false" class="emojione" alt="🌈" title=":rainbow:" src="/emoji/1f308.svg"> <img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg">');
});

it('does an emoji that has no shortcode', () => {
expect(emojify('👁‍🗨')).toEqual('<picture><img draggable="false" class="emojione" alt="👁‍🗨" title="" src="/emoji/1f441-200d-1f5e8.svg"></picture>');
expect(emojify('👁‍🗨')).toEqual('<img draggable="false" class="emojione" alt="👁‍🗨" title="" src="/emoji/1f441-200d-1f5e8.svg">');
});

it('does an emoji whose filename is irregular', () => {
expect(emojify('↙️')).toEqual('<picture><img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg"></picture>');
expect(emojify('↙️')).toEqual('<img draggable="false" class="emojione" alt="↙️" title=":arrow_lower_left:" src="/emoji/2199.svg">');
});

it('avoid emojifying on invisible text', () => {
Expand All @@ -67,11 +67,11 @@ describe('emoji', () => {

it('avoid emojifying on invisible text with nested tags', () => {
expect(emojify('<span class="invisible">😄<span class="foo">bar</span>😴</span>😇'))
.toEqual('<span class="invisible">😄<span class="foo">bar</span>😴</span><picture><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg"></picture>');
.toEqual('<span class="invisible">😄<span class="foo">bar</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg">');
expect(emojify('<span class="invisible">😄<span class="invisible">😕</span>😴</span>😇'))
.toEqual('<span class="invisible">😄<span class="invisible">😕</span>😴</span><picture><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg"></picture>');
.toEqual('<span class="invisible">😄<span class="invisible">😕</span>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg">');
expect(emojify('<span class="invisible">😄<br>😴</span>😇'))
.toEqual('<span class="invisible">😄<br>😴</span><picture><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg"></picture>');
.toEqual('<span class="invisible">😄<br>😴</span><img draggable="false" class="emojione" alt="😇" title=":innocent:" src="/emoji/1f607.svg">');
});

it('does not emojify emojis with textual presentation VS15 character', () => {
Expand All @@ -81,17 +81,17 @@ describe('emoji', () => {

it('does a simple emoji properly', () => {
expect(emojify('♀♂'))
.toEqual('<picture><img draggable="false" class="emojione" alt="♀" title=":female_sign:" src="/emoji/2640.svg"></picture><picture><img draggable="false" class="emojione" alt="♂" title=":male_sign:" src="/emoji/2642.svg"></picture>');
.toEqual('<img draggable="false" class="emojione" alt="♀" title=":female_sign:" src="/emoji/2640.svg"><img draggable="false" class="emojione" alt="♂" title=":male_sign:" src="/emoji/2642.svg">');
});

it('does an emoji containing ZWJ properly', () => {
expect(emojify('💂‍♀️💂‍♂️'))
.toEqual('<picture><img draggable="false" class="emojione" alt="💂\u200D♀️" title=":female-guard:" src="/emoji/1f482-200d-2640-fe0f_border.svg"></picture><picture><img draggable="false" class="emojione" alt="💂\u200D♂️" title=":male-guard:" src="/emoji/1f482-200d-2642-fe0f_border.svg"></picture>');
.toEqual('<img draggable="false" class="emojione" alt="💂\u200D♀️" title=":female-guard:" src="/emoji/1f482-200d-2640-fe0f_border.svg"><img draggable="false" class="emojione" alt="💂\u200D♂️" title=":male-guard:" src="/emoji/1f482-200d-2642-fe0f_border.svg">');
});

it('keeps ordering as expected (issue fixed by PR 20677)', () => {
expect(emojify('<p>💕 <a class="hashtag" href="https://example.com/tags/foo" rel="nofollow noopener noreferrer" target="_blank">#<span>foo</span></a> test: foo.</p>'))
.toEqual('<p><picture><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg"></picture> <a class="hashtag" href="https://example.com/tags/foo" rel="nofollow noopener noreferrer" target="_blank">#<span>foo</span></a> test: foo.</p>');
expect(emojify('<p>💕 <a class="hashtag" href="https://example.com/tags/foo" rel="nofollow noopener" target="_blank">#<span>foo</span></a> test: foo.</p>'))
.toEqual('<p><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg"> <a class="hashtag" href="https://example.com/tags/foo" rel="nofollow noopener" target="_blank">#<span>foo</span></a> test: foo.</p>');
});
});
});
Loading
Loading