Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 19 additions & 7 deletions app/components/OgImage/Package.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ const { data: likes, refresh: refreshLikes } = useFetch(() => `/api/social/likes
const { stars, refresh: refreshRepoMeta } = useRepoMeta(repositoryUrl)

const formattedStars = computed(() =>
Intl.NumberFormat('en', {
notation: 'compact',
maximumFractionDigits: 1,
}).format(stars.value),
stars.value > 0
? Intl.NumberFormat('en', {
notation: 'compact',
maximumFractionDigits: 1,
}).format(stars.value)
: '',
)

try {
Expand All @@ -75,6 +77,7 @@ try {
class="h-full w-full flex flex-col justify-center px-20 bg-[#050505] text-[#fafafa] relative overflow-hidden"
>
<div class="relative z-10 flex flex-col gap-6">
<!-- Package name -->
<div class="flex items-start gap-4">
<div
class="flex items-center justify-center w-16 h-16 p-4 rounded-xl shadow-lg bg-gradient-to-tr from-[#3b82f6]"
Expand Down Expand Up @@ -107,6 +110,7 @@ try {
</h1>
</div>

<!-- Version -->
<div
class="flex items-center gap-5 text-4xl font-light text-[#a3a3a3]"
style="font-family: 'Geist Sans', sans-serif"
Expand All @@ -122,6 +126,8 @@ try {
>
{{ resolvedVersion }}
</span>

<!-- Downloads (if any) -->
<span v-if="downloads" class="flex items-center gap-2">
<svg
width="30"
Expand All @@ -139,7 +145,9 @@ try {
</svg>
<span>{{ $n(downloads.downloads) }}/wk</span>
</span>
<span v-if="pkg?.license" class="flex items-center gap-2">

<!-- License (if any) -->
<span v-if="pkg?.license" class="flex items-center gap-2" data-testid="license">
<svg
viewBox="0 0 32 32"
:fill="primaryColor"
Expand All @@ -162,7 +170,9 @@ try {
{{ pkg.license }}
</span>
</span>
<span class="flex items-center gap-2">

<!-- Stars (if any) -->
<span v-if="formattedStars" class="flex items-center gap-2" data-testid="stars">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32 32"
Expand All @@ -179,7 +189,9 @@ try {
{{ formattedStars }}
</span>
</span>
<span class="flex items-center gap-2">

<!-- Likes (if any) -->
<span v-if="likes.totalLikes > 0" class="flex items-center gap-2" data-testid="likes">
<svg
width="32"
height="32"
Expand Down
2 changes: 2 additions & 0 deletions app/composables/useRepoMeta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,8 @@ export function useRepoMeta(repositoryUrl: MaybeRefOrGetter<string | null | unde
repoRef,
meta,

// TODO(serhalp): Consider removing the zero fallback so callers can make a distinction between
// "unresolved data" and "zero value"
stars: computed(() => meta.value?.stars ?? 0),
forks: computed(() => meta.value?.forks ?? 0),
watchers: computed(() => meta.value?.watchers ?? 0),
Expand Down
2 changes: 2 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export default defineConfig<ConfigOptions>({
reuseExistingServer: false,
timeout: 60_000,
},
// We currently only test on one browser on one platform
snapshotPathTemplate: '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{ext}',
use: {
baseURL,
trace: 'on-first-retry',
Expand Down
8 changes: 6 additions & 2 deletions test/e2e/og-image.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { expect, test } from './test-utils'

// TODO(serhalp): The nuxt@3.20.2 fixture has no stars. Update fixture to have stars coverage here.
const paths = ['/', '/package/nuxt/v/3.20.2']

for (const path of paths) {
test.describe(path, () => {
test.skip(`og image for ${path}`, async ({ page, goto, baseURL }) => {
test(`og image for ${path}`, async ({ page, goto, baseURL }) => {
await goto(path, { waitUntil: 'domcontentloaded' })

const ogImageUrl = await page.locator('meta[property="og:image"]').getAttribute('content')
Expand All @@ -19,7 +21,9 @@ for (const path of paths) {
expect(response.headers()['content-type']).toContain('image/png')

const imageBuffer = await response.body()
expect(imageBuffer).toMatchSnapshot({ name: `og-image-for-${path.replace(/\//g, '-')}.png` })
expect(imageBuffer).toMatchSnapshot({
name: `og-image-for-${path.replace(/\//g, '-')}.png`,
})
})
})
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
160 changes: 160 additions & 0 deletions test/nuxt/components/OgImagePackage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { mockNuxtImport, mountSuspended, registerEndpoint } from '@nuxt/test-utils/runtime'
import { describe, expect, it, vi, beforeEach } from 'vitest'

const { mockUseResolvedVersion, mockUsePackageDownloads, mockUsePackage, mockUseRepoMeta } =
vi.hoisted(() => ({
mockUseResolvedVersion: vi.fn(),
mockUsePackageDownloads: vi.fn(),
mockUsePackage: vi.fn(),
mockUseRepoMeta: vi.fn(),
}))

mockNuxtImport('useResolvedVersion', () => mockUseResolvedVersion)
mockNuxtImport('usePackageDownloads', () => mockUsePackageDownloads)
mockNuxtImport('usePackage', () => mockUsePackage)
mockNuxtImport('useRepoMeta', () => mockUseRepoMeta)
mockNuxtImport('normalizeGitUrl', () => () => 'https://github.com/test/repo')

import OgImagePackage from '~/components/OgImage/Package.vue'

describe('OgImagePackage', () => {
const baseProps = {
name: 'test-package',
version: '1.0.0',
}

function setupMocks(
overrides: {
stars?: number
totalLikes?: number
downloads?: number | null
license?: string | null
packageName?: string
} = {},
) {
const {
stars = 0,
totalLikes = 0,
downloads = 1000,
license = 'MIT',
packageName = 'test-package',
} = overrides

mockUseResolvedVersion.mockReturnValue({
data: ref('1.0.0'),
status: ref('success'),
error: ref(null),
})

mockUsePackageDownloads.mockReturnValue({
data: downloads != null ? ref({ downloads }) : ref(null),
refresh: vi.fn().mockResolvedValue(undefined),
})

mockUsePackage.mockReturnValue({
data: ref({
name: packageName,
license,
requestedVersion: {
repository: { url: 'git+https://github.com/test/repo.git' },
},
}),
refresh: vi.fn().mockResolvedValue(undefined),
})

mockUseRepoMeta.mockReturnValue({
stars: computed(() => stars),
refresh: vi.fn().mockResolvedValue(undefined),
})

// Mock the likes API endpoint used by useFetch
registerEndpoint(`/api/social/likes/${packageName}`, () => ({
totalLikes,
userHasLiked: false,
}))
}

beforeEach(() => {
mockUseResolvedVersion.mockReset()
mockUsePackageDownloads.mockReset()
mockUsePackage.mockReset()
mockUseRepoMeta.mockReset()
})

it('renders the package name and version', async () => {
setupMocks({ packageName: 'vue' })

const component = await mountSuspended(OgImagePackage, {
props: { ...baseProps, name: 'vue' },
})

expect(component.text()).toContain('vue')
expect(component.text()).toContain('1.0.0')
})

describe('license', () => {
it('renders the license when present', async () => {
setupMocks({ license: 'MIT' })

const component = await mountSuspended(OgImagePackage, {
props: baseProps,
})

expect(component.text()).toContain('MIT')
})

it('hides the license section when license is missing', async () => {
setupMocks({ license: null })

const component = await mountSuspended(OgImagePackage, {
props: baseProps,
})

expect(component.find('[data-testid="license"]').exists()).toBe(false)
})
})

describe('stars', () => {
it('hides stars section when count is 0', async () => {
setupMocks({ stars: 0 })

const component = await mountSuspended(OgImagePackage, {
props: baseProps,
})

expect(component.find('[data-testid="stars"]').exists()).toBe(false)
})

it('shows formatted stars when count is positive', async () => {
setupMocks({ stars: 45200 })

const component = await mountSuspended(OgImagePackage, {
props: baseProps,
})

expect(component.text()).toContain('45.2K')
})
})

describe('likes', () => {
it('hides likes section when totalLikes is 0', async () => {
setupMocks({ totalLikes: 0 })

const component = await mountSuspended(OgImagePackage, {
props: baseProps,
})

expect(component.find('[data-testid="likes"]').exists()).toBe(false)
})

it('shows likes section when totalLikes is positive', async () => {
setupMocks({ totalLikes: 42 })

const component = await mountSuspended(OgImagePackage, {
props: baseProps,
})

expect(component.text()).toContain('42')
})
})
})
Loading