forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import { expect, test } from './test-utils'
2
3test.describe('Compare Page', () => {
4 test('no-dep column renders separately from package columns', async ({ page, goto }) => {
5 await goto('/compare?packages=vue,__no_dependency__', { waitUntil: 'hydration' })
6
7 const grid = page.locator('.comparison-grid')
8 await expect(grid).toBeVisible({ timeout: 15000 })
9
10 // Should have the no-dep column with special styling
11 const noDepColumn = grid.locator('.comparison-cell-nodep')
12 await expect(noDepColumn).toBeVisible()
13
14 // The no-dep column should not contain a link
15 await expect(noDepColumn.locator('a')).toHaveCount(0)
16 })
17
18 test('no-dep column is always last even when packages are added after', async ({
19 page,
20 goto,
21 }) => {
22 // Start with vue and no-dep
23 await goto('/compare?packages=vue,__no_dependency__', { waitUntil: 'hydration' })
24
25 const grid = page.locator('.comparison-grid')
26 await expect(grid).toBeVisible({ timeout: 15000 })
27
28 // Add another package via the input
29 const input = page.locator('#package-search')
30 await input.fill('nuxt')
31
32 // Wait for search results and click on nuxt
33 const nuxtOption = page.locator('button:has-text("nuxt")').first()
34 await expect(nuxtOption).toBeVisible({ timeout: 10000 })
35 await nuxtOption.click()
36
37 // URL should have no-dep at the end, not in the middle
38 await expect(page).toHaveURL(/packages=vue,nuxt,__no_dependency__/)
39
40 // Verify column order in the grid: vue, nuxt, then no-dep
41 const headerLinks = grid.locator('.comparison-cell-header a.truncate')
42 await expect(headerLinks).toHaveCount(2)
43 await expect(headerLinks.nth(0)).toContainText('vue')
44 await expect(headerLinks.nth(1)).toContainText('nuxt')
45
46 // No-dep should still be visible as the last column
47 const noDepColumn = grid.locator('.comparison-cell-nodep')
48 await expect(noDepColumn).toBeVisible()
49 })
50})
51
52test.describe('Search Pages', () => {
53 test('/search?q=vue → keyboard navigation (arrow keys + enter)', async ({ page, goto }) => {
54 await goto('/search?q=vue', { waitUntil: 'hydration' })
55
56 await expect(page.locator('text=/found \\d+|showing \\d+/i').first()).toBeVisible({
57 timeout: 15000,
58 })
59
60 const firstResult = page.locator('[data-result-index="0"]').first()
61 await expect(firstResult).toBeVisible()
62
63 // Global keyboard navigation works regardless of focus
64 // ArrowDown selects the next result
65 await page.keyboard.press('ArrowDown')
66
67 // ArrowUp selects the previous result
68 await page.keyboard.press('ArrowUp')
69
70 // Enter navigates to the selected result
71 // URL is /package/vue or /org/vue or /user/vue. Not /vue
72 await page.keyboard.press('Enter')
73 await expect(page).toHaveURL(/\/(package|org|user)\/vue/)
74 })
75
76 test('/search?q=vue → "/" focuses the search input from results', async ({ page, goto }) => {
77 await goto('/search?q=vue', { waitUntil: 'hydration' })
78
79 await expect(page.locator('text=/found \\d+|showing \\d+/i').first()).toBeVisible({
80 timeout: 15000,
81 })
82
83 await page.locator('[data-result-index="0"]').first().focus()
84 await page.keyboard.press('/')
85 await expect(page.locator('input[type="search"]')).toBeFocused()
86 })
87
88 test('/ (homepage) → search, keeps focus on search input', async ({ page, goto }) => {
89 await goto('/', { waitUntil: 'hydration' })
90
91 const homeSearchInput = page.locator('#home-search')
92 await homeSearchInput.click()
93 await page.keyboard.type('vue')
94
95 // Wait for navigation to /search (debounce is 250ms)
96 await expect(page).toHaveURL(/\/search/, { timeout: 10000 })
97
98 await expect(page.locator('[data-result-index="0"]').first()).toBeVisible({
99 timeout: 15000,
100 })
101
102 // Home search input should be gone (we're on /search now)
103 await expect(homeSearchInput).not.toBeVisible()
104
105 // Header search input should now exist and be focused
106 const headerSearchInput = page.locator('#header-search')
107 await expect(headerSearchInput).toBeVisible()
108 await expect(headerSearchInput).toBeFocused()
109 })
110
111 test('/settings → search, keeps focus on search input', async ({ page, goto }) => {
112 await goto('/settings', { waitUntil: 'hydration' })
113
114 const searchInput = page.locator('input[type="search"]')
115 await expect(searchInput).toBeVisible()
116
117 await searchInput.click()
118 await searchInput.fill('vue')
119
120 await expect(page).toHaveURL(/\/search/, { timeout: 10000 })
121
122 await expect(page.locator('[data-result-index="0"]').first()).toBeVisible({
123 timeout: 15000,
124 })
125
126 const headerSearchInput = page.locator('#header-search')
127 await expect(headerSearchInput).toBeFocused()
128 })
129})
130
131test.describe('Keyboard Shortcuts', () => {
132 test('"c" navigates to /compare', async ({ page, goto }) => {
133 await goto('/settings', { waitUntil: 'hydration' })
134
135 await page.keyboard.press('c')
136
137 await expect(page).toHaveURL(/\/compare/)
138 })
139
140 test('"c" does not navigate when any modifier key is pressed', async ({ page, goto }) => {
141 await goto('/settings', { waitUntil: 'hydration' })
142
143 await page.keyboard.press('Shift+c')
144 await expect(page).toHaveURL(/\/settings/)
145 await page.keyboard.press('Control+c')
146 await expect(page).toHaveURL(/\/settings/)
147 await page.keyboard.press('Alt+c')
148 await expect(page).toHaveURL(/\/settings/)
149 await page.keyboard.press('Meta+c')
150 await expect(page).toHaveURL(/\/settings/)
151 await page.keyboard.press('ControlOrMeta+Shift+c')
152 await expect(page).toHaveURL(/\/settings/)
153 })
154
155 test('"c" on package page navigates to /compare with package pre-filled', async ({
156 page,
157 goto,
158 }) => {
159 await goto('/package/vue', { waitUntil: 'hydration' })
160
161 await page.keyboard.press('c')
162
163 // Should navigate to /compare with the package in the query
164 await expect(page).toHaveURL(/\/compare\?packages=vue/)
165 })
166
167 test('"c" does not navigate when search input is focused', async ({ page, goto }) => {
168 await goto('/settings', { waitUntil: 'hydration' })
169
170 const searchInput = page.locator('#header-search')
171 await searchInput.focus()
172 await expect(searchInput).toBeFocused()
173
174 await page.keyboard.press('c')
175
176 // Should still be on settings, not navigated to compare
177 await expect(page).toHaveURL(/\/settings/)
178 // The 'c' should have been typed into the input
179 await expect(searchInput).toHaveValue('c')
180 })
181
182 test('"c" on package page does not navigate when any modifier key is pressed', async ({
183 page,
184 goto,
185 }) => {
186 await goto('/package/vue', { waitUntil: 'hydration' })
187
188 await page.keyboard.press('Shift+c')
189 await expect(page).toHaveURL(/\/vue/)
190 await page.keyboard.press('Control+c')
191 await expect(page).toHaveURL(/\/vue/)
192 await page.keyboard.press('Alt+c')
193 await expect(page).toHaveURL(/\/vue/)
194 await page.keyboard.press('Meta+c')
195 await expect(page).toHaveURL(/\/vue/)
196 await page.keyboard.press('ControlOrMeta+Shift+c')
197 await expect(page).toHaveURL(/\/vue/)
198 })
199
200 test('"," navigates to /settings', async ({ page, goto }) => {
201 await goto('/compare', { waitUntil: 'hydration' })
202
203 await page.keyboard.press(',')
204
205 await expect(page).toHaveURL(/\/settings/)
206 })
207
208 test('"," does not navigate when any modifier key is pressed', async ({ page, goto }) => {
209 await goto('/settings', { waitUntil: 'hydration' })
210
211 const searchInput = page.locator('#header-search')
212 await searchInput.focus()
213 await expect(searchInput).toBeFocused()
214
215 await page.keyboard.press('Shift+,')
216 await expect(page).toHaveURL(/\/settings/)
217 await page.keyboard.press('Control+,')
218 await expect(page).toHaveURL(/\/settings/)
219 await page.keyboard.press('Alt+,')
220 await expect(page).toHaveURL(/\/settings/)
221 await page.keyboard.press('Meta+,')
222 await expect(page).toHaveURL(/\/settings/)
223 await page.keyboard.press('ControlOrMeta+Shift+,')
224 await expect(page).toHaveURL(/\/settings/)
225 })
226
227 test('"," does not navigate when search input is focused', async ({ page, goto }) => {
228 await goto('/compare', { waitUntil: 'hydration' })
229
230 const searchInput = page.locator('#header-search')
231 await searchInput.focus()
232 await expect(searchInput).toBeFocused()
233
234 await page.keyboard.press(',')
235
236 // Should still be on compare, not navigated to settings
237 await expect(page).toHaveURL(/\/compare/)
238 // The ',' should have been typed into the input
239 await expect(searchInput).toHaveValue(',')
240 })
241})