Skip to content

Commit

Permalink
fix: page router fixed and tests added - nuejs#263
Browse files Browse the repository at this point in the history
  • Loading branch information
mszmida committed May 1, 2024
1 parent 2842df8 commit 7c480ee
Show file tree
Hide file tree
Showing 12 changed files with 236 additions and 5 deletions.
Binary file modified bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[test]
preload = "./happydom.js"
3 changes: 3 additions & 0 deletions happydom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { GlobalRegistrator } from '@happy-dom/global-registrator'

GlobalRegistrator.register()
5 changes: 4 additions & 1 deletion packages/nuekit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,8 @@
"engines": {
"bun": ">= 1",
"node": ">= 18"
}
},
"devDependencies": {
"@happy-dom/global-registrator": "^14.7.1"
}
}
25 changes: 21 additions & 4 deletions packages/nuekit/src/browser/page-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ export async function loadPage(path) {
// change title
document.title = $('title', dom)?.textContent

// update components in <meta> tag
updateComponents(dom)

// inline CSS
const new_styles = swapStyles($$('style'), $$('style', dom))
new_styles.forEach(style => $('head').appendChild(style))

// body class
$('body').classList = $('body2', dom).classList
$('body').classList.value = $('body2', dom).classList.value || ''

// external CSS
const paths = swapStyles($$('link'), $$('link', dom))
Expand All @@ -29,6 +32,23 @@ export async function loadPage(path) {
})
}

function updateComponents(dom) {
const a = $('[name="nue:components"]')
const b = $('[name="nue:components"]', dom)

if (a && b) {
if (a.content != b.content) {
a.content = b.content
}

} else if (a) {
a.remove()

} else if (b) {
document.head.append(b)
}
}

function updateHTML(dom) {

for (const query of ['header', 'main', 'footer']) {
Expand Down Expand Up @@ -176,6 +196,3 @@ function loadSheet(path, fn) {
$('head').appendChild(el)
el.onload = fn
}



9 changes: 9 additions & 0 deletions packages/nuekit/test/page-router-test/app.nue
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<section @name="app">
<script>
mounted() {
console.log('<app> mounted')
}
</script>

<h2>App mounted</h2>
</section>
10 changes: 10 additions & 0 deletions packages/nuekit/test/page-router-test/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<main>
<nav>
<a href="/">Home</a>
<a href="/page">Page</a>
</nav>

<h1>Home</h1>

<app />
</main>
1 change: 1 addition & 0 deletions packages/nuekit/test/page-router-test/page/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
title: Page Router Test - Page
9 changes: 9 additions & 0 deletions packages/nuekit/test/page-router-test/page/component.nue
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<section @name="component">
<script>
mounted() {
console.log('<component> mounted')
}
</script>

<h2>Page component mounted</h2>
</section>
9 changes: 9 additions & 0 deletions packages/nuekit/test/page-router-test/page/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<main>
<nav>
<a href="/">Home</a>
</nav>

<h1>Page</h1>

<component />
</main>
3 changes: 3 additions & 0 deletions packages/nuekit/test/page-router-test/site.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
title: Page Router Test
globals: [style]
router: true
165 changes: 165 additions & 0 deletions packages/nuekit/test/page-router.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { beforeAll, afterEach, afterAll, test, expect, spyOn, jest } from 'bun:test'
import * as path from 'node:path'
import * as fs from 'node:fs/promises'

import { createKit } from '../src/nuekit.js'

// temporary directory
const dist = path.join(__dirname, './page-router-test/.dist')
const distDev = `${dist}/dev`

// setup and teardown
beforeAll(async () => {
await fs.rm(dist, { recursive: true, force: true })

const nue = await createKit({ root: './packages/nuekit/test/page-router-test' })
await nue.build()
})

afterEach(() => {
jest.restoreAllMocks()
})

afterAll(async () => {
await fs.rm(dist, { recursive: true, force: true })
})

function readFile(filePath) {
return fs.readFile(`${distDev}/${filePath}`)
}

function preparePage(html) {
const fragment = document.createElement('template')
fragment.innerHTML = html

const components = fragment.content.querySelector('[name="nue:components"]')
// We need to adjust paths for component files to import correctly later.
components.content = components.content
.split(' ')
.map(compPath => `${distDev}${compPath}`)
.join(' ')

/**
* @happy-dom do not support loading scripts from local files without server yet:
* https://github.com/capricorn86/happy-dom/issues/318
* https://github.com/capricorn86/happy-dom/issues/320
* https://github.com/capricorn86/happy-dom/issues/600
*/
fragment.content.querySelectorAll('script').forEach(script => script.remove())

return fragment
}

async function loadPage() {
window.happyDOM.setURL('http://localhost:8080')

const html = (await readFile('./index.html')).toString()
const fragment = preparePage(html)

document.replaceChildren(fragment.content.cloneNode(true))
}

async function waitFor(callback) {
await window.happyDOM.waitUntilComplete()

return new Promise(resolve => {
function runCallback() {
try {
callback()

// cleanup
clearInterval(callbackIntervalId)
observer.disconnect()

resolve()
} catch (error) {
// If `callback` throws, wait for the next mutation, interval or timeout.
}
}

const callbackIntervalId = setInterval(runCallback, 50)

const observer = new MutationObserver(runCallback)
observer.observe(document.body, {
attributes: true,
subtree: true,
childList: true,
})

runCallback()
})
}

test('renders "/" route and mount component', async () => {
await loadPage()

// importing scripts manually for side effects
await Promise.all([import(`${distDev}/@nue/mount.js`), import(`${distDev}/@nue/page-router.js`)])

// imitating loaded page
window.dispatchEvent(new Event('DOMContentLoaded'))

const logSpy = spyOn(console, 'log')

await waitFor(() => {
expect(document.body.querySelector('[is="app"]').innerHTML.trim()).toBe('<h2>App mounted</h2>')
})

expect(document.title).toBe('Page Router Test')

expect(logSpy).toHaveBeenCalledTimes(1)
expect(logSpy.mock.calls[0][0]).toBe('<app> mounted')
})

test('renders "/page" route and mount component when click in a link', async () => {
await loadPage()

// importing scripts manually for side effects
await Promise.all([import(`${distDev}/@nue/mount.js`), import(`${distDev}/@nue/page-router.js`)])

// imitating loaded page
window.dispatchEvent(new Event('DOMContentLoaded'))

const logSpy = spyOn(console, 'log')
// mocking window.fetch API
spyOn(window, 'fetch').mockImplementation(async () => {
const pageHtml = (await readFile('./page/index.html')).toString()
const pageFragment = preparePage(pageHtml)

return Promise.resolve({
text: () => {
const serializer = new XMLSerializer()
return serializer.serializeToString(pageFragment.content)
},
})
})

/**
* The "click()" method does not work - looks like generated event is buggy.
* document.body.querySelector('a[href="/page"]').click()
*/
document.body.querySelector('a[href="/page"]').dispatchEvent(
new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: true,
})
)

await waitFor(() => {
expect(document.body.querySelector('[is="component"]').innerHTML.trim()).toBe(
'<h2>Page component mounted</h2>'
)
})

/**
* It does not work for now:
* expect(location.pathname).toBe('/page')
* https://github.com/capricorn86/happy-dom/issues/994
*/
expect(document.title).toBe('Page Router Test - Page')

expect(logSpy).toHaveBeenCalledTimes(2)
expect(logSpy.mock.calls[0][0]).toBe('<app> mounted')
expect(logSpy.mock.calls[1][0]).toBe('<component> mounted')
})

0 comments on commit 7c480ee

Please sign in to comment.