diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index adaca267..558e4af9 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -10,11 +10,35 @@ on:
jobs:
test:
- runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ env:
+ - { os: 'ubuntu-22.04', tool: 'bun' }
+ - { os: 'macos-12', tool: 'bun' }
+ # Windows support for bun install is not implemented yet
+ # - { os: 'windows-2022', tool: 'bun' }
+ # - { os: 'ubuntu-22.04', tool: 'node+jest' }
+ # - { os: 'macos-12', tool: 'node+jest' }
+ - { os: 'windows-2022', tool: 'node+jest' }
+ runs-on: ${{ matrix.env.os }}
steps:
- uses: actions/checkout@v3
+
+ # Testing `bun`
- uses: oven-sh/setup-bun@v1
+ if: ${{ matrix.env.tool == 'bun' }}
- run: |
+ bun -v
bun install
bun install --no-save esbuild@^0.19.11
bun test --coverage
+ if: ${{ matrix.env.tool == 'bun' }}
+
+ # Testing `node+jest`
+ - run: |
+ node -v && npm -v
+ npm install
+ npm install --no-save jest jest-extended esbuild@^0.19.11
+ # https://jestjs.io/docs/ecmascript-modules
+ node --experimental-vm-modules node_modules/jest/bin/jest --runInBand --coverage
+ if: ${{ matrix.env.tool == 'node+jest' }}
diff --git a/package.json b/package.json
index d989b25b..5d5abdf2 100644
--- a/package.json
+++ b/package.json
@@ -9,5 +9,8 @@
"engines": {
"bun": ">=1",
"node": ">=18"
+ },
+ "jest": {
+ "setupFilesAfterEnv": ["jest-extended/all"]
}
}
diff --git a/packages/nuejs/test/render.test.js b/packages/nuejs/test/render.test.js
index 2dfdb145..e0d0fe74 100644
--- a/packages/nuejs/test/render.test.js
+++ b/packages/nuejs/test/render.test.js
@@ -218,9 +218,18 @@ test('{ expr } error', () => {
render('
\nHey { foo[0] } { title }
')
} catch (e) {
- expect(e.subexpr).toBe('foo[0]')
- expect(e.line).toBe(2)
- expect(e.column).toBe(9)
+ // Getting different results from different environments
+ // bun: TypeError: undefined is not an object (evaluating '_.foo[0]')
+ if (process.isBun) {
+ expect(e.subexpr).toBe('foo[0]')
+ expect(e.line).toBe(2)
+ expect(e.column).toBe(9)
+ } else {
+ // node: TypeError: Cannot read properties of undefined (reading '0')
+ expect(e.subexpr).toBe('0')
+ expect(e.line).toBe(2)
+ expect(e.column).toBe(13)
+ }
}
})
diff --git a/packages/nuekit/package.json b/packages/nuekit/package.json
index 73584e04..650615c1 100644
--- a/packages/nuekit/package.json
+++ b/packages/nuekit/package.json
@@ -18,6 +18,7 @@
},
"dependencies": {
"diff-dom": "^5.0.6",
+ "es-main": "^1.3.0",
"import-meta-resolve": "^4.0.0",
"js-yaml": "^4.1.0",
"nuejs-core": "^0.3.0",
diff --git a/packages/nuekit/src/browser/app-router.js b/packages/nuekit/src/browser/app-router.js
index 50d82a8f..cfeeb645 100644
--- a/packages/nuekit/src/browser/app-router.js
+++ b/packages/nuekit/src/browser/app-router.js
@@ -2,6 +2,8 @@
// Router for single-page applications
import { onclick, loadPage, setSelected } from './page-router.js'
+const is_browser = typeof window == 'object'
+
const fns = []
async function fire(path) {
@@ -13,7 +15,7 @@ async function fire(path) {
}
// clear existing routes
-addEventListener('before:route', () => {
+is_browser && addEventListener('before:route', () => {
fns.splice(0, fns.length)
})
diff --git a/packages/nuekit/src/browser/page-router.js b/packages/nuekit/src/browser/page-router.js
index 452fab69..7ea24835 100644
--- a/packages/nuekit/src/browser/page-router.js
+++ b/packages/nuekit/src/browser/page-router.js
@@ -48,13 +48,6 @@ export async function loadPage(path) {
}
-// back button
-addEventListener('popstate', e => {
- const { path, is_spa } = e.state || {}
- if (path) loadPage(path)
-})
-
-
// setup linking
export function onclick(root, fn) {
@@ -103,6 +96,12 @@ if (is_browser) {
// initial selected
setSelected(location.pathname)
+
+ // back button
+ addEventListener('popstate', e => {
+ const { path } = e.state || {}
+ if (path) loadPage(path)
+ })
}
diff --git a/packages/nuekit/src/cli.js b/packages/nuekit/src/cli.js
index 367801c9..3d8ca7ac 100755
--- a/packages/nuekit/src/cli.js
+++ b/packages/nuekit/src/cli.js
@@ -1,6 +1,7 @@
#!/usr/bin/env bun
import { log, colors } from './util.js'
+import esMain from 'es-main'
// [-npe] --> [-n, -p, -e]
export function expandArgs(args) {
@@ -104,6 +105,9 @@ async function runCommand(args) {
else if (cmd == 'stats') await nue.stats()
}
+// Only run main when called as real CLI
+if (esMain(import.meta)) {
+
const args = getArgs(process.argv)
// help
@@ -127,11 +131,4 @@ if (args.help) {
}
}
-
-
-
-
-
-
-
-
+}
diff --git a/packages/nuekit/src/site.js b/packages/nuekit/src/site.js
index 4391aa5a..9449583b 100644
--- a/packages/nuekit/src/site.js
+++ b/packages/nuekit/src/site.js
@@ -1,6 +1,6 @@
import { join, extname, basename, sep, parse as parsePath } from 'node:path'
-import { log, getParts, getAppDir, getDirs, colors } from './util.js'
+import { log, getParts, getAppDir, getDirs, colors, getPosixPath } from './util.js'
import { parse as parseNue } from 'nuejs-core/index.js'
import { nuemark } from 'nuemark/index.js'
import { promises as fs } from 'node:fs'
@@ -125,7 +125,8 @@ export async function createSite(args) {
}).forEach(path => {
const ext = extname(path)
- arr.push('/' + join(dir, to_ext ? path.replace(ext, '.' + to_ext) : path))
+ const subpath = to_ext ? path.replace(ext, '.' + to_ext) : path
+ arr.push('/' + getPosixPath(join(dir, subpath)))
})
} catch (e) {
diff --git a/packages/nuekit/src/util.js b/packages/nuekit/src/util.js
index acdd39b4..e2d13205 100644
--- a/packages/nuekit/src/util.js
+++ b/packages/nuekit/src/util.js
@@ -1,6 +1,6 @@
/* misc stuff. think shame.css */
-import { sep, parse } from 'node:path'
+import { sep, parse, normalize } from 'node:path'
export function log(msg, extra='') {
@@ -29,6 +29,7 @@ export const colors = getColorFunctions()
/* path parts */
export function getParts(path) {
+ path = normalize(path)
const { dir, name, base } = parse(path)
const appdir = getAppDir(path)
const url = getUrl(dir, name)
@@ -37,6 +38,7 @@ export function getParts(path) {
export function getAppDir(path) {
+ path = normalize(path)
const [ appdir ] = path.split(sep)
return appdir == path ? '' : appdir
}
@@ -44,14 +46,19 @@ export function getAppDir(path) {
// getDirs('a/b/c') --> ['a', 'a/b', 'a/b/c']
export function getDirs(dir) {
if (!dir) return []
+ dir = normalize(dir)
const els = dir.split(sep)
return els.map((el, i) => els.slice(0, i + 1).join(sep))
}
export function getUrl(dir, name) {
- let url = dir.replace('\\', '/') + '/'
+ let url = getPosixPath(dir) + '/'
if (url[0] != '/') url = '/' + url
// if (name != 'index')
url += name + '.html'
return url
}
+
+export function getPosixPath(path) {
+ return path.replaceAll('\\', '/')
+}
diff --git a/packages/nuekit/test/kit-init.test.js b/packages/nuekit/test/kit-init.test.js
new file mode 100644
index 00000000..72f31f5a
--- /dev/null
+++ b/packages/nuekit/test/kit-init.test.js
@@ -0,0 +1,19 @@
+import { promises as fs } from 'node:fs'
+import { join } from 'node:path'
+import { init } from '../src/init.js'
+
+// temporary directory
+const root = '_test'
+
+// setup and teardown
+beforeAll(async () => {
+ await fs.rm(root, { recursive: true, force: true })
+ await fs.mkdir(root, { recursive: true })
+})
+afterAll(async () => await fs.rm(root, { recursive: true, force: true }))
+
+test('init dist/@nue dir', async () => {
+ await init({ dist: root, is_dev: true, esbuild: false })
+ const names = await fs.readdir(join(root, '@nue'))
+ expect(names.length).toBeGreaterThan(7)
+})
diff --git a/packages/nuekit/test/match-path.js b/packages/nuekit/test/match-path.js
new file mode 100644
index 00000000..270b2088
--- /dev/null
+++ b/packages/nuekit/test/match-path.js
@@ -0,0 +1,26 @@
+import { getPosixPath } from '../src/util.js'
+
+// https://stackoverflow.com/questions/67325342/how-to-run-os-agnostic-jest-test-files-that-check-paths
+// https://jestjs.io/docs/expect#expectextendmatchers
+export function toMatchPath(actual, expected) {
+ const { printReceived, printExpected, matcherHint } = this.utils
+
+ const pass = getPosixPath(actual) == expected
+
+ return {
+ pass,
+ message: () => pass
+ ? matcherHint('.not.toMatchPath') +
+ '\n\n' +
+ 'Expected path not to match:\n' +
+ ` ${printExpected(expected)}\n` +
+ 'Received:\n' +
+ ` ${printReceived(actual)}`
+ : matcherHint('.toMatchPath') +
+ '\n\n' +
+ 'Expected path to match:\n' +
+ ` ${printExpected(expected)}\n` +
+ 'Received:\n' +
+ ` ${printReceived(actual)}`
+ }
+}
diff --git a/packages/nuekit/test/misc.test.js b/packages/nuekit/test/misc.test.js
index 7753ccf7..5a1bb196 100644
--- a/packages/nuekit/test/misc.test.js
+++ b/packages/nuekit/test/misc.test.js
@@ -6,6 +6,10 @@ import { match } from '../src/browser/app-router.js'
import { renderHead } from '../src/layout.js'
import { getArgs } from '../src/cli.js'
+import { toMatchPath } from './match-path.js'
+
+expect.extend({ toMatchPath })
+
const lcss = await findModule('lightningcss')
const stylus = await findModule('stylus')
@@ -85,8 +89,8 @@ test('app router', async () => {
test('path parts', () => {
const parts = getParts('docs/glossary/semantic-css.md')
expect(parts.url).toBe('/docs/glossary/semantic-css.html')
- expect(parts.dir).toBe('docs/glossary')
- expect(parts.appdir).toBe('docs')
+ expect(parts.dir).toMatchPath('docs/glossary')
+ expect(parts.appdir).toMatchPath('docs')
expect(parts.slug).toBe('semantic-css.html')
})
diff --git a/packages/nuekit/test/nuekit.test.js b/packages/nuekit/test/nuekit.test.js
index 04c9b11f..2d18cc43 100644
--- a/packages/nuekit/test/nuekit.test.js
+++ b/packages/nuekit/test/nuekit.test.js
@@ -5,13 +5,19 @@ import { createSite } from '../src/site.js'
import { createKit } from '../src/nuekit.js'
import { promises as fs } from 'node:fs'
import { join, parse } from 'node:path'
-import { init } from '../src/init.js'
+
+import { toMatchPath } from './match-path.js'
+
+expect.extend({ toMatchPath })
// temporary directory
const root = '_test'
// setup and teardown
-beforeAll(async () => await fs.mkdir(root, { recursive: true }))
+beforeAll(async () => {
+ await fs.rm(root, { recursive: true, force: true })
+ await fs.mkdir(root, { recursive: true })
+})
afterAll(async () => await fs.rm(root, { recursive: true, force: true }))
// helper function for creating files to the root directory
@@ -124,7 +130,7 @@ test('content collection', async () => {
expect(coll[0].url).toBe('/blog/first.html')
expect(coll[0].title).toBe('First')
expect(coll[1].title).toBe('Second')
- expect(coll[1].dir).toBe('blog/nested')
+ expect(coll[1].dir).toMatchPath('blog/nested')
expect(coll[1].slug).toBe('hey.html')
})
@@ -174,20 +180,12 @@ test('getRequestPaths', async () => {
// SPA root
const path = 'admin/index.html'
await write(path)
- expect(await site.getRequestPaths('/admin/')).toMatchObject({ path })
- expect(await site.getRequestPaths('/admin/customers')).toMatchObject({ path })
+ expect((await site.getRequestPaths('/admin/')).path).toMatchPath(path)
+ expect((await site.getRequestPaths('/admin/customers')).path).toMatchPath(path)
expect(await site.getRequestPaths('/admin/readme.html')).toMatchObject({ path: '404.html' })
})
-
-test('init dist/@nue dir', async () => {
- await init({ dist: root, is_dev: true, esbuild: false })
- const names = await fs.readdir(join(root, '@nue'))
- expect(names.length).toBeGreaterThan(7)
-})
-
-
test('inline CSS', async () => {
const kit = await getKit()
await write('inline/style.css', 'body { margin: 0 }')
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 63c44393..34b1189c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -19,6 +19,9 @@ importers:
diff-dom:
specifier: ^5.0.6
version: 5.1.2
+ es-main:
+ specifier: ^1.3.0
+ version: 1.3.0
import-meta-resolve:
specifier: ^4.0.0
version: 4.0.0
@@ -83,6 +86,10 @@ packages:
engines: {node: '>=0.12'}
dev: false
+ /es-main@1.3.0:
+ resolution: {integrity: sha512-AzORKdz1Zt97TzbYQnIrI3ZiibWpRXUfpo/w0xOJ20GpNYd2bd3MU9m31zS/aJ1TJl6JfLTok83Y8HjNunYT0A==}
+ dev: false
+
/htmlparser2@9.0.0:
resolution: {integrity: sha512-uxbSI98wmFT/G4P2zXx4OVx04qWUmyFPrD2/CNepa2Zo3GPNaCaaxElDgwUrwYWkK1nr9fft0Ya8dws8coDLLQ==}
dependencies: