diff --git a/app/components/OgImage/Package.vue b/app/components/OgImage/Package.vue index c3ee265f73..90cdd513f6 100644 --- a/app/components/OgImage/Package.vue +++ b/app/components/OgImage/Package.vue @@ -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 { @@ -75,6 +77,7 @@ try { class="h-full w-full flex flex-col justify-center px-20 bg-[#050505] text-[#fafafa] relative overflow-hidden" >
+
+
{{ resolvedVersion }} + + {{ $n(downloads.downloads) }}/wk - + + + - + + + - + + + meta.value?.stars ?? 0), forks: computed(() => meta.value?.forks ?? 0), watchers: computed(() => meta.value?.watchers ?? 0), diff --git a/playwright.config.ts b/playwright.config.ts index 64c942f1fa..6dc5642ea7 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -18,6 +18,8 @@ export default defineConfig({ 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', diff --git a/test/e2e/og-image.spec.ts b/test/e2e/og-image.spec.ts index be10fb0795..d34b62a144 100644 --- a/test/e2e/og-image.spec.ts +++ b/test/e2e/og-image.spec.ts @@ -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') @@ -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`, + }) }) }) } diff --git a/test/e2e/og-image.spec.ts-snapshots/og-image-for--.png b/test/e2e/og-image.spec.ts-snapshots/og-image-for--.png new file mode 100644 index 0000000000..677c2efdd6 Binary files /dev/null and b/test/e2e/og-image.spec.ts-snapshots/og-image-for--.png differ diff --git a/test/e2e/og-image.spec.ts-snapshots/og-image-for--package-nuxt-v-3-20-2.png b/test/e2e/og-image.spec.ts-snapshots/og-image-for--package-nuxt-v-3-20-2.png new file mode 100644 index 0000000000..e9c461df41 Binary files /dev/null and b/test/e2e/og-image.spec.ts-snapshots/og-image-for--package-nuxt-v-3-20-2.png differ diff --git a/test/nuxt/components/OgImagePackage.spec.ts b/test/nuxt/components/OgImagePackage.spec.ts new file mode 100644 index 0000000000..1414a03f27 --- /dev/null +++ b/test/nuxt/components/OgImagePackage.spec.ts @@ -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') + }) + }) +})