diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 4def6043..0a290412 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -2,11 +2,35 @@ name: Test
on: [push, pull_request, workflow_dispatch]
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 esbuild@^0.19.11
+ # https://jestjs.io/docs/ecmascript-modules
+ node --experimental-vm-modules node_modules/jest/bin/jest --coverage
+ if: ${{ matrix.env.tool == 'node+jest' }}
diff --git a/bun.lockb b/bun.lockb
index 853b4383..95ba727a 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/packages/nuejs/test/render.test.js b/packages/nuejs/test/render.test.js
index 2dfdb145..8c9dd7f1 100644
--- a/packages/nuejs/test/render.test.js
+++ b/packages/nuejs/test/render.test.js
@@ -197,7 +197,7 @@ const IF_SIBLING = `
test('If sibling', () => {
const els = [{ label: 'First'}]
const html = render(IF_SIBLING, { els })
- expect(html).toInclude('First')
+ expect(html).toContain('First')
})
@@ -207,7 +207,7 @@ test(':for error', () => {
render('
\n
Hey
\n')
} catch (e) {
- expect(e.lineText).toInclude(':for="foo bar"')
+ expect(e.lineText).toContain(':for="foo bar"')
expect(e.column).toBeGreaterThan(1)
expect(e.line).toBe(3)
}
@@ -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 27eb9b2c..d1823ecc 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",
"js-yaml": "^4.1.0",
"marked": "^9.1.6",
"nuejs-core": "^0.3.0"
diff --git a/packages/nuekit/src/browser/app-router.js b/packages/nuekit/src/browser/app-router.js
index 8407f19e..57728dfc 100644
--- a/packages/nuekit/src/browser/app-router.js
+++ b/packages/nuekit/src/browser/app-router.js
@@ -1,6 +1,8 @@
import { onclick, loadPage, setSelected } from './page-router.js'
+const is_browser = typeof window == 'object'
+
const fns = []
async function fire(path) {
@@ -12,7 +14,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 e2bed154..803996ca 100644
--- a/packages/nuekit/src/browser/page-router.js
+++ b/packages/nuekit/src/browser/page-router.js
@@ -38,7 +38,7 @@ export async function loadPage(path) {
// back button
-addEventListener('popstate', e => {
+is_browser && addEventListener('popstate', e => {
const { path, is_spa } = e.state || {}
if (path) loadPage(path)
})
diff --git a/packages/nuekit/src/builder.js b/packages/nuekit/src/builder.js
index 2d9a6934..62f9c32a 100644
--- a/packages/nuekit/src/builder.js
+++ b/packages/nuekit/src/builder.js
@@ -1,5 +1,5 @@
-import { join, extname } from 'node:path'
+import { join, normalize } from 'node:path'
export async function getBuilder(is_esbuild) {
try {
diff --git a/packages/nuekit/src/cli.js b/packages/nuekit/src/cli.js
index ca1e704b..a0364d97 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) {
@@ -103,6 +104,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
@@ -126,11 +130,4 @@ if (args.help) {
}
}
-
-
-
-
-
-
-
-
+}
diff --git a/packages/nuekit/src/site.js b/packages/nuekit/src/site.js
index 9c76c3d2..44d54781 100644
--- a/packages/nuekit/src/site.js
+++ b/packages/nuekit/src/site.js
@@ -1,5 +1,5 @@
-import { log, parseMarkdown, getParts, getAppDir, getDirs, colors } from './util.js'
+import { log, parseMarkdown, getParts, getAppDir, getDirs, colors, getPosixPath } from './util.js'
import { join, extname, basename, sep, parse as parsePath } from 'node:path'
import { parse as parseNue } from 'nuejs-core/index.js'
import { promises as fs } from 'node:fs'
@@ -124,7 +124,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 6ec44c93..02b65e5c 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'
import { marked } from 'marked'
import yaml from 'js-yaml'
@@ -46,6 +46,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)
@@ -54,20 +55,26 @@ export function getParts(path) {
export function getAppDir(path) {
+ path = normalize(path)
const [ appdir ] = path.split(sep)
return appdir == path ? '.' : appdir
}
export function getDirs(dir) {
+ dir = normalize(dir)
if (!dir) return []
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..2ec8dba7
--- /dev/null
+++ b/packages/nuekit/test/kit-init.test.js
@@ -0,0 +1,16 @@
+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.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/kit.test.js b/packages/nuekit/test/kit.test.js
index 1499059f..60c326d3 100644
--- a/packages/nuekit/test/kit.test.js
+++ b/packages/nuekit/test/kit.test.js
@@ -5,7 +5,10 @@ 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'
@@ -120,7 +123,7 @@ test('content collection', async () => {
expect(coll.length).toBe(2)
expect(coll[0].url).toBe('/blog/first.html')
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')
})
@@ -159,20 +162,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 }')
@@ -183,7 +178,7 @@ test('inline CSS', async () => {
expect(data.inline_css[0]).toEqual({ path: "/inline/style.css", content: "body { margin: 0 }"})
const html = await kit.renderPage('inline/index.md', data)
- expect(html).toInclude('')
+ expect(html).toContain('')
})
@@ -207,8 +202,8 @@ test('index.html', async() => {
await kit.gen('index.html')
const html = await readDist(kit.dist, 'index.html')
- expect(html).toInclude('hotreload.js')
- expect(html).toInclude('island="test"')
+ expect(html).toContain('hotreload.js')
+ expect(html).toContain('island="test"')
})
test('index.md', async() => {
@@ -217,9 +212,9 @@ test('index.md', async() => {
await kit.gen('index.md')
const html = await readDist(kit.dist, 'index.html')
- expect(html).toInclude('hotreload.js')
- expect(html).toInclude('Hey')
- expect(html).toInclude('Hey
')
+ expect(html).toContain('hotreload.js')
+ expect(html).toContain('Hey')
+ expect(html).toContain('Hey
')
})
@@ -230,11 +225,11 @@ test('bundle', async() => {
// bun bundle
const opts = { path: `./${root}/b.ts`, outdir: root, bundle: true }
await buildJS(opts)
- expect(await read('b.js')).toInclude('var foo = 30')
+ expect(await read('b.js')).toContain('var foo = 30')
// esbuild bundl3
await buildJS({ ...opts, esbuild: true })
- expect(await read('b.js')).toInclude('var foo = 30')
+ expect(await read('b.js')).toContain('var foo = 30')
})
test('syntax errors', async() => {
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 d6c091fa..2f70a354 100644
--- a/packages/nuekit/test/misc.test.js
+++ b/packages/nuekit/test/misc.test.js
@@ -5,6 +5,9 @@ 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 })
test('CLI args', () => {
@@ -16,9 +19,9 @@ test('CLI args', () => {
test('head', () => {
const head = renderHead({ charset: 'foo', title: 'Hey', preload_image: 'hey.png' })
- expect(head).toInclude('meta charset="foo"')
- expect(head).toInclude('Hey')
- expect(head).toInclude('')
+ expect(head).toContain('meta charset="foo"')
+ expect(head).toContain('Hey')
+ expect(head).toContain('')
})
@@ -38,8 +41,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/pnpm-lock.yaml b/pnpm-lock.yaml
index 820114a5..d89ad05c 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
js-yaml:
specifier: ^4.1.0
version: 4.1.0
@@ -29,15 +32,6 @@ importers:
specifier: ^0.3.0
version: link:../nuejs
- packages/nuemark:
- dependencies:
- js-yaml:
- specifier: ^4.1.0
- version: 4.1.0
- marked:
- specifier: ^9.1.2
- version: 9.1.6
-
packages:
/argparse@2.0.1:
@@ -80,6 +74,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: