diff --git a/.npmrc b/.npmrc index 9951b11..3757b30 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,2 @@ -package-lock=false ignore-scripts=true +package-lock=false diff --git a/index.js b/index.js index bc5922f..0610965 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ /** - * @typedef {import('./lib/index.js').Child} Child * @typedef {import('./lib/index.js').Attributes} Attributes + * @typedef {import('./lib/index.js').Child} Child * @typedef {import('./lib/index.js').Result} Result */ diff --git a/jsx-runtime.js b/jsx-runtime.js index 6ce9428..2b83b94 100644 --- a/jsx-runtime.js +++ b/jsx-runtime.js @@ -1,8 +1,8 @@ /** - * @typedef {import('./lib/runtime.js').JSXProps}} JSXProps + * @typedef {import('./lib/runtime.js').JSXProps} JSXProps */ // Export `JSX` as a global for TypeScript. export * from './lib/jsx-automatic.js' -export {jsx, jsxs, Fragment} from './lib/runtime.js' +export {Fragment, jsx, jsxs} from './lib/runtime.js' diff --git a/lib/index.js b/lib/index.js index cb36f7e..725713a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,28 +1,30 @@ /** - * @typedef {import('xast').Root} Root * @typedef {import('xast').Element} Element + * @typedef {import('xast').Nodes} Nodes + * @typedef {import('xast').Root} Root */ /** - * @typedef {Root['children'][number]} Content - * @typedef {Content | Root} Node - * Any concrete `xast` node. - * - * @typedef {Root | Element} Result + * @typedef {Element | Root} Result * Result from a `x` call. * - * @typedef {string | number | boolean | null | undefined} Value + * @typedef {boolean | number | string | null | undefined} Value * Attribute value + * * @typedef {{[attribute: string]: Value}} Attributes * Acceptable value for element properties. * - * @typedef {string | number | null | undefined} PrimitiveChild + * @typedef {boolean | number | string | null | undefined} PrimitiveChild * Primitive children, either ignored (nullish), or turned into text nodes. - * @typedef {Array} ArrayChild + * @typedef {Array} ArrayChild * List of children. - * @typedef {Node | PrimitiveChild | ArrayChild} Child + * @typedef {Nodes | PrimitiveChild | ArrayChild} Child * Acceptable child value. - * + */ + +// Define JSX. + +/** * @typedef {import('./jsx-classic.js').Element} x.JSX.Element * @typedef {import('./jsx-classic.js').IntrinsicAttributes} x.JSX.IntrinsicAttributes * @typedef {import('./jsx-classic.js').IntrinsicElements} x.JSX.IntrinsicElements @@ -39,13 +41,14 @@ * When string, an `Element` is built. * When nullish, a `Root` is built instead. * @param attributes - * Attributes of the element. + * Attributes of the element or first child. * @param children * Children of the node. * @returns * `Element` or `Root`. */ export const x = + // Note: not yet possible to use the spread `...children` in JSDoc overloads. /** * @type {{ * (): Root @@ -68,7 +71,7 @@ export const x = if (name === undefined || name === null) { node = {type: 'root', children: []} - // @ts-expect-error Root builder doesn’t accept attributes. + // @ts-expect-error: Root builder doesn’t accept attributes. children.unshift(attributes) } else if (typeof name === 'string') { node = {type: 'element', name, attributes: {}, children: []} @@ -85,7 +88,6 @@ export const x = (typeof attributes[key] !== 'number' || !Number.isNaN(attributes[key])) ) { - // @ts-expect-error Pretty sure we just set it. node.attributes[key] = String(attributes[key]) } } @@ -112,7 +114,7 @@ export const x = * List of nodes. * @param {Child} value * Child. - * @returns {void} + * @returns {undefined} * Nothing. */ function addChild(nodes, value) { diff --git a/lib/jsx-automatic.d.ts b/lib/jsx-automatic.d.ts index c18f132..28ce76e 100644 --- a/lib/jsx-automatic.d.ts +++ b/lib/jsx-automatic.d.ts @@ -1,5 +1,4 @@ -/* eslint-disable-next-line @typescript-eslint/consistent-type-imports -- fix in major */ -import {Attributes, Child, Result} from './index.js' +import type {Attributes, Child, Result} from './index.js' export namespace JSX { /** @@ -12,8 +11,6 @@ export namespace JSX { */ type IntrinsicAttributes = never - /* eslint-disable @typescript-eslint/consistent-type-definitions -- interfaces are required here */ - /** * This defines the prop types for known elements. * @@ -21,7 +18,6 @@ export namespace JSX { * * This **must** be an interface. */ - // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style interface IntrinsicElements { [name: string]: | Attributes @@ -42,6 +38,4 @@ export namespace JSX { */ children?: never } - - /* eslint-enable @typescript-eslint/consistent-type-definitions */ } diff --git a/lib/jsx-classic.d.ts b/lib/jsx-classic.d.ts index fd2721d..669817f 100644 --- a/lib/jsx-classic.d.ts +++ b/lib/jsx-classic.d.ts @@ -1,5 +1,4 @@ -/* eslint-disable-next-line @typescript-eslint/consistent-type-imports -- fix in major */ -import {Attributes, Child, Result} from './index.js' +import type {Attributes, Child, Result} from './index.js' /** * This unique symbol is declared to specify the key on which JSX children are passed, without conflicting @@ -17,8 +16,6 @@ export type Element = Result */ export type IntrinsicAttributes = never -/* eslint-disable @typescript-eslint/consistent-type-definitions -- interfaces are required here */ - /** * This defines the prop types for known elements. * @@ -26,7 +23,6 @@ export type IntrinsicAttributes = never * * This **must** be an interface. */ -// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style export interface IntrinsicElements { [name: string]: | Attributes @@ -47,5 +43,3 @@ export interface ElementChildrenAttribute { */ [children]?: never } - -/* eslint-enable @typescript-eslint/consistent-type-definitions */ diff --git a/lib/runtime.js b/lib/runtime.js index 6df8e5b..5e8d9a2 100644 --- a/lib/runtime.js +++ b/lib/runtime.js @@ -1,12 +1,12 @@ /** + * @typedef {import('./index.js').Child} Child * @typedef {import('./index.js').Element} Element * @typedef {import('./index.js').Root} Root - * @typedef {import('./index.js').Result} Result - * @typedef {import('./index.js').Child} Child - * @typedef {import('./index.js').Attributes} Attributes * @typedef {import('./index.js').Value} Value - * - * @typedef {{[x: string]: Value | Child}} JSXProps + */ + +/** + * @typedef {{[x: string]: Child | Value}} JSXProps */ import {x} from './index.js' @@ -14,13 +14,25 @@ import {x} from './index.js' /** * Create XML trees in xast through JSX. * - * @param name + * @overload + * @param {null} name + * @param {{children?: Child}} props + * @param {string} [key] + * @returns {Root} + * + * @overload + * @param {string} name + * @param {JSXProps} props + * @param {string} [key] + * @returns {Element} + * + * @param {string | null} name * Qualified name. * * Case sensitive and can contain a namespace prefix (such as `rdf:RDF`). * When string, an `Element` is built. * When nullish, a `Root` is built instead. - * @param props + * @param {JSXProps} props * Map of attributes. * * Nullish (`null` or `undefined`) or `NaN` values are ignored, other values @@ -29,25 +41,18 @@ import {x} from './index.js' * Cannot be given if building a `Root`. * Cannot be omitted when building an `Element` if the first child is a * `Node`. + * @param {string | null | undefined} [_key] + * Key (not used). + * @returns {Element | Root} + * Result. */ -export const jsx = - /** - * @type {{ - * (name: null | undefined, props: {children?: Child}, key?: string): Root - * (name: string, props: JSXProps, key?: string): Element - * }} - */ - ( - /** - * @param {string | null} name - * @param {Attributes & {children?: Child}} props - * @returns {Result} - */ - function (name, props) { - const {children, ...properties} = props - return name === null ? x(name, children) : x(name, properties, children) - } - ) +export function jsx(name, props, _key) { + const {children, ...properties} = props + return name === null + ? x(name, children) + : // @ts-expect-error: overloads are complex. + x(name, properties, children) +} export const jsxs = jsx diff --git a/package.json b/package.json index 741740b..7c1ecb4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xastscript", - "version": "3.1.1", + "version": "4.0.0", "description": "xast utility to create trees", "license": "MIT", "keywords": [ @@ -27,8 +27,12 @@ ], "sideEffects": false, "type": "module", - "main": "index.js", - "types": "index.d.ts", + "exports": { + ".": "./index.js", + "./index.js": "./index.js", + "./jsx-dev-runtime": "./jsx-dev-runtime.js", + "./jsx-runtime": "./jsx-runtime.js" + }, "files": [ "lib/", "index.d.ts", @@ -38,58 +42,64 @@ "jsx-runtime.d.ts", "jsx-runtime.js" ], - "exports": { - ".": "./index.js", - "./index.js": "./index.js", - "./jsx-dev-runtime": "./jsx-dev-runtime.js", - "./jsx-runtime": "./jsx-runtime.js" - }, "dependencies": { - "@types/xast": "^1.0.0" + "@types/xast": "^2.0.0" }, "devDependencies": { - "@types/node": "^18.0.0", - "c8": "^7.0.0", + "@types/node": "^20.0.0", + "c8": "^8.0.0", "esast-util-from-js": "^1.0.0", "estree-util-build-jsx": "^2.0.0", "estree-util-to-js": "^1.0.0", - "prettier": "^2.0.0", + "prettier": "^3.0.0", "remark-cli": "^11.0.0", "remark-preset-wooorm": "^9.0.0", - "tsd": "^0.25.0", + "tsd": "^0.28.0", "type-coverage": "^2.0.0", - "typescript": "^4.0.0", - "unist-builder": "^3.0.0", - "xo": "^0.53.0" + "typescript": "^5.0.0", + "unist-builder": "^4.0.0", + "xo": "^0.55.0" }, "scripts": { - "prepack": "npm run build && npm run format", + "prepack": "npm run generate && npm run build && npm run format", "generate": "node script/generate-jsx.js", "build": "tsc --build --clean && tsc --build && tsd && type-coverage", - "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", + "format": "remark . -qfo && prettier . -w --log-level warn && xo --fix", "test-api": "node --conditions development test/index.js", - "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api", + "test-coverage": "c8 --100 --reporter lcov npm run test-api", "test": "npm run generate && npm run build && npm run format && npm run test-coverage" }, "prettier": { - "tabWidth": 2, - "useTabs": false, - "singleQuote": true, "bracketSpacing": false, "semi": false, - "trailingComma": "none" - }, - "xo": { - "prettier": true + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "none", + "useTabs": false }, "remarkConfig": { "plugins": [ - "preset-wooorm" + "remark-preset-wooorm" ] }, "typeCoverage": { "atLeast": 100, "detail": true, + "ignoreCatch": true, "strict": true + }, + "xo": { + "overrides": [ + { + "files": [ + "**/*.ts" + ], + "rules": { + "@typescript-eslint/consistent-indexed-object-style": "off", + "@typescript-eslint/consistent-type-definitions": "off" + } + } + ], + "prettier": true } } diff --git a/readme.md b/readme.md index 4d7eb8d..58ecac1 100644 --- a/readme.md +++ b/readme.md @@ -48,7 +48,7 @@ You can instead use [`unist-builder`][u] when creating any unist nodes and ## Install This package is [ESM only][esm]. -In Node.js (version 14.14+ and 16.0+), install with [npm][]: +In Node.js (version 16+), install with [npm][]: ```sh npm install xastscript @@ -57,14 +57,14 @@ npm install xastscript In Deno with [`esm.sh`][esmsh]: ```js -import {x} from 'https://esm.sh/xastscript@3' +import {x} from 'https://esm.sh/xastscript@4' ``` In browsers with [`esm.sh`][esmsh]: ```html ``` @@ -191,7 +191,7 @@ Yields: ## API -This package exports the identifier [`x`][x]. +This package exports the identifier [`x`][api-x]. There is no default export. The export map supports the automatic JSX runtime. @@ -220,15 +220,15 @@ When nullish, a [`Root`][root] is built instead. ###### `attributes` -Attributes of the element ([`Attributes`][attributes], optional). +Attributes of the element ([`Attributes`][api-attributes], optional). ###### `children` -Children of the node ([`Child`][child] or `Array`, optional). +Children of the node ([`Array`][api-child] or `Child`, optional). ##### Returns -Created tree ([`Result`][result]). +Created tree ([`Result`][api-result]). [`Element`][element] when a `name` is passed, otherwise [`Root`][root]. @@ -242,7 +242,7 @@ turned to strings. ###### Type ```ts -type Attributes = Record +type Attributes = Record ``` ### `Child` @@ -258,12 +258,13 @@ are used instead. ```ts type Child = - | string + | Array + | Node + | boolean | number + | string | null | undefined - | Node - | Array ``` ### `Result` @@ -273,7 +274,7 @@ Result from a `x` call (TypeScript type). ###### Type ```ts -type Result = Root | Element +type Result = Element | Root ``` ## Syntax tree @@ -293,7 +294,7 @@ You should use the automatic JSX runtime set to `xastscript`. The Use example above (omitting the second) can then be written like so: ```jsx -/** @jsxImportSource x */ +/** @jsxImportSource xastscript */ import {u} from 'unist-builder' @@ -320,15 +321,18 @@ console.log( ## Types This package is fully typed with [TypeScript][]. -It exports the additional types [`Attributes`][attributes], [`Child`][child], -and [`Result`][result]. +It exports the additional types [`Attributes`][api-attributes], +[`Child`][api-child], and [`Result`][api-result]. ## Compatibility -Projects maintained by the unified collective are compatible with all maintained +Projects maintained by the unified collective are compatible with maintained versions of Node.js. -As of now, that is Node.js 14.14+ and 16.0+. -Our projects sometimes work with older versions, but this is not guaranteed. + +When we cut a new major release, we drop support for unmaintained versions of +Node. +This means we try to keep the current release line, `xastscript@^4`, compatible +with Node.js 16. ## Security @@ -375,9 +379,9 @@ abide by its terms. [downloads]: https://www.npmjs.com/package/xastscript -[size-badge]: https://img.shields.io/bundlephobia/minzip/xastscript.svg +[size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=xastscript -[size]: https://bundlephobia.com/result?p=xastscript +[size]: https://bundlejs.com/?q=xastscript [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg @@ -421,10 +425,10 @@ abide by its terms. [h]: https://github.com/syntax-tree/hastscript -[x]: #xname-attributes-children +[api-x]: #xname-attributes-children -[attributes]: #attributes-1 +[api-attributes]: #attributes-1 -[child]: #child +[api-child]: #child -[result]: #result +[api-result]: #result diff --git a/script/generate-jsx.js b/script/generate-jsx.js index 48c34a6..625fb2d 100644 --- a/script/generate-jsx.js +++ b/script/generate-jsx.js @@ -1,8 +1,8 @@ import fs from 'node:fs/promises' import acornJsx from 'acorn-jsx' +import {buildJsx} from 'estree-util-build-jsx' import {fromJs} from 'esast-util-from-js' import {toJs} from 'estree-util-to-js' -import {buildJsx} from 'estree-util-build-jsx' const doc = String( await fs.readFile(new URL('../test/jsx.jsx', import.meta.url)) @@ -36,16 +36,18 @@ await fs.writeFile( await fs.writeFile( new URL('../test/jsx-build-jsx-automatic-development.js', import.meta.url), - toJs( - buildJsx( - fromJs( - doc.replace( - /'name'/, - "'jsx (estree-util-build-jsx, automatic, development)'" + // There’s a problem with `this` that TS doesn’t like. + '// @ts-nocheck\n\n' + + toJs( + buildJsx( + fromJs( + doc.replace( + /'name'/, + "'jsx (estree-util-build-jsx, automatic, development)'" + ), + {plugins: [acornJsx()], module: true} ), - {plugins: [acornJsx()], module: true} - ), - {runtime: 'automatic', importSource: 'xastscript', development: true} - ) - ).value + {runtime: 'automatic', importSource: 'xastscript', development: true} + ) + ).value ) diff --git a/test-d/automatic.tsx b/test-d/automatic.tsx index 61f11e8..8975dec 100644 --- a/test-d/automatic.tsx +++ b/test-d/automatic.tsx @@ -1,8 +1,8 @@ /* @jsxRuntime automatic */ /* @jsxImportSource xastscript */ -import {expectType, expectError} from 'tsd' -import type {Root, Element} from 'xast' +import {expectType} from 'tsd' +import type {Element, Root} from 'xast' import {x} from '../index.js' import {Fragment, jsx, jsxs} from '../jsx-runtime.js' @@ -41,17 +41,25 @@ expectType( ) + expectType({[, ]}) expectType({[, ]}) expectType({[]}) -expectError() -expectError() -expectError({{invalid: 'child'}}) +// @ts-expect-error: not a valid child. +expectType() + +// @ts-expect-error: not a valid child. +expectType() + +// @ts-expect-error: not a valid child. +expectType({{invalid: 'child'}}) // This is where the automatic runtime differs from the classic runtime. // The automatic runtime the children prop to define JSX children, whereas it’s used as an attribute in the classic runtime. expectType(} />) declare function Bar(props?: Record): Element -expectError() + +// @ts-expect-error: components not supported. +expectType() diff --git a/test-d/classic.tsx b/test-d/classic.tsx index 1470076..6a95e4f 100644 --- a/test-d/classic.tsx +++ b/test-d/classic.tsx @@ -1,7 +1,8 @@ /* @jsx x */ /* @jsxFrag null */ -import {expectType, expectError} from 'tsd' -import type {Root, Element} from 'xast' + +import {expectType} from 'tsd' +import type {Element, Root} from 'xast' import {x} from '../index.js' type Result = Element | Root @@ -34,14 +35,22 @@ expectType({[, ]}) expectType({[, ]}) expectType({[]}) -expectError() -expectError() -expectError({{invalid: 'child'}}) +// @ts-expect-error: not a valid attribute value. +expectType() + +// @ts-expect-error: not a valid attribute value. +expectType() +// @ts-expect-error: not a valid child. +expectType({{invalid: 'child'}}) + +// @ts-expect-error: classic runtime does not accept children as an attribute. // This is where the classic runtime differs from the automatic runtime. // The automatic runtime the children prop to define JSX children, whereas it’s // used as an attribute in the classic runtime. -expectError(} />) +expectType(} />) declare function Bar(props?: Record): Element -expectError() + +// @ts-expect-error: components not supported. +expectType() diff --git a/test-d/index.tsx b/test-d/index.tsx index 1c62379..160c199 100644 --- a/test-d/index.tsx +++ b/test-d/index.tsx @@ -1,9 +1,12 @@ -import {expectType, expectError} from 'tsd' -import type {Root, Element} from 'xast' +import {expectType} from 'tsd' +import type {Element, Root} from 'xast' import {x} from '../index.js' expectType(x()) -expectError(x(true)) + +// @ts-expect-error: not a valid name. +x(true) + expectType(x(null)) expectType(x(undefined)) expectType(x('')) @@ -11,9 +14,9 @@ expectType(x('', null)) expectType(x('', undefined)) expectType(x('', 1)) expectType(x('', 'a')) -expectError(x('', true)) +expectType(x('', true)) expectType(x('', [1, 'a', null])) -expectError(x('', [true])) +expectType(x('', [true])) expectType(x('', {})) expectType(x('', {}, [1, 'a', null])) @@ -23,7 +26,15 @@ expectType(x('', {p: undefined})) expectType(x('', {p: true})) expectType(x('', {p: false})) expectType(x('', {p: 'a'})) -expectError(x('', {p: [1]})) -expectError(x('', {p: [true]})) -expectError(x('', {p: ['a']})) -expectError(x('', {p: {x: true}})) + +// @ts-expect-error: not a valid child. +x('', {p: [1]}) + +// @ts-expect-error: not a valid child. +x('', {p: [true]}) + +// @ts-expect-error: not a valid child. +x('', {p: ['a']}) + +// @ts-expect-error: not a valid child. +x('', {p: {x: true}}) diff --git a/test/core.js b/test/core.js index 6beec97..65679b4 100644 --- a/test/core.js +++ b/test/core.js @@ -1,110 +1,130 @@ import assert from 'node:assert/strict' import test from 'node:test' import {x} from 'xastscript' -import * as coreMod from 'xastscript' -import * as jsxCoreMod from 'xastscript/jsx-runtime' -import * as jsxDevMod from 'xastscript/jsx-dev-runtime' - -test('xastscript', () => { - assert.deepEqual( - Object.keys(coreMod).sort(), - ['x'], - 'should expose the public api (`/`)' - ) - assert.deepEqual( - Object.keys(jsxCoreMod).sort(), - ['Fragment', 'jsx', 'jsxs'], - 'should expose the public api (`/jsx-runtime`)' - ) - assert.deepEqual( - Object.keys(jsxDevMod).sort(), - ['Fragment', 'jsxDEV'], - 'should expose the public api (`/jsx-dev-runtime`)' - ) - assert.deepEqual( - x(), - {type: 'root', children: []}, - 'should create a root when w/o `name`' - ) - - assert.throws( - () => { - // @ts-expect-error runtime. +test('xastscript', async function (t) { + await t.test('should expose the public api (`/`)', async function () { + assert.deepEqual(Object.keys(await import('xastscript')).sort(), ['x']) + }) + + await t.test( + 'should expose the public api (`/jsx-runtime`)', + async function () { + assert.deepEqual( + // eslint-disable-next-line n/file-extension-in-import -- ESLint is wrong. + Object.keys(await import('xastscript/jsx-runtime')).sort(), + ['Fragment', 'jsx', 'jsxs'] + ) + } + ) + + await t.test( + 'should expose the public api (`/jsx-dev-runtime`)', + async function () { + assert.deepEqual( + // eslint-disable-next-line n/file-extension-in-import -- ESLint is wrong. + Object.keys(await import('xastscript/jsx-dev-runtime')).sort(), + ['Fragment', 'jsxDEV'] + ) + } + ) + + await t.test('should create a root when w/o `name`', async function () { + assert.deepEqual(x(), {type: 'root', children: []}) + }) + + await t.test('should throw w/ incorrect `name`', async function () { + assert.throws(function () { + // @ts-expect-error: check how the runtime handles an incorrect name. x(1) - }, - /Expected element name, got `1`/, - 'should throw w/ incorrect `name`' - ) - - assert.deepEqual( - x('y'), - {type: 'element', name: 'y', attributes: {}, children: []}, - 'should create an element when given `name`' - ) - - assert.deepEqual( - x('Y'), - {type: 'element', name: 'Y', attributes: {}, children: []}, - 'should treat `name` case-sensitive' - ) - - assert.deepEqual( - x('y', {a: 'b'}), - {type: 'element', name: 'y', attributes: {a: 'b'}, children: []}, - 'should create an element with given attributes' - ) - - assert.deepEqual( - x('y', {a: null, b: undefined, c: Number.NaN}), - {type: 'element', name: 'y', attributes: {}, children: []}, - 'should ignore null, undefined, and NaN attribute values' - ) + }, /Expected element name, got `1`/) + }) - assert.deepEqual( - x('y', {}, x('z')), - { + await t.test('should create an element when given `name`', async function () { + assert.deepEqual(x('y'), { type: 'element', name: 'y', attributes: {}, - children: [{type: 'element', name: 'z', attributes: {}, children: []}] - }, - 'should add a child' - ) + children: [] + }) + }) - assert.deepEqual( - x('y', {}, [x('a'), x('b')]), - { + await t.test('should treat `name` case-sensitive', async function () { + assert.deepEqual(x('Y'), { + type: 'element', + name: 'Y', + attributes: {}, + children: [] + }) + }) + + await t.test( + 'should create an element with given attributes', + async function () { + assert.deepEqual(x('y', {a: 'b'}), { + type: 'element', + name: 'y', + attributes: {a: 'b'}, + children: [] + }) + } + ) + + await t.test( + 'should ignore null, undefined, and NaN attribute values', + async function () { + assert.deepEqual(x('y', {a: null, b: undefined, c: Number.NaN}), { + type: 'element', + name: 'y', + attributes: {}, + children: [] + }) + } + ) + + await t.test('should add a child', async function () { + assert.deepEqual(x('y', {}, x('z')), { type: 'element', name: 'y', attributes: {}, - children: [ - {type: 'element', name: 'a', attributes: {}, children: []}, - {type: 'element', name: 'b', attributes: {}, children: []} - ] - }, - 'should add children as an array' - ) + children: [{type: 'element', name: 'z', attributes: {}, children: []}] + }) + }) - assert.deepEqual( - // @ts-expect-error Deeply nested children are not typed. - x('y', {}, [[[x('a')]], [[[[x('b')]], x('c')]]]), - { + await t.test('should add children as an array', async function () { + assert.deepEqual(x('y', {}, [x('a'), x('b')]), { type: 'element', name: 'y', attributes: {}, children: [ {type: 'element', name: 'a', attributes: {}, children: []}, - {type: 'element', name: 'b', attributes: {}, children: []}, - {type: 'element', name: 'c', attributes: {}, children: []} + {type: 'element', name: 'b', attributes: {}, children: []} ] - }, - 'should add children in a deeply nested array' - ) - - assert.deepEqual( - x('y', {}, x('a'), x('b')), - { + }) + }) + + await t.test( + 'should add children in a deeply nested array', + async function () { + assert.deepEqual( + // @ts-expect-error: check how the runtime handles deep children lists (not support in TS). + x('y', {}, [[[x('a')]], [[[[x('b')]], x('c')]]]), + { + type: 'element', + name: 'y', + attributes: {}, + children: [ + {type: 'element', name: 'a', attributes: {}, children: []}, + {type: 'element', name: 'b', attributes: {}, children: []}, + {type: 'element', name: 'c', attributes: {}, children: []} + ] + } + ) + } + ) + + await t.test('should add children as arguments', async function () { + assert.deepEqual(x('y', {}, x('a'), x('b')), { type: 'element', name: 'y', attributes: {}, @@ -112,13 +132,11 @@ test('xastscript', () => { {type: 'element', name: 'a', attributes: {}, children: []}, {type: 'element', name: 'b', attributes: {}, children: []} ] - }, - 'should add children as arguments' - ) + }) + }) - assert.deepEqual( - x('y', {}, 'a', 1), - { + await t.test('should add strings and numbers as literals', async function () { + assert.deepEqual(x('y', {}, 'a', 1), { type: 'element', name: 'y', attributes: {}, @@ -126,90 +144,97 @@ test('xastscript', () => { {type: 'text', value: 'a'}, {type: 'text', value: '1'} ] - }, - 'should add strings and numbers as literals' - ) - - assert.deepEqual( - x('y', {}, null, undefined), - {type: 'element', name: 'y', attributes: {}, children: []}, - 'should ignore null and undefined children' - ) + }) + }) - assert.throws( - () => { - // @ts-expect-error runtime. - x('y', {}, {}) - }, - /Expected node, nodes, string, got `\[object Object]`/, - 'should throw on invalid children' - ) - - assert.deepEqual( - x('y', 'z'), - { + await t.test('should ignore null and undefined children', async function () { + assert.deepEqual(x('y', {}, null, undefined), { type: 'element', name: 'y', attributes: {}, - children: [{type: 'text', value: 'z'}] - }, - 'should support omitting attributes when given a string for a child' - ) + children: [] + }) + }) - assert.deepEqual( - x('y', 1), - { - type: 'element', - name: 'y', - attributes: {}, + await t.test('should throw on invalid children', async function () { + assert.throws(function () { + // @ts-expect-error: check how the runtime handles non-nodes. + x('y', {}, {}) + }, /Expected node, nodes, string, got `\[object Object]`/) + }) + + await t.test( + 'should support omitting attributes when given a string for a child', + async function () { + assert.deepEqual(x('y', 'z'), { + type: 'element', + name: 'y', + attributes: {}, + children: [{type: 'text', value: 'z'}] + }) + } + ) + + await t.test( + 'should support omitting attributes when given a number for a child', + async function () { + assert.deepEqual(x('y', 1), { + type: 'element', + name: 'y', + attributes: {}, + children: [{type: 'text', value: '1'}] + }) + } + ) + + await t.test( + 'should support omitting attributes when given an array for a child', + async function () { + assert.deepEqual(x('y', ['a', 1]), { + type: 'element', + name: 'y', + attributes: {}, + children: [ + {type: 'text', value: 'a'}, + {type: 'text', value: '1'} + ] + }) + } + ) + + await t.test('should create a root with a textual child', async function () { + assert.deepEqual(x(null, '1'), { + type: 'root', children: [{type: 'text', value: '1'}] - }, - 'should support omitting attributes when given a number for a child' - ) - - assert.deepEqual( - x('y', ['a', 1]), - { - type: 'element', - name: 'y', - attributes: {}, - children: [ - {type: 'text', value: 'a'}, - {type: 'text', value: '1'} - ] - }, - 'should support omitting attributes when given an array for a child' - ) - - assert.deepEqual( - x(null, '1'), - {type: 'root', children: [{type: 'text', value: '1'}]}, - 'should create a root with a textual child' - ) + }) + }) - assert.deepEqual( - x(null, 1), - {type: 'root', children: [{type: 'text', value: '1'}]}, - 'should create a root with a numerical child' + await t.test( + 'should create a root with a numerical child', + async function () { + assert.deepEqual(x(null, 1), { + type: 'root', + children: [{type: 'text', value: '1'}] + }) + } ) - assert.deepEqual( - x(null, x('a')), - { + await t.test('should create a root with a node child', async function () { + assert.deepEqual(x(null, x('a')), { type: 'root', children: [{type: 'element', name: 'a', attributes: {}, children: []}] - }, - 'should create a root with a node child' - ) - - assert.deepEqual( - x('a', {}, [x(null, x('b'))]), - { - type: 'element', - name: 'a', - attributes: {}, - children: [{type: 'element', name: 'b', attributes: {}, children: []}] - }, - 'should create a node w/ by unraveling roots' + }) + }) + + await t.test( + 'should create a node w/ by unraveling roots', + async function () { + assert.deepEqual(x('a', {}, [x(null, x('b'))]), { + type: 'element', + name: 'a', + attributes: {}, + children: [{type: 'element', name: 'b', attributes: {}, children: []}] + }) + } ) }) diff --git a/test/index.js b/test/index.js index 04e777f..27670b0 100644 --- a/test/index.js +++ b/test/index.js @@ -1,6 +1,6 @@ /* eslint-disable import/no-unassigned-import */ import './core.js' -import './jsx-build-jsx-classic.js' import './jsx-build-jsx-automatic.js' import './jsx-build-jsx-automatic-development.js' +import './jsx-build-jsx-classic.js' /* eslint-enable import/no-unassigned-import */ diff --git a/test/jsx.jsx b/test/jsx.jsx index 437ea5f..7f00a77 100644 --- a/test/jsx.jsx +++ b/test/jsx.jsx @@ -5,124 +5,138 @@ import test from 'node:test' import {u} from 'unist-builder' import {x} from 'xastscript' -test('name', () => { - assert.deepEqual(, x('a'), 'should support a self-closing element') +test('name', async function (t) { + await t.test('should support a self-closing element', async function () { + assert.deepEqual(, x('a')) + }) - assert.deepEqual(b, x('a', 'b'), 'should support a value as a child') + await t.test('should support a value as a child', async function () { + assert.deepEqual(b, x('a', 'b')) + }) - const A = 'a' + await t.test('should support an uppercase tag name', async function () { + const A = 'a' - // Note: this file is a template, generated with different runtimes. - // @ts-ignore: TS (depending on this build) sometimes doesn’t understand. - assert.deepEqual(, x(A), 'should support an uppercase tag name') - - assert.deepEqual( - {1 + 1}, - x('a', '2'), - 'should support expressions as children' - ) - - assert.deepEqual(<>, u('root', []), 'should support a fragment') - - assert.deepEqual( - <>a, - u('root', [u('text', 'a')]), - 'should support a fragment with text' - ) - - assert.deepEqual( - <> - - , - u('root', [x('a')]), - 'should support a fragment with an element' - ) - - assert.deepEqual( - <>{-1}, - u('root', [u('text', '-1')]), - 'should support a fragment with an expression' - ) - - const com = {acme: {a: 'A', b: 'B'}} - - assert.deepEqual( // Note: this file is a template, generated with different runtimes. // @ts-ignore: TS (depending on this build) sometimes doesn’t understand. - , - x(com.acme.a), - 'should support members as names (`a.b`)' + assert.deepEqual(, x(A)) + }) + + await t.test('should support expressions as children', async function () { + assert.deepEqual({1 + 1}, x('a', '2')) + }) + + await t.test('should support a fragment', async function () { + assert.deepEqual(<>, u('root', [])) + }) + + await t.test('should support a fragment with text', async function () { + assert.deepEqual(<>a, u('root', [u('text', 'a')])) + }) + + await t.test('should support a fragment with an element', async function () { + assert.deepEqual( + <> + + , + u('root', [x('a')]) + ) + }) + + await t.test( + 'should support a fragment with an expression', + async function () { + assert.deepEqual(<>{-1}, u('root', [u('text', '-1')])) + } ) - assert.deepEqual( - , - x('a', {b: 'true'}), - 'should support a boolean attribute' - ) + await t.test('should support members as names (`a.b`)', async function () { + const com = {acme: {a: 'A', b: 'B'}} - assert.deepEqual( - , - x('a', {b: ''}), - 'should support a double quoted attribute' - ) + assert.deepEqual( + // Note: this file is a template, generated with different runtimes. + // @ts-ignore: TS (depending on this build) sometimes doesn’t understand. + , + x(com.acme.a) + ) + }) - assert.deepEqual( - , - x('a', {b: '"'}), - 'should support a single quoted attribute' - ) + await t.test('should support a boolean attribute', async function () { + assert.deepEqual(, x('a', {b: 'true'})) + }) - assert.deepEqual( - , - x('a', {b: '2'}), - 'should support expression value attributes' - ) + await t.test('should support a double quoted attribute', async function () { + assert.deepEqual(, x('a', {b: ''})) + }) + + await t.test('should support a single quoted attribute', async function () { + assert.deepEqual(, x('a', {b: '"'})) + }) + + await t.test('should support expression value attributes', async function () { + assert.deepEqual(, x('a', {b: '2'})) + }) - const props = {a: 1, b: 2} + await t.test( + 'should support expression spread attributes', + async function () { + const props = {a: 1, b: 2} - assert.deepEqual( - , - x('a', props), - 'should support expression spread attributes' + assert.deepEqual(, x('a', props)) + } ) - assert.deepEqual( - - ce - {1 + 1} - , - x('a', [x('b'), 'c', x('d', 'e'), '2']), - 'should support text, elements, and expressions in JSX' + await t.test( + 'should support text, elements, and expressions in JSX', + async function () { + assert.deepEqual( + + ce + {1 + 1} + , + x('a', [x('b'), 'c', x('d', 'e'), '2']) + ) + } ) - assert.deepEqual( - - <>{1} - , - x('a', '1'), - 'should support a fragment in an element (#1)' + await t.test( + 'should support a fragment in an element (#1)', + async function () { + assert.deepEqual( + + <>{1} + , + x('a', '1') + ) + } ) - const dl = [ - ['Firefox', 'A red panda.'], - ['Chrome', 'A chemical element.'] - ] - - assert.deepEqual( -
- {dl.map(([title, definition]) => ( - <> -
{title}
-
{definition}
- - ))} -
, - x('dl', [ - x('dt', dl[0][0]), - x('dd', dl[0][1]), - x('dt', dl[1][0]), - x('dd', dl[1][1]) - ]), - 'should support a fragment in an element (#2)' + await t.test( + 'should support a fragment in an element (#2)', + async function () { + const dl = [ + ['Firefox', 'A red panda.'], + ['Chrome', 'A chemical element.'] + ] + + assert.deepEqual( +
+ {dl.map(function ([title, definition]) { + return ( + <> +
{title}
+
{definition}
+ + ) + })} +
, + x('dl', [ + x('dt', dl[0][0]), + x('dd', dl[0][1]), + x('dt', dl[1][0]), + x('dd', dl[1][1]) + ]) + ) + } ) }) diff --git a/tsconfig.json b/tsconfig.json index 6250214..2197f6c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,23 +1,21 @@ { - "include": ["**/**.js", "**/**.jsx"], - "exclude": [ - "coverage/", - "node_modules/", - "lib/jsx-automatic.js", - "lib/jsx-classic.js" - ], "compilerOptions": { "checkJs": true, + "customConditions": ["development"], "declaration": true, "emitDeclarationOnly": true, "exactOptionalPropertyTypes": true, - "forceConsistentCasingInFileNames": true, - "lib": ["es2020"], + "jsx": "preserve", + "lib": ["es2022"], "module": "node16", - "newLine": "lf", - "skipLibCheck": true, "strict": true, - "target": "es2020", - "jsx": "preserve" - } + "target": "es2022" + }, + "exclude": ["coverage/", "node_modules/"], + "include": [ + "**/*.js", + "**/*.jsx", + "lib/jsx-automatic.d.ts", + "lib/jsx-classic.d.ts" + ] }