Skip to content

Commit 9c5a6cb

Browse files
serhalpdanielroe
andauthored
fix(ui): don't show state before profile/likes finish loading (#1910)
Co-authored-by: Daniel Roe <daniel@roe.dev>
1 parent 9216309 commit 9c5a6cb

File tree

4 files changed

+95
-2
lines changed

4 files changed

+95
-2
lines changed

app/components/Package/LikeCard.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const { user } = useAtproto()
1717
1818
const authModal = useModal('auth-modal')
1919
20-
const { data: likesData } = useFetch(() => `/api/social/likes/${name.value}`, {
20+
const { data: likesData, status: likesStatus } = useFetch(() => `/api/social/likes/${name.value}`, {
2121
default: () => ({ totalLikes: 0, userHasLiked: false }),
2222
server: false,
2323
})
@@ -76,6 +76,7 @@ const likeAction = async () => {
7676
<div class="flex items-center gap-4 justify-between shrink-0">
7777
<ClientOnly>
7878
<TooltipApp
79+
v-if="likesStatus !== 'pending'"
7980
:text="likesData?.userHasLiked ? $t('package.likes.unlike') : $t('package.likes.like')"
8081
position="bottom"
8182
>

app/pages/profile/[identity]/index.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ if (!profile.value || profileError.value?.statusCode === 404) {
2323
})
2424
}
2525
26-
const { user } = useAtproto()
26+
const { user, pending: userPending } = useAtproto()
2727
const isEditing = ref(false)
2828
const displayNameInput = ref()
2929
const descriptionInput = ref()
@@ -84,6 +84,7 @@ const showInviteSection = computed(() => {
8484
profile.value.recordExists === false &&
8585
status.value === 'success' &&
8686
!likes.value?.records?.length &&
87+
!userPending.value &&
8788
user.value?.handle !== profile.value.handle
8889
)
8990
})

test/nuxt/components/PackageLikeCard.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,11 @@ describe('PackageLikeCard', () => {
4444

4545
expect(wrapper.find('span.truncate').text()).toBe('@scope/pkg')
4646
})
47+
48+
it('hides the like button entirely while like data is pending', async () => {
49+
wrapper = await mountLikeCard('https://npmx.dev/package/vue')
50+
51+
const button = wrapper.find('button')
52+
expect(button.exists()).toBe(false)
53+
})
4754
})
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { mockNuxtImport, mountSuspended, registerEndpoint } from '@nuxt/test-utils/runtime'
2+
import { describe, expect, it, vi, beforeEach } from 'vitest'
3+
4+
const { mockUseAtproto, mockUseProfileLikes } = vi.hoisted(() => ({
5+
mockUseAtproto: vi.fn(),
6+
mockUseProfileLikes: vi.fn(),
7+
}))
8+
9+
mockNuxtImport('useAtproto', () => mockUseAtproto)
10+
mockNuxtImport('useProfileLikes', () => mockUseProfileLikes)
11+
12+
import ProfilePage from '~/pages/profile/[identity]/index.vue'
13+
14+
registerEndpoint('/api/social/profile/test-handle', () => ({
15+
displayName: 'Test User',
16+
description: '',
17+
website: '',
18+
handle: 'test-handle',
19+
recordExists: false,
20+
}))
21+
22+
describe('Profile invite section', () => {
23+
beforeEach(() => {
24+
mockUseAtproto.mockReset()
25+
mockUseProfileLikes.mockReset()
26+
})
27+
28+
it('does not show invite section while auth is still loading', async () => {
29+
mockUseAtproto.mockReturnValue({
30+
user: ref(null),
31+
pending: ref(true),
32+
logout: vi.fn(),
33+
})
34+
35+
mockUseProfileLikes.mockReturnValue({
36+
data: ref({ records: [] }),
37+
status: ref('success'),
38+
})
39+
40+
const wrapper = await mountSuspended(ProfilePage, {
41+
route: '/profile/test-handle',
42+
})
43+
44+
expect(wrapper.text()).not.toContain("It doesn't look like they're using npmx yet")
45+
})
46+
47+
it('shows invite section after auth resolves for non-owner', async () => {
48+
mockUseAtproto.mockReturnValue({
49+
user: ref({ handle: 'other-user' }),
50+
pending: ref(false),
51+
logout: vi.fn(),
52+
})
53+
54+
mockUseProfileLikes.mockReturnValue({
55+
data: ref({ records: [] }),
56+
status: ref('success'),
57+
})
58+
59+
const wrapper = await mountSuspended(ProfilePage, {
60+
route: '/profile/test-handle',
61+
})
62+
63+
expect(wrapper.text()).toContain("It doesn't look like they're using npmx yet")
64+
})
65+
66+
it('does not show invite section for profile owner', async () => {
67+
mockUseAtproto.mockReturnValue({
68+
user: ref({ handle: 'test-handle' }),
69+
pending: ref(false),
70+
logout: vi.fn(),
71+
})
72+
73+
mockUseProfileLikes.mockReturnValue({
74+
data: ref({ records: [] }),
75+
status: ref('success'),
76+
})
77+
78+
const wrapper = await mountSuspended(ProfilePage, {
79+
route: '/profile/test-handle',
80+
})
81+
82+
expect(wrapper.text()).not.toContain("It doesn't look like they're using npmx yet")
83+
})
84+
})

0 commit comments

Comments
 (0)