Skip to content

Commit 521684f

Browse files
authored
Merge branch 'main' into main
2 parents b18ae74 + c295d19 commit 521684f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+39692
-233
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ jobs:
117117
run: pnpm install
118118

119119
- name: 🏗️ Build project
120-
run: pnpm build
120+
run: NODE_ENV=test pnpm build
121121

122122
- name: ♿ Accessibility audit (Lighthouse - ${{ matrix.mode }} mode)
123123
run: ./scripts/lighthouse-a11y.sh

CONTRIBUTING.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ We want to create 'a fast, modern browser for the npm registry.' This means, amo
4242
- [Unit tests](#unit-tests)
4343
- [Component accessibility tests](#component-accessibility-tests)
4444
- [End to end tests](#end-to-end-tests)
45+
- [Test fixtures (mocking external APIs)](#test-fixtures-mocking-external-apis)
4546
- [Submitting changes](#submitting-changes)
4647
- [Before submitting](#before-submitting)
4748
- [Pull request process](#pull-request-process)
@@ -482,6 +483,69 @@ pnpm test:browser:ui # Run with Playwright UI
482483

483484
Make sure to read about [Playwright best practices](https://playwright.dev/docs/best-practices) and don't rely on classes/IDs but try to follow user-replicable behaviour (like selecting an element based on text content instead).
484485

486+
### Test fixtures (mocking external APIs)
487+
488+
E2E tests use a fixture system to mock external API requests, ensuring tests are deterministic and don't hit real APIs. This is handled at two levels:
489+
490+
**Server-side mocking** (`modules/fixtures.ts` + `modules/runtime/server/cache.ts`):
491+
492+
- Intercepts all `$fetch` calls during SSR
493+
- Serves pre-recorded fixture data from `test/fixtures/`
494+
- Enabled via `NUXT_TEST_FIXTURES=true` or Nuxt test mode
495+
496+
**Client-side mocking** (`test/e2e/test-utils.ts`):
497+
498+
- Uses Playwright's route interception to mock browser requests
499+
- All test files import from `./test-utils` instead of `@nuxt/test-utils/playwright`
500+
- Throws a clear error if an unmocked external request is detected
501+
502+
#### Fixture files
503+
504+
Fixtures are stored in `test/fixtures/` with this structure:
505+
506+
```
507+
test/fixtures/
508+
├── npm-registry/
509+
│ ├── packuments/ # Package metadata (vue.json, @nuxt/kit.json)
510+
│ ├── search/ # Search results (vue.json, nuxt.json)
511+
│ └── orgs/ # Org package lists (nuxt.json)
512+
├── npm-api/
513+
│ └── downloads/ # Download stats
514+
└── users/ # User package lists
515+
```
516+
517+
#### Adding new fixtures
518+
519+
1. **Generate fixtures** using the script:
520+
521+
```bash
522+
pnpm generate:fixtures vue lodash @nuxt/kit
523+
```
524+
525+
2. **Or manually create** a JSON file in the appropriate directory
526+
527+
#### Environment variables
528+
529+
| Variable | Purpose |
530+
| --------------------------------- | ---------------------------------- |
531+
| `NUXT_TEST_FIXTURES=true` | Enable server-side fixture mocking |
532+
| `NUXT_TEST_FIXTURES_VERBOSE=true` | Enable detailed fixture logging |
533+
534+
#### When tests fail due to missing fixtures
535+
536+
If a test fails with an error like:
537+
538+
```
539+
UNMOCKED EXTERNAL API REQUEST DETECTED
540+
API: npm registry
541+
URL: https://registry.npmjs.org/some-package
542+
```
543+
544+
You need to either:
545+
546+
1. Add a fixture file for that package/endpoint
547+
2. Update the mock handlers in `test/e2e/test-utils.ts` (client) or `modules/runtime/server/cache.ts` (server)
548+
485549
## Submitting changes
486550

487551
### Before submitting

app/components/Header/ConnectorModal.vue

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,8 @@ function handleDisconnect() {
8181
<form v-else class="space-y-4" @submit.prevent="handleConnect">
8282
<!-- Contributor-only notice -->
8383
<div class="p-3 bg-amber-500/10 border border-amber-500/30 rounded-lg">
84-
<div class="space-y-2">
85-
<span
86-
class="inline-block px-2 py-0.5 text-xs font-bold uppercase tracking-wider bg-amber-500/20 text-amber-400 rounded"
87-
>
84+
<div>
85+
<span class="inline-block text-xs font-bold uppercase tracking-wider text-fg rounded">
8886
{{ $t('connector.modal.contributor_badge') }}
8987
</span>
9088
<p class="text-sm text-fg-muted">
@@ -94,7 +92,7 @@ function handleDisconnect() {
9492
href="https://github.com/npmx-dev/npmx.dev/blob/main/CONTRIBUTING.md#local-connector-cli"
9593
target="_blank"
9694
rel="noopener noreferrer"
97-
class="text-amber-400 hover:underline"
95+
class="text-blue-400 hover:underline"
9896
>
9997
{{ $t('connector.modal.contributor_link') }}
10098
</a>
@@ -213,7 +211,7 @@ function handleDisconnect() {
213211
role="alert"
214212
class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md"
215213
>
216-
<p class="font-mono text-sm text-fg font-bold">
214+
<p class="inline-block text-xs font-bold uppercase tracking-wider text-fg rounded">
217215
{{ $t('connector.modal.warning') }}
218216
</p>
219217
<p class="text-sm text-fg-muted">

app/composables/useAtproto.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import type { UserSession } from '#shared/schemas/userSession'
2-
31
export function useAtproto() {
42
const {
53
data: user,
64
pending,
75
clear,
8-
} = useFetch<UserSession | null>('/api/auth/session', {
6+
} = useFetch('/api/auth/session', {
97
server: false,
108
immediate: !import.meta.test,
119
})

knip.json

Lines changed: 0 additions & 49 deletions
This file was deleted.

knip.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type { KnipConfig } from 'knip'
2+
3+
const config: KnipConfig = {
4+
workspaces: {
5+
'.': {
6+
entry: [
7+
'app/app.vue!',
8+
'app/error.vue!',
9+
'app/pages/**/*.vue!',
10+
'app/components/**/*.vue!',
11+
'app/composables/**/*.ts!',
12+
'app/middleware/**/*.ts!',
13+
'app/plugins/**/*.ts!',
14+
'app/utils/**/*.ts!',
15+
'server/**/*.ts!',
16+
'modules/**/*.ts!',
17+
'config/**/*.ts!',
18+
'lunaria/**/*.ts!',
19+
'shared/**/*.ts!',
20+
'i18n/**/*.ts',
21+
'lunaria.config.ts',
22+
'pwa-assets.config.ts',
23+
'.lighthouserc.cjs',
24+
'lighthouse-setup.cjs',
25+
'uno-preset-rtl.ts!',
26+
'scripts/**/*.ts',
27+
],
28+
project: ['**/*.{ts,vue,cjs,mjs}'],
29+
ignore: ['test/fixtures/**'],
30+
ignoreDependencies: [
31+
'@iconify-json/*',
32+
'@vercel/kv',
33+
'@voidzero-dev/vite-plus-core',
34+
'vite-plus!',
35+
'h3',
36+
/** Needs to be explicitly installed, even though it is not imported, to avoid type errors. */
37+
'unplugin-vue-router',
38+
'vite-plugin-pwa',
39+
40+
/** Some components import types from here, but installing it directly could lead to a version mismatch */
41+
'vue-router',
42+
],
43+
ignoreUnresolved: ['#components', '#oauth/config'],
44+
},
45+
'cli': {
46+
project: ['src/**/*.ts!'],
47+
},
48+
'docs': {
49+
entry: ['app/**/*.{ts,vue}'],
50+
ignoreDependencies: ['docus', 'better-sqlite3', 'nuxt!'],
51+
},
52+
},
53+
}
54+
55+
export default config

lighthouse-setup.cjs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* If not set, defaults to 'dark'.
77
*/
88

9-
/** @param {import('puppeteer').Browser} browser */
109
module.exports = async function setup(browser, { url }) {
1110
const colorMode = process.env.LIGHTHOUSE_COLOR_MODE || 'dark'
1211
const page = await browser.newPage()

modules/cache.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,6 @@ export default defineNuxtModule({
2727
...nitroConfig.storage[FETCH_CACHE_STORAGE_BASE],
2828
driver: 'vercel-runtime-cache',
2929
}
30-
31-
const env = process.env.VERCEL_ENV
32-
33-
nitroConfig.storage['oauth-atproto-state'] = {
34-
driver: env === 'production' ? 'vercel-kv' : 'vercel-runtime-cache',
35-
}
36-
37-
nitroConfig.storage['oauth-atproto-session'] = {
38-
driver: env === 'production' ? 'vercel-kv' : 'vercel-runtime-cache',
39-
}
4030
})
4131
},
4232
})

modules/fixtures.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import process from 'node:process'
2+
import { addServerPlugin, createResolver, defineNuxtModule, useNuxt } from 'nuxt/kit'
3+
4+
/**
5+
* Test fixtures module for mocking external API requests.
6+
*
7+
* This module intercepts server-side requests to external APIs (npm registry, etc.)
8+
* and serves pre-recorded fixture data instead. This ensures tests are deterministic
9+
* and don't depend on external API availability.
10+
*
11+
* Enabled when:
12+
* - `nuxt.options.test` is true (Nuxt test mode), OR
13+
* - `NUXT_TEST_FIXTURES=true` environment variable is set
14+
*
15+
* Set `NUXT_TEST_FIXTURES_VERBOSE=true` for detailed logging.
16+
*
17+
* Note: This only mocks server-side requests. For client-side mocking in
18+
* Playwright tests, see test/e2e/test-utils.ts.
19+
*/
20+
export default defineNuxtModule({
21+
meta: {
22+
name: 'fixtures',
23+
},
24+
setup() {
25+
const nuxt = useNuxt()
26+
const resolver = createResolver(import.meta.url)
27+
28+
if (nuxt.options.test || process.env.NUXT_TEST_FIXTURES === 'true') {
29+
addServerPlugin(resolver.resolve('./runtime/server/cache.ts'))
30+
31+
nuxt.hook('nitro:config', nitroConfig => {
32+
nitroConfig.storage ||= {}
33+
nitroConfig.storage['fixtures'] = {
34+
driver: 'fsLite',
35+
base: resolver.resolve('../test/fixtures'),
36+
}
37+
})
38+
}
39+
},
40+
})

0 commit comments

Comments
 (0)