diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index b10657d..49f1600 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -12,6 +12,7 @@ on: - 'packages/countries/**' - 'packages/scripts/**' - 'packages/test-js/**' + - 'packages/test-node/**' - 'packages/tsconfig/**' - 'package.json' - 'bun.lock' @@ -41,3 +42,32 @@ jobs: run: bun run ci env: CI: true + + test-node: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '24' + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Build dist + run: bun run build + + # Run directly with `node` (not `bun run`, which `bunfig [run] bun=true` + # would re-route to Bun) to validate the published bundles under Node.js. + - name: Test dist under Node.js + run: node --test packages/test-node/*.test.ts + env: + CI: true diff --git a/bun.lock b/bun.lock index fed9f71..eaa85f7 100644 --- a/bun.lock +++ b/bun.lock @@ -40,6 +40,9 @@ "typescript": "catalog:default", }, }, + "packages/test-node": { + "name": "test-node", + }, "packages/tsconfig": { "name": "tsconfig", "version": "1.0.0", @@ -354,6 +357,8 @@ "test-js": ["test-js@workspace:packages/test-js"], + "test-node": ["test-node@workspace:packages/test-node"], + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], diff --git a/packages/test-node/index.test.ts b/packages/test-node/index.test.ts index 0994693..cbbeb69 100644 --- a/packages/test-node/index.test.ts +++ b/packages/test-node/index.test.ts @@ -1,73 +1,55 @@ // biome-ignore-all lint/performance/noDynamicNamespaceImportAccess: test file -import { describe, expect, test } from 'bun:test' -import fs from 'node:fs' -import path from 'node:path' +// Validates the published bundles under the real Node.js runtime (CommonJS + ESM +// loaders), which the Bun-run test-js suite does not exercise. Imports only built +// dist artifacts β€” never the TS source β€” so it mirrors what a Node consumer resolves. -import * as mjs from '../../dist/mjs/index.js' -import * as source from '../countries/src/index.ts' +import assert from 'node:assert/strict' +import { createRequire } from 'node:module' +import { describe, test } from 'node:test' -const distDir = path.resolve(import.meta.dir, '../../dist') +const require = createRequire(import.meta.url) -const exportDataList: Partial[] = ['continents', 'countries', 'languages'] -const exportFnList: Partial[] = ['getEmojiFlag'] +const expectedFns = ['getCountryCode', 'getCountryData', 'getCountryDataList', 'getEmojiFlag'] +const expectedData = ['continents', 'countries', 'languages'] -function evalIIFE(name = 'Countries') { - const script = fs.readFileSync(path.join(distDir, 'index.iife.js'), { encoding: 'utf-8' }) - // biome-ignore lint/security/noGlobalEval: - - eval(`this.${name} = (function () { ${script}\nreturn ${name}})()`) -} +describe('dist under Node.js', () => { + test('CJS main bundle loads via require()', () => { + const cjs = require('../../dist/cjs/index.js') -describe('dist', () => { - test('has proper CJS ES6 export', () => { - expect(typeof mjs).toBe('object') - - for (const prop of exportFnList) { - expect(Object.hasOwn(mjs, prop)).toBe(true) + for (const fn of expectedFns) { + assert.equal(typeof cjs[fn], 'function', `missing function: ${fn}`) } - - for (const prop of exportDataList) { - expect(Object.hasOwn(mjs, prop)).toBe(true) - - const cjsKeys = Object.keys(mjs[prop]) - const srcKeys = Object.keys(source[prop]) - expect(cjsKeys).toEqual(srcKeys) + for (const key of expectedData) { + assert.equal(typeof cjs[key], 'object', `missing data: ${key}`) } + assert.equal(cjs.getEmojiFlag('UA'), 'πŸ‡ΊπŸ‡¦') }) - test('all English country names should contain only A-Z characters', () => { - const nameReg = /^[a-z\s.()-]+$/i - const nonAZ: string[] = [] - for (const c of Object.values(source.countries)) { - if (!nameReg.test(c.name)) { - nonAZ.push(c.name) - } - } + test('ESM main bundle loads via import()', async () => { + const mjs = await import('../../dist/mjs/index.js') - // It helps see incorrect names right away in logs - expect(nonAZ).toEqual([]) + for (const fn of expectedFns) { + assert.equal(typeof mjs[fn], 'function', `missing function: ${fn}`) + } + for (const key of expectedData) { + assert.equal(typeof mjs[key], 'object', `missing data: ${key}`) + } + assert.equal(mjs.getEmojiFlag('UA'), 'πŸ‡ΊπŸ‡¦') }) - test('loads ES6