forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import { afterEach, describe, expect, it, vi } from 'vitest'
2import { mountSuspended } from '@nuxt/test-utils/runtime'
3import type { PackageComparisonData } from '~/composables/usePackageComparison'
4
5/**
6 * Helper to test usePackageComparison by wrapping it in a component.
7 * This is required because the composable uses useI18n which must be
8 * called inside a Vue component's setup function.
9 */
10async function usePackageComparisonInComponent(packageNames: string[]) {
11 // Create refs to capture the composable's return values
12 const capturedPackagesData = ref<(PackageComparisonData | null)[]>([]) as Ref<
13 (PackageComparisonData | null)[]
14 >
15 const capturedStatus = ref<'idle' | 'pending' | 'success' | 'error'>('idle') as Ref<
16 'idle' | 'pending' | 'success' | 'error'
17 >
18 let capturedGetFacetValues: (facet: ComparisonFacet) => (FacetValue | null)[]
19
20 const WrapperComponent = defineComponent({
21 setup() {
22 const { packagesData, status, getFacetValues } = usePackageComparison(packageNames)
23
24 // Sync values to captured refs
25 watchEffect(() => {
26 capturedPackagesData.value = [...packagesData.value]
27 capturedStatus.value = status.value
28 })
29 capturedGetFacetValues = getFacetValues
30
31 return () => h('div')
32 },
33 })
34
35 await mountSuspended(WrapperComponent)
36
37 return {
38 packagesData: capturedPackagesData,
39 status: capturedStatus,
40 getFacetValues: (facet: ComparisonFacet) => capturedGetFacetValues(facet),
41 }
42}
43
44describe('usePackageComparison', () => {
45 afterEach(() => {
46 vi.unstubAllGlobals()
47 })
48
49 describe('lastUpdated facet', () => {
50 it('uses version-specific publish date, not time.modified', async () => {
51 vi.stubGlobal(
52 '$fetch',
53 vi.fn().mockImplementation((url: string) => {
54 if (url.startsWith('https://registry.npmjs.org/')) {
55 return Promise.resolve({
56 'name': 'test-package',
57 'dist-tags': { latest: '2.0.0' },
58 'time': {
59 // This is the WRONG value - updated by metadata changes
60 'modified': '2024-12-01T00:00:00.000Z',
61 // This is the CORRECT value - actual publish date
62 '2.0.0': '2024-06-15T00:00:00.000Z',
63 },
64 'license': 'MIT',
65 'versions': {
66 '2.0.0': { dist: { unpackedSize: 15000 } },
67 },
68 })
69 }
70 return Promise.resolve(null)
71 }),
72 )
73
74 const { packagesData, status, getFacetValues } = await usePackageComparisonInComponent([
75 'test-package',
76 ])
77
78 await vi.waitFor(() => {
79 expect(status.value).toBe('success')
80 })
81
82 expect(packagesData.value[0]).not.toBeNull()
83
84 const values = getFacetValues('lastUpdated')
85 expect(values).toHaveLength(1)
86 expect(values[0]).not.toBeNull()
87
88 // Should use version-specific timestamp, NOT time.modified
89 expect(values[0]!.display).toBe('2024-06-15T00:00:00.000Z')
90 expect(values[0]!.raw).toBe(new Date('2024-06-15T00:00:00.000Z').getTime())
91 })
92
93 it('stores version-specific time in metadata', async () => {
94 vi.stubGlobal(
95 '$fetch',
96 vi.fn().mockImplementation((url: string) => {
97 if (url.startsWith('https://registry.npmjs.org/')) {
98 return Promise.resolve({
99 'name': 'test-package',
100 'dist-tags': { latest: '1.0.0' },
101 'time': {
102 'modified': '2025-01-01T00:00:00.000Z',
103 '1.0.0': '2023-03-20T00:00:00.000Z',
104 },
105 'license': 'MIT',
106 'versions': {
107 '1.0.0': { dist: { unpackedSize: 10000 } },
108 },
109 })
110 }
111 return Promise.resolve(null)
112 }),
113 )
114
115 const { packagesData, status } = await usePackageComparisonInComponent(['test-package'])
116
117 await vi.waitFor(() => {
118 expect(status.value).toBe('success')
119 })
120
121 const metadata = packagesData.value[0]?.metadata
122 expect(metadata?.lastUpdated).toBe('2023-03-20T00:00:00.000Z')
123 expect(metadata?.lastUpdated).not.toBe('2025-01-01T00:00:00.000Z')
124 })
125 })
126
127 describe('staleness detection', () => {
128 it('marks packages not published in 2+ years as stale', async () => {
129 vi.stubGlobal(
130 '$fetch',
131 vi.fn().mockImplementation((url: string) => {
132 if (url.startsWith('https://registry.npmjs.org/')) {
133 return Promise.resolve({
134 'name': 'old-package',
135 'dist-tags': { latest: '1.0.0' },
136 'time': {
137 '1.0.0': '2020-01-01T00:00:00.000Z', // More than 2 years ago
138 },
139 'license': 'MIT',
140 'versions': {
141 '1.0.0': { dist: { unpackedSize: 5000 } },
142 },
143 })
144 }
145 return Promise.resolve(null)
146 }),
147 )
148
149 const { status, getFacetValues } = await usePackageComparisonInComponent(['old-package'])
150
151 await vi.waitFor(() => {
152 expect(status.value).toBe('success')
153 })
154
155 const values = getFacetValues('lastUpdated')
156 expect(values[0]?.status).toBe('warning')
157 })
158
159 it('marks recently published packages as neutral', async () => {
160 vi.stubGlobal(
161 '$fetch',
162 vi.fn().mockImplementation((url: string) => {
163 if (url.startsWith('https://registry.npmjs.org/')) {
164 return Promise.resolve({
165 'name': 'fresh-package',
166 'dist-tags': { latest: '1.0.0' },
167 'time': {
168 '1.0.0': new Date().toISOString(), // Today
169 },
170 'license': 'MIT',
171 'versions': {
172 '1.0.0': { dist: { unpackedSize: 5000 } },
173 },
174 })
175 }
176 return Promise.resolve(null)
177 }),
178 )
179
180 const { status, getFacetValues } = await usePackageComparisonInComponent(['fresh-package'])
181
182 await vi.waitFor(() => {
183 expect(status.value).toBe('success')
184 })
185
186 const values = getFacetValues('lastUpdated')
187 expect(values[0]?.status).toBe('neutral')
188 })
189 })
190})