Skip to content

Commit 6190224

Browse files
committed
fix: fix some edge cases
1 parent 44a820f commit 6190224

2 files changed

Lines changed: 122 additions & 14 deletions

File tree

app/components/Package/Likes.vue

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,28 @@ const { user } = useAtproto()
3838
const authModal = useModal('auth-modal')
3939
const compactNumberFormatter = useCompactNumberFormatter()
4040
41-
const { data: likesData } = useFetch<PackageLikes>(() => `/api/social/likes/${props.packageName}`, {
42-
default: () => ({
43-
totalLikes: 0,
44-
userHasLiked: false,
45-
topLikedRank: null,
46-
}),
47-
server: false,
48-
})
41+
const { data: likesData, status: likeStatus } = useFetch<PackageLikes>(
42+
() => `/api/social/likes/${props.packageName}`,
43+
{
44+
default: () => ({
45+
totalLikes: 0,
46+
userHasLiked: false,
47+
topLikedRank: null,
48+
}),
49+
server: false,
50+
},
51+
)
52+
const isLoadingLikeData = computed(
53+
() => likeStatus.value === 'pending' || likeStatus.value === 'idle',
54+
)
4955
const isPackageLiked = computed(() => likesData.value?.userHasLiked ?? false)
5056
const topLikedRank = computed(() => likesData.value?.topLikedRank ?? null)
5157
const likeButtonLabel = computed(() =>
5258
isPackageLiked.value ? $t('package.likes.unlike') : $t('package.likes.like'),
5359
)
60+
const likeTooltipLabel = computed(() =>
61+
isLoadingLikeData.value ? $t('common.loading') : likeButtonLabel.value,
62+
)
5463
const topLikedBadgeLabel = computed(() =>
5564
topLikedRank.value == null
5665
? ''
@@ -105,6 +114,7 @@ const likeAction = async () => {
105114
? {
106115
...previousLikesState,
107116
...result.data,
117+
topLikedRank: result.data.topLikedRank ?? previousLikesState.topLikedRank,
108118
}
109119
: previousLikesState
110120
} catch {
@@ -117,7 +127,7 @@ const likeAction = async () => {
117127

118128
<template>
119129
<div class="relative inline-flex items-center">
120-
<TooltipApp :text="likeButtonLabel" position="bottom" class="items-center" strategy="fixed">
130+
<TooltipApp :text="likeTooltipLabel" position="bottom" class="items-center" strategy="fixed">
121131
<div class="relative inline-flex">
122132
<span v-if="showLikeFloat" :key="likeFloatKey" aria-hidden="true" class="like-float"
123133
>+1</span
@@ -139,7 +149,12 @@ const likeAction = async () => {
139149
aria-hidden="true"
140150
class="inline-block w-4 h-4"
141151
/>
142-
<span>{{ compactNumberFormatter.format(likesData?.totalLikes ?? 0) }}</span>
152+
<span
153+
v-if="isLoadingLikeData"
154+
class="i-svg-spinners:ring-resize w-3 h-3 my-0.5"
155+
aria-hidden="true"
156+
/>
157+
<span v-else>{{ compactNumberFormatter.format(likesData?.totalLikes ?? 0) }}</span>
143158
</ButtonBase>
144159
</div>
145160
</TooltipApp>
@@ -150,13 +165,13 @@ const likeAction = async () => {
150165
position="left"
151166
:offset="8"
152167
strategy="fixed"
153-
class="absolute [inset-inline-end:-0.5rem] top-[-0.4rem] z-1"
168+
class="top-liked-badge-anchor"
154169
>
155170
<NuxtLink
156171
:to="{ name: 'leaderboard-likes' }"
157172
:aria-label="topLikedBadgeLabel"
158173
data-testid="top-liked-badge"
159-
class="inline-flex items-center justify-center min-w-5 rounded-full px-1.5 py-0.5 text-2xs font-bold leading-none no-underline text-[var(--bg)] border border-[var(--bg)] bg-[radial-gradient(circle_at_28%_25%,rgb(255_255_255_/_0.34),transparent_38%),linear-gradient(135deg,color-mix(in_oklab,white_10%,var(--accent))_0%,var(--accent)_100%)] shadow-[0_1px_0_rgb(255_255_255_/_0.32)_inset,0_2px_6px_color-mix(in_oklab,var(--accent)_14%,transparent)] transition-shadow duration-[160ms] hover:shadow-[0_1px_0_rgb(255_255_255_/_0.38)_inset,0_4px_10px_color-mix(in_oklab,var(--accent)_18%,transparent)] focus-visible:outline-2 focus-visible:outline-fg focus-visible:outline-offset-2"
174+
class="top-liked-badge"
160175
>
161176
<span>{{ $t('package.likes.top_rank_label', { rank: topLikedRank }) }}</span>
162177
</NuxtLink>
@@ -177,6 +192,40 @@ const likeAction = async () => {
177192
animation: float-up 0.75s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
178193
}
179194
195+
.top-liked-badge-anchor {
196+
position: absolute;
197+
inset-inline-end: -0.5rem;
198+
top: -0.4rem;
199+
z-index: 1;
200+
}
201+
202+
.top-liked-badge {
203+
display: inline-flex;
204+
align-items: center;
205+
justify-content: center;
206+
min-width: 1.25rem;
207+
padding: 0.125rem 0.375rem;
208+
border: 1px solid var(--bg);
209+
border-radius: 9999px;
210+
background: var(--accent);
211+
color: var(--bg);
212+
font-size: 0.6875rem;
213+
font-weight: 700;
214+
line-height: 1;
215+
text-decoration: none;
216+
box-shadow: 0 2px 6px color-mix(in oklab, var(--accent) 14%, transparent);
217+
transition: box-shadow 160ms ease;
218+
}
219+
220+
.top-liked-badge:hover {
221+
box-shadow: 0 4px 10px color-mix(in oklab, var(--accent) 18%, transparent);
222+
}
223+
224+
.top-liked-badge:focus-visible {
225+
outline: 2px solid var(--fg);
226+
outline-offset: 2px;
227+
}
228+
180229
@media (prefers-reduced-motion: reduce) {
181230
.like-float {
182231
display: none;

test/nuxt/components/Package/Likes.spec.ts

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
1-
import { afterEach, describe, expect, it, vi } from 'vitest'
2-
import { mountSuspended, registerEndpoint } from '@nuxt/test-utils/runtime'
31
import type { VueWrapper } from '@vue/test-utils'
2+
import { mountSuspended, registerEndpoint } from '@nuxt/test-utils/runtime'
3+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
4+
import { ref } from 'vue'
45
import Likes from '~/components/Package/Likes.vue'
56

7+
const { mockUseAtproto } = vi.hoisted(() => ({
8+
mockUseAtproto: vi.fn(),
9+
}))
10+
11+
vi.mock('~/composables/atproto/useAtproto', () => ({
12+
useAtproto: mockUseAtproto,
13+
}))
14+
615
describe('PackageLikes', () => {
716
let wrapper: VueWrapper | undefined
817

18+
beforeEach(() => {
19+
mockUseAtproto.mockReturnValue({
20+
user: ref(null),
21+
pending: ref(false),
22+
logout: vi.fn(),
23+
})
24+
})
25+
926
afterEach(() => {
1027
wrapper?.unmount()
1128
})
@@ -55,4 +72,46 @@ describe('PackageLikes', () => {
5572

5673
expect(wrapper.find('[data-testid="top-liked-badge"]').exists()).toBe(false)
5774
})
75+
76+
it('keeps the top liked badge when a like response omits the rank', async () => {
77+
let likeRequests = 0
78+
79+
mockUseAtproto.mockReturnValue({
80+
user: ref({ handle: 'tester.test' }),
81+
pending: ref(false),
82+
logout: vi.fn(),
83+
})
84+
85+
registerEndpoint('/api/social/likes/svelte', () => ({
86+
totalLikes: 42,
87+
userHasLiked: false,
88+
topLikedRank: 3,
89+
}))
90+
registerEndpoint('/api/social/like', () => {
91+
likeRequests++
92+
93+
return {
94+
totalLikes: 43,
95+
userHasLiked: true,
96+
topLikedRank: null,
97+
}
98+
})
99+
100+
wrapper = await mountSuspended(Likes, {
101+
props: { packageName: 'svelte' },
102+
attachTo: document.body,
103+
})
104+
105+
await vi.waitFor(() => {
106+
expect(wrapper?.find('[data-testid="top-liked-badge"]').text()).toContain('#3')
107+
})
108+
109+
await wrapper.get('button').trigger('click')
110+
111+
await vi.waitFor(() => {
112+
expect(likeRequests).toBe(1)
113+
expect(wrapper?.text()).toContain('43')
114+
expect(wrapper?.find('[data-testid="top-liked-badge"]').text()).toContain('#3')
115+
})
116+
})
58117
})

0 commit comments

Comments
 (0)