WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
29 changes: 29 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Playwright Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version: lts/*
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
- name: Run Playwright tests
run: pnpm exec playwright test
- uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,10 @@ tsconfig.vitest-temp.json
docs/.vitepress/cache
vite.config.ts.timestamp-*
.DS_Store

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/
16 changes: 14 additions & 2 deletions e2e/__snapshots__/routes.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,10 @@ exports[`e2e routes > generates the routes 1`] = `
}
]

export function handleHotUpdate(_router) {
export function handleHotUpdate(_router, _hotUpdateCallback) {
if (import.meta.hot) {
import.meta.hot.data.router = _router
import.meta.hot.data.router_hotUpdateCallback = _hotUpdateCallback
}
}

Expand All @@ -136,7 +137,18 @@ if (import.meta.hot) {
for (const route of mod.routes) {
router.addRoute(route)
}
router.replace('')
// call the hotUpdateCallback for custom updates
import.meta.hot.data.router_hotUpdateCallback?.(mod.routes)
const route = router.currentRoute.value
router.replace({
...route,
// NOTE: we should be able to just do ...route but the router
// currently skips resolving and can give errors with renamed routes
// so we explicitly set remove matched and name
name: undefined,
matched: undefined,
force: true
})
})
}

Expand Down
1 change: 1 addition & 0 deletions e2e/hmr/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
playground-tmp
81 changes: 81 additions & 0 deletions e2e/hmr/fixtures/vite-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { test as base, expect } from '@playwright/test'
import { createServer, type ViteDevServer } from 'vite'
import { type AddressInfo } from 'node:net'
import path from 'node:path'
import fs from 'node:fs'
import { fileURLToPath } from 'node:url'
import { cpSync, rmSync } from 'node:fs'

type ViteFixtures = {
devServer: ViteDevServer
baseURL: string
projectRoot: string
}

export function applyEditFile(
sourceFilePath: string,
newContentFilePath: string
) {
fs.writeFileSync(
path.join(projectRoot, sourceFilePath),
fs.readFileSync(path.join(projectRoot, newContentFilePath), 'utf8'),
'utf8'
)
}

const sourceDir = fileURLToPath(new URL('../playground', import.meta.url))
const fixtureDir = fileURLToPath(new URL('../playground-tmp', import.meta.url))
const projectRoot = path.resolve(fixtureDir)

export const test = base.extend<ViteFixtures>({
projectRoot,

// @ts-expect-error: type matched what is passed to use(server)
devServer: [
async ({}, use) => {
console.log(projectRoot)

rmSync(fixtureDir, { force: true, recursive: true })
cpSync(sourceDir, fixtureDir, {
recursive: true,
filter: (src) => {
return (
!src.includes('.cache') &&
!src.endsWith('.sock') &&
!src.includes('.output') &&
!src.includes('.vite')
)
},
})
// Start a real Vite dev server with your plugin(s) & config.
// If you already have vite.config.ts, omit configFile:false and rely on it.
const server = await createServer({
configFile: path.join(fixtureDir, 'vite.config.ts'),
// If you need to inline the plugin directly, you could do:
// configFile: false,
// plugins: [myPlugin()],
server: { host: '127.0.0.1', port: 0, strictPort: false }, // random open port
logLevel: 'error',
})

await server.listen()

const http = server.httpServer
if (!http) throw new Error('No httpServer from Vite')

// Expose the running server & URL to tests
await use(server)

await server.close()
},
{ scope: 'worker' },
],

baseURL: async ({ devServer }, use) => {
const http = devServer.httpServer!
const addr = http.address() as AddressInfo
await use(`http://127.0.0.1:${addr.port}`)
},
})

export { expect }
21 changes: 21 additions & 0 deletions e2e/hmr/global.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { fileURLToPath } from 'node:url'
import { cpSync, rmSync } from 'node:fs'
import { test as setup } from '@playwright/test'

const fixtureDir = fileURLToPath(new URL('./playground-tmp', import.meta.url))
const sourceDir = fileURLToPath(new URL('./playground', import.meta.url))

setup('create temporary hmr playground directory', () => {
rmSync(fixtureDir, { force: true, recursive: true })
cpSync(sourceDir, fixtureDir, {
recursive: true,
filter: (src) => {
return (
!src.includes('.cache') &&
!src.endsWith('.sock') &&
!src.includes('.output') &&
!src.includes('.vite')
)
},
})
})
37 changes: 37 additions & 0 deletions e2e/hmr/hmr.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Page } from '@playwright/test'
import { test, expect, applyEditFile } from './fixtures/vite-server'

test.describe('Pages HMR', () => {
let hmrToken: number = -1
// reset hmr token before each test
test.beforeEach(() => {
hmrToken = -1
})

async function ensureHmrToken(page: Page) {
hmrToken = await page.evaluate(
() => ((window as any).__hmrToken ??= Math.random())
)
}

// ensure hmr token is stable across tests
test.afterEach(async ({ page }) => {
if (hmrToken === -1) {
throw new Error('hmrToken was not set in the test')
}
await expect
.poll(async () => page.evaluate(() => (window as any).__hmrToken))
.toBe(hmrToken)
})

test('applies meta changes in <route> block', async ({ page, baseURL }) => {
await page.goto(baseURL + '/')

await expect(page.locator('[data-testid="meta-hello"]')).toHaveText('')

await ensureHmrToken(page)
applyEditFile('src/pages/(home).vue', 'edits/(home)-with-route-block.vue')

await expect(page.locator('[data-testid="meta-hello"]')).toHaveText('world')
})
})
15 changes: 15 additions & 0 deletions e2e/hmr/playground/edits/(home)-with-route-block.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<main>
<h1>Home</h1>

<pre data-testid="meta-hello">{{ $route.meta.hello }}</pre>
</main>
</template>

<route lang="json">
{
"meta": {
"hello": "world"
}
}
</route>
12 changes: 12 additions & 0 deletions e2e/hmr/playground/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HMR E2E Tests</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/main.ts"></script>
</body>
</html>
7 changes: 7 additions & 0 deletions e2e/hmr/playground/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"private": true,
"name": "fixture-hmr",
"scripts": {
"build": "vite build"
}
}
3 changes: 3 additions & 0 deletions e2e/hmr/playground/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template>
<RouterView />
</template>
14 changes: 14 additions & 0 deletions e2e/hmr/playground/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createApp } from 'vue'
import App from './App.vue'
import { router } from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')

// small logger for navigations, useful to check HMR
router.isReady().then(() => {
router.beforeEach((to, from) => {
console.log('🧭', from.fullPath, '->', to.fullPath)
})
})
7 changes: 7 additions & 0 deletions e2e/hmr/playground/src/pages/(home).vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<template>
<main>
<h1>Home</h1>

<pre data-testid="meta-hello">{{ $route.meta.hello }}</pre>
</main>
</template>
13 changes: 13 additions & 0 deletions e2e/hmr/playground/src/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createRouter, createWebHistory } from 'vue-router'
import { routes, handleHotUpdate } from 'vue-router/auto-routes'

export const router = createRouter({
history: createWebHistory(),
routes,
})

if (import.meta.hot) {
handleHotUpdate(router, (routes) => {
console.log('🔥 HMR with', routes)
})
}
51 changes: 51 additions & 0 deletions e2e/hmr/playground/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { defineConfig } from 'vite'
import { fileURLToPath, URL } from 'node:url'
import VueRouter from '../../../src/vite'
import Vue from '@vitejs/plugin-vue'

const root = fileURLToPath(new URL('./', import.meta.url))

export default defineConfig({
root,
clearScreen: false,
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'~': fileURLToPath(new URL('./src', import.meta.url)),
'unplugin-vue-router/runtime': fileURLToPath(
new URL('../../../src/runtime.ts', import.meta.url)
),
'unplugin-vue-router/types': fileURLToPath(
new URL('../../../src/types.ts', import.meta.url)
),
'unplugin-vue-router/data-loaders/basic': fileURLToPath(
new URL('../../../src/data-loaders/entries/basic.ts', import.meta.url)
),
'unplugin-vue-router/data-loaders/pinia-colada': fileURLToPath(
new URL(
'../../../src/data-loaders/entries/pinia-colada.ts',
import.meta.url
)
),
'unplugin-vue-router/data-loaders': fileURLToPath(
new URL('../../../src/data-loaders/entries/index.ts', import.meta.url)
),
},
},
build: {
sourcemap: true,
},

plugins: [
VueRouter({
root,
logs: true,
// getRouteName: getPascalCaseRouteName,
experimental: {
autoExportsDataLoaders: ['src/loaders/**/*', '@/loaders/**/*'],
paramParsers: false,
},
}),
Vue(),
],
})
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@
"devDependencies": {
"@babel/types": "^7.28.5",
"@pinia/colada": "^0.17.8",
"@playwright/test": "^1.56.1",
"@posva/prompts": "^2.4.4",
"@shikijs/vitepress-twoslash": "3.15.0",
"@tanstack/vue-query": "^5.90.7",
Expand Down
3 changes: 2 additions & 1 deletion playground/src/pages/[name].vue
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ definePage({

<template>
<main>
<h1>Param: {{ $route.params.name }}</h1>
<!-- FIXME: it broke at some point -->
<!-- <h1>Param: {{ $route.params.name }}</h1> -->
<h2>Param: {{ route.params.name }}</h2>
<p v-show="false">{{ thing }}</p>
<p v-if="isLoading">Loading user...</p>
Expand Down
14 changes: 11 additions & 3 deletions playground/src/pages/about.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,29 @@ import { routes } from 'vue-router/auto-routes'

definePage({
meta: {
n: 12,
n: 26,
},
})

onMounted(() => {
console.log('routes', routes)
console.log('routes', routes.find((record) => record.name === '/about')?.meta)
})
</script>

<template>
<main>
<h1>About</h1>

<p>Increment to test HMR: 1</p>
<p>Increment to test HMR: 3</p>

<pre>{{ $route.meta }}</pre>
</main>
</template>

<route lang="json">
{
"meta": {
"number": 14
}
}
</route>
Loading
Loading