-
-
Notifications
You must be signed in to change notification settings - Fork 355
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
35 changed files
with
733 additions
and
121 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
import { testRun } from './.testRun' | ||
testRun('npm run dev') | ||
testRun('pnpm run dev') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
import { testRun } from './.testRun' | ||
testRun('npm run preview') | ||
testRun('pnpm run preview') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,58 +1,249 @@ | ||
import { run, page, test, expect, getServerUrl, fetchHtml, autoRetry } from '@brillout/test-e2e' | ||
import { ensureWasClientSideRouted, testCounter } from '../../test/utils' | ||
|
||
export { testRun } | ||
import { test, expect, run, fetchHtml, page, getServerUrl, autoRetry, partRegex } from '@brillout/test-e2e' | ||
import assert from 'node:assert' | ||
|
||
let isProd: boolean | ||
|
||
const titleDefault = 'My Vike + React App' | ||
const pages = { | ||
'/': { | ||
title: titleDefault, | ||
text: 'Rendered to HTML.', | ||
counter: true, | ||
image: true | ||
}, | ||
'/star-wars': { | ||
title: '6 Star Wars Movies', | ||
description: 'All the 6 movies from the Star Wars franchise', | ||
text: 'A New Hope', | ||
image: true | ||
}, | ||
'/star-wars/3': { | ||
title: 'Return of the Jedi', | ||
description: 'Star Wars Movie Return of the Jedi from Richard Marquand', | ||
text: '1983-05-25' | ||
}, | ||
'/streaming': { | ||
title: titleDefault, | ||
text: 'Progressive Rendering', | ||
counter: true | ||
}, | ||
'/without-ssr': { | ||
title: 'No SSR', | ||
text: 'This page is rendered only in the browser', | ||
counter: true, | ||
noSSR: true | ||
} | ||
} as const | ||
|
||
function testRun(cmd: 'npm run dev' | 'npm run preview', isStem?: true) { | ||
run(cmd, { doNotFailOnWarning: true }) | ||
function testRun(cmd: `pnpm run ${'dev' | 'preview'}`) { | ||
run(cmd) | ||
isProd = cmd !== 'pnpm run dev' | ||
testPages() | ||
testPageNavigation_betweenWithSSRAndWithout() | ||
testPageNavigation_titleUpdate() | ||
testUseConfig() | ||
} | ||
|
||
function testPageNavigation_betweenWithSSRAndWithout() { | ||
const textWithSSR = 'Rendered to HTML.' | ||
const textWithoutSSR = "It isn't rendered to HTML" | ||
|
||
test('page content is rendered to HTML', async () => { | ||
const html = await fetchHtml('/') | ||
expect(html).toContain('<li>Rendered to HTML.</li>') | ||
const url = '/without-ssr' | ||
test(url + " isn't rendered to HTML", async () => { | ||
const html = await fetchHtml(url) | ||
expect(html).toContain('<div id="root"></div>') | ||
expect(html).not.toContain(textWithoutSSR) | ||
await page.goto(getServerUrl() + url) | ||
await testCounter() | ||
const body = await page.textContent('body') | ||
expect(body).toContain(textWithoutSSR) | ||
}) | ||
|
||
test('page is rendered to the DOM and interactive', async () => { | ||
await page.goto(getServerUrl() + '/') | ||
test('Switch between SSR and non-SSR page', async () => { | ||
let body: string | null | ||
const t1 = textWithoutSSR | ||
const t2 = textWithSSR | ||
|
||
body = await page.textContent('body') | ||
expect(body).toContain(t1) | ||
expect(body).not.toContain(t2) | ||
ensureWasClientSideRouted('/pages/without-ssr') | ||
|
||
await page.click('a:has-text("Welcome")') | ||
await testCounter() | ||
body = await page.textContent('body') | ||
expect(body).toContain(t2) | ||
expect(body).not.toContain(t1) | ||
ensureWasClientSideRouted('/pages/without-ssr') | ||
|
||
// Client-side routing | ||
await page.click('a[href="/star-wars"]') | ||
await autoRetry(async () => { | ||
expect(await page.textContent('h1')).toBe('Star Wars Movies') | ||
}) | ||
expect(await page.textContent('body')).toContain('The Phantom Menace') | ||
await ensureWasClientSideRouted(isStem ? '/pages/index/index' : '/pages/index') | ||
}) | ||
|
||
test('data fetching page, HTML', async () => { | ||
const html = await fetchHtml('/star-wars') | ||
expect(html).toContain('<a href="/star-wars/6">Revenge of the Sith</a>') | ||
expect(html).toContain('<a href="/star-wars/4">The Phantom Menace</a>') | ||
}) | ||
|
||
test('data fetching page, DOM', async () => { | ||
await page.goto(getServerUrl() + '/star-wars') | ||
const text = await page.textContent('body') | ||
expect(text).toContain('Revenge of the Sith') | ||
expect(text).toContain('The Phantom Menace') | ||
|
||
await page.click('a[href="/star-wars/4"]') | ||
await autoRetry(async () => { | ||
expect(await page.textContent('h1')).toBe('The Phantom Menace') | ||
}) | ||
const pageContent = 'The Phantom MenaceRelease Date: 1999-05-19Director: George LucasProducer: Rick McCallum' | ||
expect(await page.textContent('body')).toContain(pageContent) | ||
}) | ||
|
||
test('ssr: false', async () => { | ||
const html = await fetchHtml('/star-wars/4') | ||
expect(html).toContain('<html') | ||
// <head> is render to HTML | ||
expect(html).toContain('<title>The Phantom Menace</title>') | ||
expect(html).toContain('<meta name="description" content="Demo showcasing Vike + React"/>') | ||
expect(html).toContain('<link rel="icon"') | ||
// <body> isn't rendered to HTML | ||
expect(html).not.toContain('<h1>') | ||
expect(html).toContain('<div id="root"></div>') | ||
await page.click('a:has-text("Without SSR")') | ||
await testCounter() | ||
body = await page.textContent('body') | ||
expect(body).toContain(t1) | ||
expect(body).not.toContain(t2) | ||
ensureWasClientSideRouted('/pages/without-ssr') | ||
}) | ||
} | ||
|
||
function testPageNavigation_titleUpdate() { | ||
test('title update client-side page navigation', async () => { | ||
{ | ||
const { title } = pages['/'] | ||
await page.goto(getServerUrl() + '/') | ||
await expectTitle(title) | ||
} | ||
const testMovieList = async () => { | ||
const { title } = pages['/star-wars'] | ||
await page.click(`a[href="/star-wars"]`) | ||
await expectTitle(title) | ||
} | ||
await testMovieList() | ||
const testMoviePage = async () => { | ||
const { title } = pages['/star-wars/3'] | ||
await page.click(`a:has-text("${title}")`) | ||
await expectTitle(title) | ||
} | ||
await testMoviePage() | ||
await testMovieList() | ||
await testMoviePage() | ||
await testMovieList() | ||
await testMoviePage() | ||
}) | ||
} | ||
async function expectTitle(title: string) { | ||
await autoRetry(async () => { | ||
const titleActual = await page.evaluate(() => window.document.title) | ||
expect(titleActual).toBe(title) | ||
}) | ||
} | ||
|
||
function testPages() { | ||
Object.entries(pages).forEach(([url, pageInfo]) => { | ||
testPage({ url, ...pageInfo }) | ||
}) | ||
} | ||
|
||
function testPage({ | ||
url, | ||
title, | ||
description, | ||
text, | ||
counter, | ||
noSSR, | ||
image | ||
}: { | ||
url: string | ||
title: string | ||
description?: string | ||
text: string | ||
counter?: true | ||
noSSR?: true | ||
image?: true | ||
}) { | ||
test(url + ' (HTML)', async () => { | ||
const html = await fetchHtml(url) | ||
if (!noSSR) { | ||
expect(html).toContain(text) | ||
} | ||
expect(getTitle(html)).toBe(title) | ||
expect(html).toMatch(partRegex`<link rel="icon" href="${getAssetUrl('logo.svg')}"/>`) | ||
|
||
if (description) { | ||
expect(html).toMatch(partRegex`<meta name="description" content="${description}"${/\s*/}/>`) | ||
} else { | ||
expect(html).not.toContain('<meta name="description"') | ||
} | ||
|
||
if (image) { | ||
expect(html).toMatch(partRegex`<meta property="og:image" content="${getAssetUrl('logo-new.svg')}">`) | ||
} else { | ||
expect(html).not.toContain('og:image') | ||
} | ||
}) | ||
test(url + ' (Hydration)', async () => { | ||
await page.goto(getServerUrl() + url) | ||
if (counter) { | ||
await testCounter() | ||
} | ||
const body = await page.textContent('body') | ||
expect(body).toContain(text) | ||
}) | ||
} | ||
|
||
function getTitle(html: string) { | ||
const title = html.match(/<title>(.*?)<\/title>/i)?.[1] | ||
return title | ||
} | ||
|
||
async function testCounter() { | ||
// autoRetry() for awaiting client-side code loading & executing | ||
await autoRetry( | ||
async () => { | ||
expect(await page.textContent('button')).toBe('Counter 0') | ||
await page.click('button') | ||
expect(await page.textContent('button')).toContain('Counter 1') | ||
}, | ||
{ timeout: 5 * 1000 } | ||
) | ||
} | ||
|
||
function testUseConfig() { | ||
test('useConfig() HTML', async () => { | ||
const html = await fetchHtml('/images') | ||
expect(html).toMatch( | ||
partRegex`<script type="application/ld+json">{"@context":"https://schema.org/","contentUrl":{"src":"${getAssetUrl( | ||
'logo-new.svg' | ||
)}"},"creator":{"@type":"Person","name":"brillout"}}</script>` | ||
) | ||
expect(html).toMatch( | ||
partRegex`<script type="application/ld+json">{"@context":"https://schema.org/","contentUrl":{"src":"${getAssetUrl( | ||
'logo.svg' | ||
)}"},"creator":{"@type":"Person","name":"Romuald Brillout"}}</script>` | ||
) | ||
}) | ||
test('useConfig() hydration', async () => { | ||
await page.goto(getServerUrl() + '/') | ||
await testCounter() | ||
ensureWasClientSideRouted('/pages/index') | ||
await page.click('a:has-text("useConfig()")') | ||
await testCounter() | ||
ensureWasClientSideRouted('/pages/index') | ||
await page.goto(getServerUrl() + '/images') | ||
await testCounter() | ||
}) | ||
} | ||
|
||
/** Ensure page wasn't server-side routed. | ||
* | ||
* Examples: | ||
* await ensureWasClientSideRouted('/pages/index') | ||
* await ensureWasClientSideRouted('/pages/about') | ||
*/ | ||
async function ensureWasClientSideRouted(pageIdFirst: `/pages/${string}`) { | ||
// Check whether the HTML is from the first page before Client-side Routing. | ||
// page.content() doesn't return the original HTML (it dumps the DOM to HTML). | ||
// Therefore only the serialized pageContext tell us the original HTML. | ||
const html = await page.content() | ||
const pageId = findFirstPageId(html) | ||
expect(pageId).toBe(pageIdFirst) | ||
} | ||
function findFirstPageId(html: string) { | ||
expect(html).toContain('<script id="vike_pageContext" type="application/json">') | ||
expect(html).toContain('"pageId"') | ||
expect(html.split('"pageId"').length).toBe(2) | ||
const match = partRegex`"pageId":"${/([^"]+)/}"`.exec(html) | ||
expect(match).toBeTruthy() | ||
const pageId = match![1] | ||
expect(pageId).toBeTruthy() | ||
return pageId | ||
} | ||
|
||
function getAssetUrl(fileName: string) { | ||
if (!isProd) { | ||
return `/assets/${fileName}` | ||
} | ||
const [fileBaseName, fileExt, ...r] = fileName.split('.') | ||
assert(r.length === 0) | ||
return partRegex`/assets/static/${fileBaseName}.${/[a-zA-Z0-9_-]+/}.${fileExt}` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Copied from: https://github.com/vikejs/vike-react/tree/main/examples/full |
Oops, something went wrong.