diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ee318ca..fb63387 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,5 +17,5 @@ jobs: strategy: matrix: node: - - lts/hydrogen + - lts/gallium - node diff --git a/.npmrc b/.npmrc index 43c97e7..3757b30 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ +ignore-scripts=true package-lock=false diff --git a/index.js b/index.js index 2163112..5f21ca9 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,8 @@ /** - * @typedef {import('./lib/types.js').CharacterReferences} CharacterReferences - * @typedef {import('./lib/types.js').Options} Options - * @typedef {import('./lib/types.js').Quote} Quote - * @typedef {import('./lib/types.js').Space} Space + * @typedef {import('./lib/index.js').CharacterReferences} CharacterReferences + * @typedef {import('./lib/index.js').Options} Options + * @typedef {import('./lib/index.js').Quote} Quote + * @typedef {import('./lib/index.js').Space} Space */ export {toHtml} from './lib/index.js' diff --git a/lib/handle/comment.js b/lib/handle/comment.js index 5049ef0..6e16d1f 100644 --- a/lib/handle/comment.js +++ b/lib/handle/comment.js @@ -1,7 +1,8 @@ /** - * @typedef {import('../types.js').Comment} Comment - * @typedef {import('../types.js').Parent} Parent - * @typedef {import('../types.js').State} State + * @typedef {import('hast').Comment} Comment + * @typedef {import('hast').Parents} Parents + * + * @typedef {import('../index.js').State} State */ import {stringifyEntities} from 'stringify-entities' @@ -13,7 +14,7 @@ import {stringifyEntities} from 'stringify-entities' * Node to handle. * @param {number | undefined} _1 * Index of `node` in `parent. - * @param {Parent | undefined} _2 + * @param {Parents | undefined} _2 * Parent of `node`. * @param {State} state * Info passed around about the current state. diff --git a/lib/handle/doctype.js b/lib/handle/doctype.js index f7737a4..d2b9c4a 100644 --- a/lib/handle/doctype.js +++ b/lib/handle/doctype.js @@ -1,17 +1,21 @@ /** - * @typedef {import('../types.js').DocType} DocType - * @typedef {import('../types.js').Parent} Parent - * @typedef {import('../types.js').State} State + * @typedef {import('hast').Doctype} Doctype + * @typedef {import('hast').Parents} Parents + * + * @typedef {import('../index.js').State} State */ +// Make VS code see references to the above types. +'' + /** * Serialize a doctype. * - * @param {DocType} _1 + * @param {Doctype} _1 * Node to handle. * @param {number | undefined} _2 * Index of `node` in `parent. - * @param {Parent | undefined} _3 + * @param {Parents | undefined} _3 * Parent of `node`. * @param {State} state * Info passed around about the current state. diff --git a/lib/handle/element.js b/lib/handle/element.js index 14ba7cd..0d78677 100644 --- a/lib/handle/element.js +++ b/lib/handle/element.js @@ -1,18 +1,18 @@ /** - * @typedef {import('../types.js').State} State - * @typedef {import('../types.js').Parent} Parent - * @typedef {import('../types.js').Element} Element - * @typedef {import('../types.js').Properties} Properties - * @typedef {import('../types.js').PropertyValue} PropertyValue + * @typedef {import('hast').Element} Element + * @typedef {import('hast').Parents} Parents + * @typedef {import('hast').Properties} Properties + * + * @typedef {import('../index.js').State} State */ import {ccount} from 'ccount' import {stringify as commas} from 'comma-separated-tokens' -import {svg, find} from 'property-information' +import {find, svg} from 'property-information' import {stringify as spaces} from 'space-separated-tokens' import {stringifyEntities} from 'stringify-entities' -import {opening} from '../omission/opening.js' import {closing} from '../omission/closing.js' +import {opening} from '../omission/opening.js' /** * Maps of subsets. @@ -21,7 +21,7 @@ import {closing} from '../omission/closing.js' * The value at `0` causes parse errors, the value at `1` is valid. * Of both, the value at `0` is unsafe, and the value at `1` is safe. * - * @type {Record<'name' | 'unquoted' | 'single' | 'double', Array<[Array, Array]>>} + * @type {Record<'double' | 'name' | 'single' | 'unquoted', Array<[Array, Array]>>} */ const constants = { // See: . @@ -53,14 +53,13 @@ const constants = { * Node to handle. * @param {number | undefined} index * Index of `node` in `parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of `node`. * @param {State} state * Info passed around about the current state. * @returns {string} * Serialized node. */ -// eslint-disable-next-line complexity export function element(node, index, parent, state) { const schema = state.schema const omit = schema.space === 'svg' ? false : state.settings.omitOptionalTags @@ -89,6 +88,8 @@ export function element(node, index, parent, state) { // categorisation. // This enables for example `menuitem`s, which are void in W3C HTML but not // void in WHATWG HTML, to be stringified properly. + // Note: `menuitem` has since been removed from the HTML spec, and so is no + // longer void. if (content) selfClosing = false if (attrs || !omit || !opening(node, index, parent)) { @@ -136,7 +137,7 @@ function serializeAttributes(state, props) { if (props) { for (key in props) { - if (props[key] !== undefined && props[key] !== null) { + if (props[key] !== null && props[key] !== undefined) { const value = serializeAttribute(state, key, props[key]) if (value) values.push(value) } @@ -146,7 +147,7 @@ function serializeAttributes(state, props) { while (++index < values.length) { const last = state.settings.tightAttributes ? values[index].charAt(values[index].length - 1) - : null + : undefined // In tight mode, don’t add a space after quoted attributes. if (index !== values.length - 1 && last !== '"' && last !== "'") { @@ -160,10 +161,9 @@ function serializeAttributes(state, props) { /** * @param {State} state * @param {string} key - * @param {PropertyValue} value + * @param {Properties[keyof Properties]} value * @returns {string} */ -// eslint-disable-next-line complexity function serializeAttribute(state, key, value) { const info = find(state.schema, key) const x = @@ -183,8 +183,8 @@ function serializeAttribute(state, key, value) { } if ( - value === undefined || value === null || + value === undefined || value === false || (typeof value === 'number' && Number.isNaN(value)) ) { @@ -233,8 +233,8 @@ function serializeAttribute(state, key, value) { result = stringifyEntities( value, Object.assign({}, state.settings.characterReferences, { - subset: constants.unquoted[x][y], - attribute: true + attribute: true, + subset: constants.unquoted[x][y] }) ) } diff --git a/lib/handle/index.js b/lib/handle/index.js index 9783345..e861c01 100644 --- a/lib/handle/index.js +++ b/lib/handle/index.js @@ -1,7 +1,8 @@ /** - * @typedef {import('../types.js').State} State - * @typedef {import('../types.js').Node} Node - * @typedef {import('../types.js').Parent} Parent + * @typedef {import('hast').Nodes} Nodes + * @typedef {import('hast').Parents} Parents + * + * @typedef {import('../index.js').State} State */ import {zwitch} from 'zwitch' @@ -13,7 +14,7 @@ import {root} from './root.js' import {text} from './text.js' /** - * @type {(node: Node, index: number | undefined, parent: Parent | undefined, state: State) => string} + * @type {(node: Nodes, index: number | undefined, parent: Parents | undefined, state: State) => string} */ export const handle = zwitch('type', { invalid, @@ -36,12 +37,13 @@ function invalid(node) { /** * Fail when a node with an unknown type is found in the tree. * - * @param {unknown} node + * @param {unknown} node_ * Unknown node. * @returns {never} * Never. */ -function unknown(node) { - // @ts-expect-error: `type` is defined. +function unknown(node_) { + // `type` is guaranteed by runtime JS. + const node = /** @type {Nodes} */ (node_) throw new Error('Cannot compile unknown node `' + node.type + '`') } diff --git a/lib/handle/raw.js b/lib/handle/raw.js index d32555c..6c89a11 100644 --- a/lib/handle/raw.js +++ b/lib/handle/raw.js @@ -1,7 +1,9 @@ /** - * @typedef {import('../types.js').State} State - * @typedef {import('../types.js').Parent} Parent - * @typedef {import('../types.js').Raw} Raw + * @typedef {import('hast').Parents} Parents + * + * @typedef {import('mdast-util-to-hast').Raw} Raw + * + * @typedef {import('../index.js').State} State */ import {text} from './text.js' @@ -13,7 +15,7 @@ import {text} from './text.js' * Node to handle. * @param {number | undefined} index * Index of `node` in `parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of `node`. * @param {State} state * Info passed around about the current state. diff --git a/lib/handle/root.js b/lib/handle/root.js index 974d2cc..ccd84e1 100644 --- a/lib/handle/root.js +++ b/lib/handle/root.js @@ -1,9 +1,13 @@ /** - * @typedef {import('../types.js').Root} Root - * @typedef {import('../types.js').Parent} Parent - * @typedef {import('../types.js').State} State + * @typedef {import('hast').Parents} Parents + * @typedef {import('hast').Root} Root + * + * @typedef {import('../index.js').State} State */ +// Make VS code see references to the above types. +'' + /** * Serialize a root. * @@ -11,7 +15,7 @@ * Node to handle. * @param {number | undefined} _1 * Index of `node` in `parent. - * @param {Parent | undefined} _2 + * @param {Parents | undefined} _2 * Parent of `node`. * @param {State} state * Info passed around about the current state. diff --git a/lib/handle/text.js b/lib/handle/text.js index 58d03d4..de25941 100644 --- a/lib/handle/text.js +++ b/lib/handle/text.js @@ -1,8 +1,10 @@ /** - * @typedef {import('../types.js').State} State - * @typedef {import('../types.js').Parent} Parent - * @typedef {import('../types.js').Raw} Raw - * @typedef {import('../types.js').Text} Text + * @typedef {import('hast').Parents} Parents + * @typedef {import('hast').Text} Text + * + * @typedef {import('mdast-util-to-hast').Raw} Raw + * + * @typedef {import('../index.js').State} State */ import {stringifyEntities} from 'stringify-entities' @@ -10,11 +12,11 @@ import {stringifyEntities} from 'stringify-entities' /** * Serialize a text node. * - * @param {Text | Raw} node + * @param {Raw | Text} node * Node to handle. * @param {number | undefined} _ * Index of `node` in `parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of `node`. * @param {State} state * Info passed around about the current state. diff --git a/lib/index.js b/lib/index.js index 812dca7..93bb730 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,28 +1,182 @@ /** - * @typedef {import('./types.js').Node} Node - * @typedef {import('./types.js').Parent} Parent - * @typedef {import('./types.js').Content} Content - * @typedef {import('./types.js').Options} Options - * @typedef {import('./types.js').State} State + * @typedef {import('hast').Nodes} Nodes + * @typedef {import('hast').Parents} Parents + * @typedef {import('hast').RootContent} RootContent + * + * @typedef {import('property-information').Schema} Schema + * + * @typedef {import('stringify-entities').Options} StringifyEntitiesOptions + */ + +/** + * @typedef {Omit} CharacterReferences + * + * @typedef Options + * Configuration. + * @property {boolean | null | undefined} [allowDangerousCharacters=false] + * Do not encode some characters which cause XSS vulnerabilities in older + * browsers (default: `false`). + * + * > ⚠️ **Danger**: only set this if you completely trust the content. + * @property {boolean | null | undefined} [allowDangerousHtml=false] + * Allow `raw` nodes and insert them as raw HTML (default: `false`). + * + * When `false`, `Raw` nodes are encoded. + * + * > ⚠️ **Danger**: only set this if you completely trust the content. + * @property {boolean | null | undefined} [allowParseErrors=false] + * Do not encode characters which cause parse errors (even though they work), + * to save bytes (default: `false`). + * + * Not used in the SVG space. + * + * > 👉 **Note**: intentionally creates parse errors in markup (how parse + * > errors are handled is well defined, so this works but isn’t pretty). + * @property {boolean | null | undefined} [bogusComments=false] + * Use “bogus comments” instead of comments to save byes: `` + * instead of `` (default: `false`). + * + * > 👉 **Note**: intentionally creates parse errors in markup (how parse + * > errors are handled is well defined, so this works but isn’t pretty). + * @property {CharacterReferences | null | undefined} [characterReferences] + * Configure how to serialize character references (optional). + * @property {boolean | null | undefined} [closeEmptyElements=false] + * Close SVG elements without any content with slash (`/`) on the opening tag + * instead of an end tag: `` instead of `` + * (default: `false`). + * + * See `tightSelfClosing` to control whether a space is used before the + * slash. + * + * Not used in the HTML space. + * @property {boolean | null | undefined} [closeSelfClosing=false] + * Close self-closing nodes with an extra slash (`/`): `` instead of + * `` (default: `false`). + * + * See `tightSelfClosing` to control whether a space is used before the + * slash. + * + * Not used in the SVG space. + * @property {boolean | null | undefined} [collapseEmptyAttributes=false] + * Collapse empty attributes: get `class` instead of `class=""` (default: + * `false`). + * + * Not used in the SVG space. + * + * > 👉 **Note**: boolean attributes (such as `hidden`) are always collapsed. + * @property {boolean | null | undefined} [omitOptionalTags=false] + * Omit optional opening and closing tags (default: `false`). + * + * For example, in `
  1. one
  2. two
`, both `` closing + * tags can be omitted. + * The first because it’s followed by another `li`, the last because it’s + * followed by nothing. + * + * Not used in the SVG space. + * @property {boolean | null | undefined} [preferUnquoted=false] + * Leave attributes unquoted if that results in less bytes (default: `false`). + * + * Not used in the SVG space. + * @property {Quote | null | undefined} [quote='"'] + * Preferred quote to use (default: `'"'`). + * @property {boolean | null | undefined} [quoteSmart=false] + * Use the other quote if that results in less bytes (default: `false`). + * @property {Space | null | undefined} [space='html'] + * When an `` element is found in the HTML space, this package already + * automatically switches to and from the SVG space when entering and exiting + * it (default: `'html'`). + * + * > 👉 **Note**: hast is not XML. + * > It supports SVG as embedded in HTML. + * > It does not support the features available in XML. + * > Passing SVG might break but fragments of modern SVG should be fine. + * > Use [`xast`][xast] if you need to support SVG as XML. + * @property {boolean | null | undefined} [tightAttributes=false] + * Join attributes together, without whitespace, if possible: get + * `class="a b"title="c d"` instead of `class="a b" title="c d"` to save + * bytes (default: `false`). + * + * Not used in the SVG space. + * + * > 👉 **Note**: intentionally creates parse errors in markup (how parse + * > errors are handled is well defined, so this works but isn’t pretty). + * @property {boolean | null | undefined} [tightCommaSeparatedLists=false] + * Join known comma-separated attribute values with just a comma (`,`), + * instead of padding them on the right as well (`,␠`, where `␠` represents a + * space) (default: `false`). + * @property {boolean | null | undefined} [tightDoctype=false] + * Drop unneeded spaces in doctypes: `` instead of + * `` to save bytes (default: `false`). + * + * > 👉 **Note**: intentionally creates parse errors in markup (how parse + * > errors are handled is well defined, so this works but isn’t pretty). + * @property {boolean | null | undefined} [tightSelfClosing=false] + * Do not use an extra space when closing self-closing elements: `` + * instead of `` (default: `false`). + * + * > 👉 **Note**: only used if `closeSelfClosing: true` or + * > `closeEmptyElements: true`. + * @property {boolean | null | undefined} [upperDoctype=false] + * Use a ` | null | undefined} [voids] + * Tag names of elements to serialize without closing tag (default: `html-void-elements`). + * + * Not used in the SVG space. + * + * > 👉 **Note**: It’s highly unlikely that you want to pass this, because + * > hast is not for XML, and HTML will not add more void elements. + * + * @typedef {'"' | "'"} Quote + * HTML quotes for attribute values. + * + * @typedef {Omit}>, 'space' | 'quote'>} Settings + * + * @typedef {'html' | 'svg'} Space + * Namespace. + * + * @typedef State + * Info passed around about the current state. + * @property {(node: Nodes, index: number | undefined, parent: Parents | undefined) => string} one + * Serialize one node. + * @property {(node: Parents | undefined) => string} all + * Serialize the children of a parent node. + * @property {Settings} settings + * User configuration. + * @property {Schema} schema + * Current schema. + * @property {Quote} quote + * Preferred quote. + * @property {Quote} alternative + * Alternative quote. */ -import {html, svg} from 'property-information' import {htmlVoidElements} from 'html-void-elements' +import {html, svg} from 'property-information' import {handle} from './handle/index.js' +/** @type {Options} */ +const emptyOptions = {} + +/** @type {CharacterReferences} */ +const emptyCharacterReferences = {} + +/** @type {Array} */ +const emptyChildren = [] + /** * Serialize hast as HTML. * - * @param {Node | Array} tree + * @param {Array | Nodes} tree * Tree to serialize. * @param {Options | null | undefined} [options] - * Configuration. + * Configuration (optional). * @returns {string} * Serialized HTML. */ -// eslint-disable-next-line complexity export function toHtml(tree, options) { - const options_ = options || {} + const options_ = options || emptyOptions const quote = options_.quote || '"' const alternative = quote === '"' ? "'" : '"' @@ -50,7 +204,7 @@ export function toHtml(tree, options) { allowDangerousHtml: options_.allowDangerousHtml || false, voids: options_.voids || htmlVoidElements, characterReferences: - options_.characterReferences || options_.entities || {}, + options_.characterReferences || emptyCharacterReferences, closeSelfClosing: options_.closeSelfClosing || false, closeEmptyElements: options_.closeEmptyElements || false }, @@ -71,11 +225,11 @@ export function toHtml(tree, options) { * * @this {State} * Info passed around about the current state. - * @param {Node} node + * @param {Nodes} node * Node to handle. * @param {number | undefined} index * Index of `node` in `parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of `node`. * @returns {string} * Serialized node. @@ -89,14 +243,14 @@ function one(node, index, parent) { * * @this {State} * Info passed around about the current state. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent whose children to serialize. * @returns {string} */ export function all(parent) { /** @type {Array} */ const results = [] - const children = (parent && parent.children) || [] + const children = (parent && parent.children) || emptyChildren let index = -1 while (++index < children.length) { diff --git a/lib/omission/closing.js b/lib/omission/closing.js index f295d58..a7cd1a1 100644 --- a/lib/omission/closing.js +++ b/lib/omission/closing.js @@ -1,6 +1,6 @@ /** - * @typedef {import('../types.js').Element} Element - * @typedef {import('../types.js').Parent} Parent + * @typedef {import('hast').Element} Element + * @typedef {import('hast').Parents} Parents */ import {whitespace} from 'hast-util-whitespace' @@ -8,26 +8,25 @@ import {siblingAfter} from './util/siblings.js' import {omission} from './omission.js' export const closing = omission({ - html, - head: headOrColgroupOrCaption, body, - p, - li, - dt, + caption: headOrColgroupOrCaption, + colgroup: headOrColgroupOrCaption, dd, - rt: rubyElement, - rp: rubyElement, + dt, + head: headOrColgroupOrCaption, + html, + li, optgroup, option, - menuitem, - colgroup: headOrColgroupOrCaption, - caption: headOrColgroupOrCaption, - thead, + p, + rp: rubyElement, + rt: rubyElement, tbody, - tfoot, - tr, td: cells, - th: cells + tfoot, + th: cells, + thead, + tr }) /** @@ -37,7 +36,7 @@ export const closing = omission({ * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. @@ -58,7 +57,7 @@ function headOrColgroupOrCaption(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. @@ -75,7 +74,7 @@ function html(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. @@ -92,12 +91,11 @@ function body(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. */ -// eslint-disable-next-line complexity function p(_, index, parent) { const next = siblingAfter(parent, index) return next @@ -153,7 +151,7 @@ function p(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. @@ -170,17 +168,17 @@ function li(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. */ function dt(_, index, parent) { const next = siblingAfter(parent, index) - return ( + return Boolean( next && - next.type === 'element' && - (next.tagName === 'dt' || next.tagName === 'dd') + next.type === 'element' && + (next.tagName === 'dt' || next.tagName === 'dd') ) } @@ -191,7 +189,7 @@ function dt(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. @@ -212,7 +210,7 @@ function dd(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. @@ -233,7 +231,7 @@ function rubyElement(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. @@ -250,7 +248,7 @@ function optgroup(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. @@ -264,29 +262,6 @@ function option(_, index, parent) { ) } -/** - * Whether to omit ``. - * - * @param {Element} _ - * Element. - * @param {number | undefined} index - * Index of element in parent. - * @param {Parent | undefined} parent - * Parent of element. - * @returns {boolean} - * Whether the closing tag can be omitted. - */ -function menuitem(_, index, parent) { - const next = siblingAfter(parent, index) - return ( - !next || - (next.type === 'element' && - (next.tagName === 'menuitem' || - next.tagName === 'hr' || - next.tagName === 'menu')) - ) -} - /** * Whether to omit ``. * @@ -294,17 +269,17 @@ function menuitem(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. */ function thead(_, index, parent) { const next = siblingAfter(parent, index) - return ( + return Boolean( next && - next.type === 'element' && - (next.tagName === 'tbody' || next.tagName === 'tfoot') + next.type === 'element' && + (next.tagName === 'tbody' || next.tagName === 'tfoot') ) } @@ -315,7 +290,7 @@ function thead(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. @@ -336,7 +311,7 @@ function tbody(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. @@ -352,7 +327,7 @@ function tfoot(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. @@ -369,7 +344,7 @@ function tr(_, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the closing tag can be omitted. diff --git a/lib/omission/omission.js b/lib/omission/omission.js index d00fc07..692101c 100644 --- a/lib/omission/omission.js +++ b/lib/omission/omission.js @@ -1,5 +1,20 @@ /** - * @typedef {import('../types.js').OmitHandle} OmitHandle + * @typedef {import('hast').Element} Element + * @typedef {import('hast').Parents} Parents + */ + +/** + * @callback OmitHandle + * Check if a tag can be omitted. + * @param {Element} element + * Element to check. + * @param {number | undefined} index + * Index of element in parent. + * @param {Parents | undefined} parent + * Parent of element. + * @returns {boolean} + * Whether to omit a tag. + * */ const own = {}.hasOwnProperty diff --git a/lib/omission/opening.js b/lib/omission/opening.js index 1f0077d..65c9964 100644 --- a/lib/omission/opening.js +++ b/lib/omission/opening.js @@ -1,19 +1,18 @@ /** - * @typedef {import('../types.js').Element} Element - * @typedef {import('../types.js').Parent} Parent - * @typedef {import('../types.js').Content} Content + * @typedef {import('hast').Element} Element + * @typedef {import('hast').Parents} Parents */ import {whitespace} from 'hast-util-whitespace' -import {siblingBefore, siblingAfter} from './util/siblings.js' +import {siblingAfter, siblingBefore} from './util/siblings.js' import {closing} from './closing.js' import {omission} from './omission.js' export const opening = omission({ - html, - head, body, colgroup, + head, + html, tbody }) @@ -94,7 +93,7 @@ function body(node) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the opening tag can be omitted. @@ -114,7 +113,7 @@ function colgroup(node, index, parent) { return false } - return head && head.type === 'element' && head.tagName === 'col' + return Boolean(head && head.type === 'element' && head.tagName === 'col') } /** @@ -124,7 +123,7 @@ function colgroup(node, index, parent) { * Element. * @param {number | undefined} index * Index of element in parent. - * @param {Parent | undefined} parent + * @param {Parents | undefined} parent * Parent of element. * @returns {boolean} * Whether the opening tag can be omitted. @@ -144,5 +143,5 @@ function tbody(node, index, parent) { return false } - return head && head.type === 'element' && head.tagName === 'tr' + return Boolean(head && head.type === 'element' && head.tagName === 'tr') } diff --git a/lib/omission/util/siblings.js b/lib/omission/util/siblings.js index f0d9909..c686a17 100644 --- a/lib/omission/util/siblings.js +++ b/lib/omission/util/siblings.js @@ -1,6 +1,6 @@ /** - * @typedef {import('../../types.js').Parent} Parent - * @typedef {import('../../types.js').Content} Content + * @typedef {import('hast').Parents} Parents + * @typedef {import('hast').RootContent} RootContent */ import {whitespace} from 'hast-util-whitespace' @@ -8,6 +8,9 @@ import {whitespace} from 'hast-util-whitespace' export const siblingAfter = siblings(1) export const siblingBefore = siblings(-1) +/** @type {Array} */ +const emptyChildren = [] + /** * Factory to check siblings in a direction. * @@ -19,15 +22,21 @@ function siblings(increment) { /** * Find applicable siblings in a direction. * - * @param {Parent | null | undefined} parent - * @param {number | null | undefined} index - * @param {boolean | null | undefined} [includeWhitespace=false] - * @returns {Content} + * @template {Parents} Parent + * Parent type. + * @param {Parent | undefined} parent + * Parent. + * @param {number | undefined} index + * Index of child in `parent`. + * @param {boolean | undefined} [includeWhitespace=false] + * Whether to include whitespace (default: `false`). + * @returns {Parent extends {children: Array} ? Child | undefined : never} + * Child of parent. */ function sibling(parent, index, includeWhitespace) { - const siblings = parent ? parent.children : [] + const siblings = parent ? parent.children : emptyChildren let offset = (index || 0) + increment - let next = siblings && siblings[offset] + let next = siblings[offset] if (!includeWhitespace) { while (next && whitespace(next)) { @@ -36,6 +45,7 @@ function siblings(increment) { } } + // @ts-expect-error: it’s a correct child. return next } } diff --git a/lib/types.js b/lib/types.js deleted file mode 100644 index 7c20bed..0000000 --- a/lib/types.js +++ /dev/null @@ -1,175 +0,0 @@ -/** - * @typedef {import('unist').Parent} UnistParent - * @typedef {import('unist').Literal} UnistLiteral - * @typedef {import('hast').Root} Root - * @typedef {import('hast').Comment} Comment - * @typedef {import('hast').DocType} DocType - * @typedef {import('hast').Element} Element - * @typedef {import('hast').Text} Text - * @typedef {import('hast').Content} Content - * @typedef {import('hast').Properties} Properties - * @typedef {import('hast-util-raw/complex-types.js').Raw} Raw - * @typedef {import('stringify-entities').Options} StringifyEntitiesOptions - * @typedef {import('property-information').Schema} Schema - */ - -/** - * @typedef {Content | Root} Node - * @typedef {Extract} Parent - * @typedef {Properties[keyof Properties]} PropertyValue - * - * @callback OmitHandle - * Check if a tag can be omitted. - * @param {Element} element - * Element to check. - * @param {number | undefined} index - * Index of element in parent. - * @param {Parent | undefined} parent - * Parent of element. - * @returns {boolean} - * Whether to omit a tag. - * - * @typedef {'html' | 'svg'} Space - * Namespace. - * - * @typedef {Omit} CharacterReferences - * - * @typedef {'"' | "'"} Quote - * HTML quotes for attribute values. - * - * @typedef Options - * Configuration. - * @property {boolean | null | undefined} [allowDangerousCharacters=false] - * Do not encode some characters which cause XSS vulnerabilities in older - * browsers. - * - * > ⚠️ **Danger**: only set this if you completely trust the content. - * @property {boolean | null | undefined} [allowDangerousHtml=false] - * Allow `raw` nodes and insert them as raw HTML. - * - * When `false`, `Raw` nodes are encoded. - * - * > ⚠️ **Danger**: only set this if you completely trust the content. - * @property {boolean | null | undefined} [allowParseErrors=false] - * Do not encode characters which cause parse errors (even though they work), - * to save bytes. - * - * Not used in the SVG space. - * - * > 👉 **Note**: intentionally creates parse errors in markup (how parse - * > errors are handled is well defined, so this works but isn’t pretty). - * @property {boolean | null | undefined} [bogusComments=false] - * Use “bogus comments” instead of comments to save byes: `` - * instead of ``. - * - * > 👉 **Note**: intentionally creates parse errors in markup (how parse - * > errors are handled is well defined, so this works but isn’t pretty). - * @property {CharacterReferences | null | undefined} [characterReferences] - * Configure how to serialize character references. - * @property {boolean | null | undefined} [closeEmptyElements=false] - * Close SVG elements without any content with slash (`/`) on the opening tag - * instead of an end tag: `` instead of ``. - * - * See `tightSelfClosing` to control whether a space is used before the - * slash. - * - * Not used in the HTML space. - * @property {boolean | null | undefined} [closeSelfClosing=false] - * Close self-closing nodes with an extra slash (`/`): `` instead of - * ``. - * - * See `tightSelfClosing` to control whether a space is used before the - * slash. - * - * Not used in the SVG space. - * @property {boolean | null | undefined} [collapseEmptyAttributes=false] - * Collapse empty attributes: get `class` instead of `class=""`. - * - * Not used in the SVG space. - * - * > 👉 **Note**: boolean attributes (such as `hidden`) are always collapsed. - * @property {CharacterReferences | null | undefined} [entities] - * Deprecated: please use `characterReferences`. - * @property {boolean | null | undefined} [omitOptionalTags=false] - * Omit optional opening and closing tags. - * - * For example, in `
  1. one
  2. two
`, both `` closing - * tags can be omitted. - * The first because it’s followed by another `li`, the last because it’s - * followed by nothing. - * - * Not used in the SVG space. - * @property {boolean | null | undefined} [preferUnquoted=false] - * Leave attributes unquoted if that results in less bytes. - * - * Not used in the SVG space. - * @property {Quote | null | undefined} [quote='"'] - * Preferred quote to use. - * @property {boolean | null | undefined} [quoteSmart=false] - * Use the other quote if that results in less bytes. - * @property {Space | null | undefined} [space='html'] - * When an `` element is found in the HTML space, this package already - * automatically switches to and from the SVG space when entering and exiting - * it. - * - * > 👉 **Note**: hast is not XML. - * > It supports SVG as embedded in HTML. - * > It does not support the features available in XML. - * > Passing SVG might break but fragments of modern SVG should be fine. - * > Use [`xast`][xast] if you need to support SVG as XML. - * @property {boolean | null | undefined} [tightAttributes=false] - * Join attributes together, without whitespace, if possible: get - * `class="a b"title="c d"` instead of `class="a b" title="c d"` to save - * bytes. - * - * Not used in the SVG space. - * - * > 👉 **Note**: intentionally creates parse errors in markup (how parse - * > errors are handled is well defined, so this works but isn’t pretty). - * @property {boolean | null | undefined} [tightCommaSeparatedLists=false] - * Join known comma-separated attribute values with just a comma (`,`), - * instead of padding them on the right as well (`,␠`, where `␠` represents a - * space). - * @property {boolean | null | undefined} [tightDoctype=false] - * Drop unneeded spaces in doctypes: `` instead of - * `` to save bytes. - * - * > 👉 **Note**: intentionally creates parse errors in markup (how parse - * > errors are handled is well defined, so this works but isn’t pretty). - * @property {boolean | null | undefined} [tightSelfClosing=false] - * Do not use an extra space when closing self-closing elements: `` - * instead of ``. - * - * > 👉 **Note**: only used if `closeSelfClosing: true` or - * > `closeEmptyElements: true`. - * @property {boolean | null | undefined} [upperDoctype=false] - * Use a ` | null | undefined} [voids] - * Tag names of elements to serialize without closing tag. - * - * Not used in the SVG space. - * - * > 👉 **Note**: It’s highly unlikely that you want to pass this, because - * > hast is not for XML, and HTML will not add more void elements. - * - * @typedef {Omit}>, 'quote' | 'entities' | 'space'>} Settings - * - * @typedef State - * Info passed around about the current state. - * @property {(node: Node, index: number | undefined, parent: Parent | undefined) => string} one - * Serialize one node. - * @property {(node: Parent | undefined) => string} all - * Serialize the children of a parent node. - * @property {Settings} settings - * User configuration. - * @property {Schema} schema - * Current schema. - * @property {Quote} quote - * Preferred quote. - * @property {Quote} alternative - * Alternative quote. - */ - -export {} diff --git a/package.json b/package.json index bc96da5..7596569 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hast-util-to-html", - "version": "8.0.4", + "version": "9.0.0", "description": "hast utility to serialize to HTML", "license": "MIT", "keywords": [ @@ -26,60 +26,57 @@ ], "sideEffects": false, "type": "module", - "main": "index.js", - "types": "index.d.ts", + "exports": "./index.js", "files": [ "lib/", "index.d.ts", "index.js" ], "dependencies": { - "@types/hast": "^2.0.0", - "@types/unist": "^2.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", - "hast-util-raw": "^7.0.0", - "hast-util-whitespace": "^2.0.0", - "html-void-elements": "^2.0.0", + "hast-util-raw": "^9.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" }, "devDependencies": { - "@types/node": "^18.0.0", - "c8": "^7.0.0", - "hastscript": "^7.0.0", - "prettier": "^2.0.0", + "@types/node": "^20.0.0", + "c8": "^8.0.0", + "hastscript": "^8.0.0", + "prettier": "^3.0.0", "remark-cli": "^11.0.0", "remark-preset-wooorm": "^9.0.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", "build": "tsc --build --clean && tsc --build && 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 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", [ "remark-lint-no-html", false @@ -89,6 +86,25 @@ "typeCoverage": { "atLeast": 100, "detail": true, + "ignoreCatch": true, "strict": true + }, + "xo": { + "overrides": [ + { + "files": [ + "test/**/*.js" + ], + "rules": { + "no-await-in-loop": "off" + } + } + ], + "prettier": true, + "rules": { + "complexity": "off", + "unicorn/prefer-at": "off", + "unicorn/prefer-string-replace-all": "off" + } } } diff --git a/readme.md b/readme.md index 6141e32..8790ee5 100644 --- a/readme.md +++ b/readme.md @@ -55,7 +55,7 @@ also serialize HTML at a higher-level (easier) abstraction. ## 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 hast-util-to-html @@ -64,14 +64,14 @@ npm install hast-util-to-html In Deno with [`esm.sh`][esmsh]: ```js -import {toHtml} from "https://esm.sh/hast-util-to-html@8" +import {toHtml} from "https://esm.sh/hast-util-to-html@9" ``` In browsers with [`esm.sh`][esmsh]: ```html ``` @@ -107,7 +107,7 @@ Yields: ## API -This package exports the identifier [`toHtml`][tohtml]. +This package exports the identifier [`toHtml`][api-to-html]. There is no default export. ### `toHtml(tree[, options])` @@ -118,7 +118,7 @@ Serialize hast as HTML. * `tree` ([`Node`][node] or `Array`) — tree to serialize -* `options` ([`Options`][options], optional) +* `options` ([`Options`][api-options], optional) — configuration ###### Returns @@ -136,14 +136,6 @@ How to serialize character references (TypeScript type). Prefer named character references (`&`) where possible (`boolean`, default: `false`). -###### `useShortestReferences` - -Prefer the shortest possible reference, if that results in less bytes -(`boolean`, default: `false`). - -> ⚠️ **Note**: `useNamedReferences` can be omitted when using -> `useShortestReferences`. - ###### `omitOptionalSemicolons` Whether to omit semicolons when possible (`boolean`, default: `false`). @@ -153,6 +145,14 @@ Whether to omit semicolons when possible (`boolean`, default: `false`). > Omitting semicolons is possible for certain named and numeric references in > some cases. +###### `useShortestReferences` + +Prefer the shortest possible reference, if that results in less bytes +(`boolean`, default: `false`). + +> ⚠️ **Note**: `useNamedReferences` can be omitted when using +> `useShortestReferences`. + ### `Options` Configuration (TypeScript type). @@ -195,7 +195,7 @@ Use “bogus comments” instead of comments to save byes: `` instead ###### `characterReferences` Configure how to serialize character references -([`CharacterReferences`][characterreferences], optional). +([`CharacterReferences`][api-character-references], optional). ###### `closeEmptyElements` @@ -245,7 +245,7 @@ Not used in the SVG space. ###### `quote` -Preferred quote to use ([`Quote`][quote], default: `'"'`). +Preferred quote to use ([`Quote`][api-quote], default: `'"'`). ###### `quoteSmart` @@ -253,7 +253,7 @@ Use the other quote if that results in less bytes (`boolean`, default: `false`). ###### `space` -Which space the document is in ([`Space`][space], default: `'html'`). +Which space the document is in ([`Space`][api-space], default: `'html'`). When an `` element is found in the HTML space, this package already automatically switches to and from the SVG space when entering and exiting it. @@ -341,15 +341,21 @@ followed by browsers such as Chrome and Firefox. ## Types This package is fully typed with [TypeScript][]. -It exports the additional types [`CharacterReferences`][characterreferences], -[`Options`][options], [`Quote`][quote], and [`Space`][space]. +It exports the additional types +[`CharacterReferences`][api-character-references], +[`Options`][api-options], +[`Quote`][api-quote], and +[`Space`][api-space]. ## 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, `hast-util-to-html@^9`, +compatible with Node.js 16. ## Security @@ -390,9 +396,9 @@ abide by its terms. [downloads]: https://www.npmjs.com/package/hast-util-to-html -[size-badge]: https://img.shields.io/bundlephobia/minzip/hast-util-to-html.svg +[size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=hast-util-to-html -[size]: https://bundlephobia.com/result?p=hast-util-to-html +[size]: https://bundlejs.com/?q=hast-util-to-html [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg @@ -440,12 +446,12 @@ abide by its terms. [xast]: https://github.com/syntax-tree/xast -[tohtml]: #tohtmltree-options +[api-to-html]: #tohtmltree-options -[characterreferences]: #characterreferences +[api-character-references]: #characterreferences -[options]: #options +[api-options]: #options -[space]: #space +[api-space]: #space-1 -[quote]: #quote +[api-quote]: #quote-1 diff --git a/test/attribute.js b/test/attribute.js index 2fed373..99e2a5e 100644 --- a/test/attribute.js +++ b/test/attribute.js @@ -1,832 +1,1338 @@ import assert from 'node:assert/strict' import test from 'node:test' import {h} from 'hastscript' +import {toHtml} from 'hast-util-to-html' import {u} from 'unist-builder' -import {toHtml} from '../index.js' test('`element` attributes', async (t) => { - await t.test('unknown', () => { - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {unknown: false}}, [])), - '', - 'should ignore unknowns set to `false`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {unknown: null}}, [])), - '', - 'should ignore unknowns set to `null`' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {unknown: undefined}}, []) - ), - '', - 'should ignore unknowns set to `undefined`' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {unknown: Number.NaN}}, []) - ), - '', - 'should ignore unknowns set to `NaN`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {unknown: true}}, [])), - '', - 'should serialize unknowns set to `true` without value' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {unknown: 'unknown'}}, []) - ), - '', - 'should serialize unknowns set to their name as their name' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {unknown: ['a', 'b']}}, []) - ), - '', - 'should serialize unknown lists as space-separated' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {unknown: 1}}, [])), - '', - 'should serialize unknowns set to an integer as it’s string version' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {unknown: 0}}, [])), - '', - 'should serialize unknowns set to `0`' - ) - - assert.deepEqual( - toHtml( - // @ts-expect-error runtime. - u('element', {tagName: 'i', properties: {unknown: {toString}}}, []) - ), - '', - 'should serialize unknowns set to objects' - ) - }) - - await t.test('known booleans', () => { - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {hidden: false}}, [])), - '', - 'should ignore known booleans set to `false`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {hidden: 0}}, [])), - '', - 'should ignore falsey known booleans' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {hidden: Number.NaN}}, []) - ), - '', - 'should ignore NaN known booleans' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {hidden: true}}, [])), - '', - 'should serialize known booleans set to `true` without value' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {hidden: 'hidden'}}, [])), - '', - 'should serialize known booleans set to their name without value' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {hidden: 1}}, [])), - '', - 'should serialize truthy known booleans without value' - ) + await t.test('should support unknown properties', async function (t) { + await t.test('should ignore unknowns set to `false`', async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {unknown: false}}, [])), + '' + ) + }) + + await t.test('should ignore unknowns set to `null`', async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {unknown: null}}, [])), + '' + ) + }) + + await t.test( + 'should ignore unknowns set to `undefined`', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {unknown: undefined}}, []) + ), + '' + ) + } + ) + + await t.test('should ignore unknowns set to `NaN`', async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {unknown: Number.NaN}}, []) + ), + '' + ) + }) + + await t.test( + 'should serialize unknowns set to `true` without value', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {unknown: true}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize unknowns set to their name as their name', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {unknown: 'unknown'}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize unknown lists as space-separated', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {unknown: ['a', 'b']}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize unknowns set to an integer as it’s string version', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {unknown: 1}}, [])), + '' + ) + } + ) + + await t.test('should serialize unknowns set to `0`', async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {unknown: 0}}, [])), + '' + ) + }) + + await t.test('should serialize unknowns set to objects', async function () { + assert.deepEqual( + toHtml( + // @ts-expect-error: check how the runtime handles a `toString` method on an object. + u('element', {tagName: 'i', properties: {unknown: {toString}}}, []) + ), + '' + ) + }) }) - await t.test('known overloaded booleans', () => { - assert.deepEqual( - toHtml(u('element', {tagName: 'a', properties: {download: false}}, [])), - '', - 'should ignore known overloaded booleans set to `false`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'a', properties: {download: 0}}, [])), - '', - 'should ignore falsey known overloaded booleans' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'a', properties: {download: Number.NaN}}, []) - ), - '', - 'should ignore NaN known overloaded booleans' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'a', properties: {download: true}}, [])), - '', - 'should serialize known overloaded booleans set to `true` without value' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'a', properties: {download: 'download'}}, []) - ), - '', - 'should serialize known overloaded booleans set to their name without value' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'a', properties: {download: ''}}, [])), - '', - 'should serialize known overloaded booleans set to an empty string without value' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'a', properties: {download: 1}}, [])), - '', - 'should serialize truthy known overloaded booleans without value' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'a', properties: {download: 'another'}}, []) - ), - '', - 'should serialize known overloaded booleans set to another string' - ) - }) - - await t.test('known numbers', () => { - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {cols: false}}, [])), - '', - 'should ignore known numbers set to `false`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'a', properties: {cols: Number.NaN}}, [])), - '', - 'should ignore NaN known numbers' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {cols: 0}}, [])), - '', - 'should serialize known numbers set to `0`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {cols: -1}}, [])), - '', - 'should serialize known numbers set to `-1`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {cols: 1}}, [])), - '', - 'should serialize known numbers set to `1`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {cols: Math.PI}}, [])), - '', - 'should serialize known numbers set to `Math.PI`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {cols: true}}, [])), - '', - 'should serialize known numbers set to `true` as without value' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {cols: ''}}, [])), - '', - 'should serialize known numbers set to an empty string' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {cols: 'cols'}}, [])), - '', - 'should serialize known numbers set to their name' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {cols: 'another'}}, [])), - '', - 'should serialize known numbers set to a string' - ) - - assert.deepEqual( - // @ts-expect-error runtime. - toHtml(u('element', {tagName: 'i', properties: {cols: {toString}}}, [])), - '', - 'should serialize known numbers set to an object' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {cols: ['a', 'b']}}, [])), - '', - 'should serialize known numbers set to an array of strings' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {cols: [0, 50]}}, [])), - '', - 'should serialize known numbers set to an array of numbers' - ) - - assert.deepEqual( - toHtml( - // @ts-expect-error runtime. - u('element', {tagName: 'i', properties: {cols: [true, false]}}, []) - ), - '', - 'should serialize known numbers set to an array of booleans' + await t.test('shold support known booleans', async function (t) { + await t.test( + 'should ignore known booleans set to `false`', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {hidden: false}}, [])), + '' + ) + } + ) + + await t.test('should ignore falsey known booleans', async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {hidden: 0}}, [])), + '' + ) + }) + + await t.test('should ignore NaN known booleans', async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {hidden: Number.NaN}}, []) + ), + '' + ) + }) + + await t.test( + 'should serialize known booleans set to `true` without value', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {hidden: true}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known booleans set to their name without value', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {hidden: 'hidden'}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize truthy known booleans without value', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {hidden: 1}}, [])), + '' + ) + } ) }) - await t.test('known space-separated lists', () => { - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {className: false}}, [])), - '', - 'should ignore known space-separated lists set to `false`' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'a', properties: {className: Number.NaN}}, []) - ), - '', - 'should ignore NaN known space-separated lists' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {className: 0}}, [])), - '', - 'should serialize known space-separated lists set to `0`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {className: true}}, [])), - '', - 'should serialize known space-separated lists set to `true` as without value' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {className: ''}}, [])), - '', - 'should serialize known space-separated lists set to an empty string' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {className: 'class'}}, []) - ), - '', - 'should serialize known space-separated lists set to their attribute name' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {className: 'className'}}, []) - ), - '', - 'should serialize known space-separated lists set to their property name' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {className: 'another'}}, []) - ), - '', - 'should serialize known space-separated lists set to a string' - ) - - assert.deepEqual( - toHtml( - // @ts-expect-error runtime. - u('element', {tagName: 'i', properties: {className: {toString}}}, []) - ), - '', - 'should serialize known space-separated lists set to an object' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {className: ['a', 'b']}}, []) - ), - '', - 'should serialize known space-separated lists set to an array of strings' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {className: [0, 50]}}, []) - ), - '', - 'should serialize known space-separated lists set to an array of numbers' - ) - - assert.deepEqual( - toHtml( - // @ts-expect-error runtime. - u('element', {tagName: 'i', properties: {className: [true, false]}}, []) - ), - '', - 'should serialize known space-separated lists set to an array of booleans' + await t.test('should support known overloaded booleans', async function (t) { + await t.test( + 'should ignore known overloaded booleans set to `false`', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'a', properties: {download: false}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should ignore falsey known overloaded booleans', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'a', properties: {download: 0}}, [])), + '' + ) + } + ) + + await t.test( + 'should ignore NaN known overloaded booleans', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'a', properties: {download: Number.NaN}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known overloaded booleans set to `true` without value', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'a', properties: {download: true}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known overloaded booleans set to their name without value', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'a', properties: {download: 'download'}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known overloaded booleans set to an empty string without value', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'a', properties: {download: ''}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize truthy known overloaded booleans without value', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'a', properties: {download: 1}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known overloaded booleans set to another string', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'a', properties: {download: 'another'}}, []) + ), + '' + ) + } ) }) - - await t.test('known comma-separated lists', () => { - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {accept: false}}, [])), - '', - 'should ignore known comma-separated lists set to `false`' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'a', properties: {accept: Number.NaN}}, []) - ), - '', - 'should ignore NaN known comma-separated lists' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {accept: 0}}, [])), - '', - 'should serialize known comma-separated lists set to `0`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {accept: true}}, [])), - '', - 'should serialize known comma-separated lists set to `true` as without value' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {accept: ''}}, [])), - '', - 'should serialize known comma-separated lists set to an empty string' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {accept: 'accept'}}, [])), - '', - 'should serialize known comma-separated lists set to their name' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {accept: 'another'}}, [])), - '', - 'should serialize known comma-separated lists set to a string' - ) - - assert.deepEqual( - toHtml( - // @ts-expect-error runtime. - u('element', {tagName: 'i', properties: {accept: {toString}}}, []) - ), - '', - 'should serialize known comma-separated lists set to an object' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {accept: ['a', 'b']}}, []) - ), - '', - 'should serialize known comma-separated lists set to an array of strings' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {accept: [0, 50]}}, [])), - '', - 'should serialize known comma-separated lists set to an array of numbers' - ) - - assert.deepEqual( - toHtml( - // @ts-expect-error runtime. - u('element', {tagName: 'i', properties: {accept: [true, false]}}, []) - ), - '', - 'should serialize known comma-separated lists set to an array of booleans' + await t.test('should support known numbers', async function (t) { + await t.test( + 'should ignore known numbers set to `false`', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {cols: false}}, [])), + '' + ) + } + ) + + await t.test('should ignore NaN known numbers', async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'a', properties: {cols: Number.NaN}}, []) + ), + '' + ) + }) + + await t.test( + 'should serialize known numbers set to `0`', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {cols: 0}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known numbers set to `-1`', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {cols: -1}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known numbers set to `1`', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {cols: 1}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known numbers set to `Math.PI`', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {cols: Math.PI}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known numbers set to `true` as without value', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {cols: true}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known numbers set to an empty string', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {cols: ''}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known numbers set to their name', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {cols: 'cols'}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known numbers set to a string', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {cols: 'another'}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known numbers set to an object', + async function () { + assert.deepEqual( + toHtml( + // @ts-expect-error: check how the runtime handles a `toString` method on an object. + u('element', {tagName: 'i', properties: {cols: {toString}}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known numbers set to an array of strings', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {cols: ['a', 'b']}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known numbers set to an array of numbers', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {cols: [0, 50]}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known numbers set to an array of booleans', + async function () { + assert.deepEqual( + toHtml( + // @ts-expect-error: check how the runtime handles booleans in an array. + u('element', {tagName: 'i', properties: {cols: [true, false]}}, []) + ), + '' + ) + } ) }) - await t.test('known normals', () => { - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: false}}, [])), - '', - 'should ignore known normals set to `false`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: Number.NaN}}, [])), - '', - 'should ignore NaN known normals' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: 0}}, [])), - '', - 'should serialize known normals set to `0`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: true}}, [])), - '', - 'should serialize known normals set to `true` as without value' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: ''}}, [])), - '', - 'should serialize known normals set to an empty string' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: 'id'}}, [])), - '', - 'should serialize known normals set to their name' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: 'another'}}, [])), - '', - 'should serialize known normals set to a string' - ) - - assert.deepEqual( - // @ts-expect-error runtime. - toHtml(u('element', {tagName: 'i', properties: {id: {toString}}}, [])), - '', - 'should serialize known normals set to an object' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: ['a', 'b']}}, [])), - '', - 'should serialize known normals set to an array of strings as a space-separated list' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: [0, 50]}}, [])), - '', - 'should serialize known normals set to an array of numbers as a space-separated list' - ) - - assert.deepEqual( - // @ts-expect-error runtime. - toHtml(u('element', {tagName: 'i', properties: {id: [true, false]}}, [])), - '', - 'should serialize known normals set to an array of booleans as a space-separated list' + await t.test( + 'should support known space-separated lists', + async function (t) { + await t.test( + 'should ignore known space-separated lists set to `false`', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {className: false}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should ignore NaN known space-separated lists', + async function () { + assert.deepEqual( + toHtml( + u( + 'element', + {tagName: 'a', properties: {className: Number.NaN}}, + [] + ) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known space-separated lists set to `0`', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {className: 0}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known space-separated lists set to `true` as without value', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {className: true}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known space-separated lists set to an empty string', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {className: ''}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known space-separated lists set to their attribute name', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {className: 'class'}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known space-separated lists set to their property name', + async function () { + assert.deepEqual( + toHtml( + u( + 'element', + {tagName: 'i', properties: {className: 'className'}}, + [] + ) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known space-separated lists set to a string', + async function () { + assert.deepEqual( + toHtml( + u( + 'element', + {tagName: 'i', properties: {className: 'another'}}, + [] + ) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known space-separated lists set to an object', + async function () { + assert.deepEqual( + toHtml( + // @ts-expect-error: check how the runtime handles a `toString` method on an object. + u( + 'element', + {tagName: 'i', properties: {className: {toString}}}, + [] + ) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known space-separated lists set to an array of strings', + async function () { + assert.deepEqual( + toHtml( + u( + 'element', + {tagName: 'i', properties: {className: ['a', 'b']}}, + [] + ) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known space-separated lists set to an array of numbers', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {className: [0, 50]}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known space-separated lists set to an array of booleans', + async function () { + assert.deepEqual( + toHtml( + // @ts-expect-error: check how the runtime handles booleans in an array. + u( + 'element', + {tagName: 'i', properties: {className: [true, false]}}, + [] + ) + ), + '' + ) + } + ) + } + ) + + await t.test( + 'should support known comma-separated lists', + async function (t) { + await t.test( + 'should ignore known comma-separated lists set to `false`', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {accept: false}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should ignore NaN known comma-separated lists', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'a', properties: {accept: Number.NaN}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known comma-separated lists set to `0`', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {accept: 0}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known comma-separated lists set to `true` as without value', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {accept: true}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known comma-separated lists set to an empty string', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {accept: ''}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known comma-separated lists set to their name', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {accept: 'accept'}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known comma-separated lists set to a string', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {accept: 'another'}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known comma-separated lists set to an object', + async function () { + assert.deepEqual( + toHtml( + // @ts-expect-error: check how the runtime handles a `toString` method on an object. + u('element', {tagName: 'i', properties: {accept: {toString}}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known comma-separated lists set to an array of strings', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {accept: ['a', 'b']}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known comma-separated lists set to an array of numbers', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {accept: [0, 50]}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known comma-separated lists set to an array of booleans', + async function () { + assert.deepEqual( + toHtml( + // @ts-expect-error: check how the runtime handles booleans in an array. + u( + 'element', + {tagName: 'i', properties: {accept: [true, false]}}, + [] + ) + ), + '' + ) + } + ) + } + ) + + await t.test('should support known normals', async function (t) { + await t.test( + 'should ignore known normals set to `false`', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {id: false}}, [])), + '' + ) + } + ) + + await t.test('should ignore NaN known normals', async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {id: Number.NaN}}, [])), + '' + ) + }) + + await t.test( + 'should serialize known normals set to `0`', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {id: 0}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known normals set to `true` as without value', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {id: true}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known normals set to an empty string', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {id: ''}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known normals set to their name', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {id: 'id'}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known normals set to a string', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {id: 'another'}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known normals set to an object', + async function () { + assert.deepEqual( + toHtml( + // @ts-expect-error: check how the runtime handles a `toString` method on an object. + u('element', {tagName: 'i', properties: {id: {toString}}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known normals set to an array of strings as a space-separated list', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {id: ['a', 'b']}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize known normals set to an array of numbers as a space-separated list', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {id: [0, 50]}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize known normals set to an array of booleans as a space-separated list', + async function () { + assert.deepEqual( + toHtml( + // @ts-expect-error: check how the runtime handles booleans in an array. + u('element', {tagName: 'i', properties: {id: [true, false]}}, []) + ), + '' + ) + } ) }) - await t.test('data properties', () => { - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {dataId: false}}, [])), - '', - 'should ignore data properties set to `false`' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {dataId: Number.NaN}}, []) - ), - '', - 'should ignore NaN data properties' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {dataId: 0}}, [])), - '', - 'should serialize data properties set to `0`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {dataId: true}}, [])), - '', - 'should serialize data properties set to `true` as without value' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {dataId: ''}}, [])), - '', - 'should serialize data properties set to an empty string' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {dataId: 'dataId'}}, [])), - '', - 'should serialize data properties set to their property name' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {dataId: 'data-id'}}, [])), - '', - 'should serialize data properties set to their attribute name' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {dataId: 'another'}}, [])), - '', - 'should serialize data properties set to a string' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {data123: 'a'}}, [])), - '', - 'should serialize numeric-first data properties set to a string' - ) - - assert.deepEqual( - toHtml( - // @ts-expect-error runtime. - u('element', {tagName: 'i', properties: {dataId: {toString}}}, []) - ), - '', - 'should serialize data properties set to an object' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {dataId: ['a', 'b']}}, []) - ), - '', - 'should serialize data properties set to an array of strings as a space-separated list' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {dataId: [0, 50]}}, [])), - '', - 'should serialize data properties set to an array of numbers as a space-separated list' - ) - - assert.deepEqual( - toHtml( - // @ts-expect-error runtime. - u('element', {tagName: 'i', properties: {dataId: [true, false]}}, []) - ), - '', - 'should serialize data properties set to an array of booleans as a space-separated list' + await t.test('should support data properties', async function (t) { + await t.test( + 'should ignore data properties set to `false`', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {dataId: false}}, [])), + '' + ) + } + ) + + await t.test('should ignore NaN data properties', async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {dataId: Number.NaN}}, []) + ), + '' + ) + }) + + await t.test( + 'should serialize data properties set to `0`', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {dataId: 0}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize data properties set to `true` as without value', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {dataId: true}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize data properties set to an empty string', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {dataId: ''}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize data properties set to their property name', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {dataId: 'dataId'}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize data properties set to their attribute name', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {dataId: 'data-id'}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize data properties set to a string', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {dataId: 'another'}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize numeric-first data properties set to a string', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {data123: 'a'}}, [])), + '' + ) + } + ) + + await t.test( + 'should serialize data properties set to an object', + async function () { + assert.deepEqual( + toHtml( + // @ts-expect-error: check how the runtime handles a `toString` method on an object. + u('element', {tagName: 'i', properties: {dataId: {toString}}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize data properties set to an array of strings as a space-separated list', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {dataId: ['a', 'b']}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize data properties set to an array of numbers as a space-separated list', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {dataId: [0, 50]}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize data properties set to an array of booleans as a space-separated list', + async function () { + assert.deepEqual( + toHtml( + // @ts-expect-error: check how the runtime handles booleans in an array. + u( + 'element', + {tagName: 'i', properties: {dataId: [true, false]}}, + [] + ) + ), + '' + ) + } ) }) - await t.test('collapseEmptyAttributes', () => { - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: ''}}, [])), - '', - 'should show empty string attributes' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: ''}}, []), { - collapseEmptyAttributes: true - }), - '', - 'should collapse empty string attributes in `collapseEmptyAttributes` mode' + await t.test('should support `collapseEmptyAttributes`', async function (t) { + await t.test('should show empty string attributes', async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {id: ''}}, [])), + '' + ) + }) + + await t.test( + 'should collapse empty string attributes in `collapseEmptyAttributes` mode', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {id: ''}}, []), { + collapseEmptyAttributes: true + }), + '' + ) + } ) }) - await t.test('tightAttributes', () => { - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {title: 'a', id: 'b'}}, []) - ), - '', - 'should serialize multiple properties' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {title: 'a', id: 'b'}}, []), - { - tightAttributes: true - } - ), - '', - 'should serialize multiple properties tightly in `tightAttributes` mode' + await t.test('should support `tightAttributes`', async function (t) { + await t.test('should serialize multiple properties', async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {title: 'a', id: 'b'}}, []) + ), + '' + ) + }) + + await t.test( + 'should serialize multiple properties tightly in `tightAttributes` mode', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {title: 'a', id: 'b'}}, []), + { + tightAttributes: true + } + ), + '' + ) + } ) }) - await t.test('tightCommaSeparatedLists', () => { - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {accept: ['a', 'b']}}, []) - ), - '', - 'should serialize comma-separated attributes' - ) - - assert.deepEqual( - toHtml( - u('element', {tagName: 'i', properties: {accept: ['a', 'b']}}, []), - { - tightCommaSeparatedLists: true - } - ), - '', - 'should serialize comma-separated attributes tighly in `tightCommaSeparatedLists` mode' + await t.test('should support `tightCommaSeparatedLists`', async function (t) { + await t.test( + 'should serialize comma-separated attributes', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {accept: ['a', 'b']}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should serialize comma-separated attributes tighly in `tightCommaSeparatedLists` mode', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {accept: ['a', 'b']}}, []), + { + tightCommaSeparatedLists: true + } + ), + '' + ) + } ) }) - await t.test('quote', () => { - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: 'a'}}, [])), - '', - 'should quote attribute values with double quotes by default' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: 'a'}}, []), { - quote: "'" - }), - "", - "should quote attribute values with single quotes if `quote: '\\''`" - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: 'a'}}, []), { - quote: '"' - }), - '', - "should quote attribute values with double quotes if `quote: '\\\"'`" - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: "'a'"}}, []), { - quote: "'" - }), - "", - "should quote attribute values with single quotes if `quote: '\\''` even if they occur in value" - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: '"a"'}}, []), { - quote: '"' - }), - '', - "should quote attribute values with double quotes if `quote: '\\\"'` even if they occur in value" - ) - - assert.throws( - () => { - // @ts-expect-error runtime. - toHtml(h('img'), {quote: '`'}) - }, - /Invalid quote ```, expected `'` or `"`/, - 'should throw on invalid quotes' - ) + await t.test('should support `quote`', async function (t) { + await t.test( + 'should quote attribute values with double quotes by default', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {title: 'a'}}, [])), + '' + ) + } + ) + + await t.test( + "should quote attribute values with single quotes if `quote: '\\''`", + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {title: 'a'}}, []), { + quote: "'" + }), + "" + ) + } + ) + + await t.test( + "should quote attribute values with double quotes if `quote: '\\\"'`", + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {title: 'a'}}, []), { + quote: '"' + }), + '' + ) + } + ) + + await t.test( + "should quote attribute values with single quotes if `quote: '\\''` even if they occur in value", + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {title: "'a'"}}, []), { + quote: "'" + }), + "" + ) + } + ) + + await t.test( + "should quote attribute values with double quotes if `quote: '\\\"'` even if they occur in value", + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {title: '"a"'}}, []), { + quote: '"' + }), + '' + ) + } + ) + + await t.test('should throw on invalid quotes', async function () { + assert.throws(function () { + toHtml( + h('img'), + // @ts-expect-error: check how the runtime handles an incorrect `quote` + {quote: '`'} + ) + }, /Invalid quote ```, expected `'` or `"`/) + }) }) - await t.test('quoteSmart', () => { - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: 'a'}}, []), { - allowDangerousCharacters: true, - quoteSmart: true - }), - '', - 'should quote attribute values with primary quotes by default' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: "'a'"}}, []), { - allowDangerousCharacters: true, - quoteSmart: true - }), - '', - 'should quote attribute values with primary quotes if the alternative occurs' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: "'\"a'"}}, []), { - allowDangerousCharacters: true, - quoteSmart: true - }), - '', - 'should quote attribute values with primary quotes if they occur less than the alternative' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: '"a\''}}, []), { - allowDangerousCharacters: true, - quoteSmart: true - }), - '', - 'should quote attribute values with primary quotes if they occur as much as alternatives (#1)' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: '"\'a\'"'}}, []), { - allowDangerousCharacters: true, - quoteSmart: true - }), - '', - 'should quote attribute values with primary quotes if they occur as much as alternatives (#1)' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: '"a"'}}, []), { - allowDangerousCharacters: true, - quoteSmart: true - }), - '', - 'should quote attribute values with alternative quotes if the primary occurs' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: '"\'a"'}}, []), { - allowDangerousCharacters: true, - quoteSmart: true - }), - '', - 'should quote attribute values with alternative quotes if they occur less than the primary' + await t.test('should support `quoteSmart`', async function (t) { + await t.test( + 'should quote attribute values with primary quotes by default', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {title: 'a'}}, []), { + allowDangerousCharacters: true, + quoteSmart: true + }), + '' + ) + } + ) + + await t.test( + 'should quote attribute values with primary quotes if the alternative occurs', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {title: "'a'"}}, []), { + allowDangerousCharacters: true, + quoteSmart: true + }), + '' + ) + } + ) + + await t.test( + 'should quote attribute values with primary quotes if they occur less than the alternative', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {title: "'\"a'"}}, []), + { + allowDangerousCharacters: true, + quoteSmart: true + } + ), + '' + ) + } + ) + + await t.test( + 'should quote attribute values with primary quotes if they occur as much as alternatives (#1)', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {title: '"a\''}}, []), + { + allowDangerousCharacters: true, + quoteSmart: true + } + ), + '' + ) + } + ) + + await t.test( + 'should quote attribute values with primary quotes if they occur as much as alternatives (#1)', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {title: '"\'a\'"'}}, []), + { + allowDangerousCharacters: true, + quoteSmart: true + } + ), + '' + ) + } + ) + + await t.test( + 'should quote attribute values with alternative quotes if the primary occurs', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {title: '"a"'}}, []), { + allowDangerousCharacters: true, + quoteSmart: true + }), + '' + ) + } + ) + + await t.test( + 'should quote attribute values with alternative quotes if they occur less than the primary', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {title: '"\'a"'}}, []), + { + allowDangerousCharacters: true, + quoteSmart: true + } + ), + '' + ) + } ) }) - await t.test('preferUnquoted', () => { - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: 'a'}}, []), { - preferUnquoted: true - }), - '', - 'should omit quotes in `preferUnquoted`' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: 'a b'}}, []), { - preferUnquoted: true - }), - '', - 'should keep quotes in `preferUnquoted` and impossible' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {id: ''}}, []), { - preferUnquoted: true - }), - '', - 'should not add `=` when omitting quotes on empty values' + await t.test('should support `preferUnquoted`', async function (t) { + await t.test('should omit quotes in `preferUnquoted`', async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {id: 'a'}}, []), { + preferUnquoted: true + }), + '' + ) + }) + + await t.test( + 'should keep quotes in `preferUnquoted` and impossible', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {id: 'a b'}}, []), { + preferUnquoted: true + }), + '' + ) + } + ) + + await t.test( + 'should not add `=` when omitting quotes on empty values', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {id: ''}}, []), { + preferUnquoted: true + }), + '' + ) + } ) }) - await t.test('entities in attributes', () => { - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {'3<5\0': 'a'}}, [])), - '', - 'should encode entities in attribute names' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: '3<5\0'}}, [])), - '', - 'should encode entities in attribute values' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {'3=5\0': 'a'}}, []), { - allowParseErrors: true - }), - '', - 'should not encode characters in attribute names which cause parse errors, but work, in `allowParseErrors` mode' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: '3=5\0'}}, []), { - allowParseErrors: true - }), - '', - 'should not encode characters in attribute values which cause parse errors, but work, in `allowParseErrors` mode' - ) - - assert.deepEqual( - toHtml(u('element', {tagName: 'i', properties: {title: "3'5"}}, []), { - allowDangerousCharacters: true - }), - '', - 'should not encode characters which cause XSS issues in older browsers, in `allowDangerousCharacters` mode' + await t.test('should support entities in attributes', async function (t) { + await t.test( + 'should encode entities in attribute names', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {'3<5\0': 'a'}}, [])), + '' + ) + } + ) + + await t.test( + 'should encode entities in attribute values', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {title: '3<5\0'}}, []) + ), + '' + ) + } + ) + + await t.test( + 'should not encode characters in attribute names which cause parse errors, but work, in `allowParseErrors` mode', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {'3=5\0': 'a'}}, []), { + allowParseErrors: true + }), + '' + ) + } + ) + + await t.test( + 'should not encode characters in attribute values which cause parse errors, but work, in `allowParseErrors` mode', + async function () { + assert.deepEqual( + toHtml( + u('element', {tagName: 'i', properties: {title: '3=5\0'}}, []), + { + allowParseErrors: true + } + ), + '' + ) + } + ) + + await t.test( + 'should not encode characters which cause XSS issues in older browsers, in `allowDangerousCharacters` mode', + async function () { + assert.deepEqual( + toHtml(u('element', {tagName: 'i', properties: {title: "3'5"}}, []), { + allowDangerousCharacters: true + }), + '' + ) + } ) }) }) diff --git a/test/comment.js b/test/comment.js index 83099d5..eb0004f 100644 --- a/test/comment.js +++ b/test/comment.js @@ -1,31 +1,35 @@ import assert from 'node:assert/strict' import test from 'node:test' +import {toHtml} from 'hast-util-to-html' import {u} from 'unist-builder' -import {toHtml} from '../index.js' -test('`comment`', () => { - assert.deepEqual( - toHtml(u('comment', 'alpha')), - '', - 'should serialize `comment`s' - ) +test('`comment`', async function (t) { + await t.test('should serialize `comment`s', async function () { + assert.deepEqual(toHtml(u('comment', 'alpha')), '') + }) - assert.deepEqual( - toHtml(u('comment', 'AT&T')), - '', - 'should not encode `comment`s' - ) + await t.test('should not encode `comment`s', async function () { + assert.deepEqual(toHtml(u('comment', 'AT&T')), '') + }) - assert.deepEqual( - toHtml(u('comment', 'asd'), {bogusComments: true}), - '', - '`bogusComments`: should serialize bogus comments' + await t.test( + 'should serialize bogus comments (`bogusComments`)', + async function () { + assert.deepEqual( + toHtml(u('comment', 'asd'), {bogusComments: true}), + '' + ) + } ) - assert.deepEqual( - toHtml(u('comment', 'ad'), {bogusComments: true}), - '', - '`bogusComments`: should prevent breaking out of bogus comments' + await t.test( + 'should prevent breaking out of bogus comments (`bogusComments`)', + async function () { + assert.deepEqual( + toHtml(u('comment', 'ad'), {bogusComments: true}), + '' + ) + } ) // https://html.spec.whatwg.org/multipage/syntax.html#comments @@ -48,14 +52,18 @@ test('`comment`', () => { let index = -1 while (++index < matrix.length) { - assert.deepEqual( - toHtml(u('comment', matrix[index][0])), - '', - 'security: should ' + + await t.test( + 'should ' + (matrix[index][1] === undefined ? 'allow' : 'prevent') + ' `' + matrix[index][0] + - '`' + '` (security)', + async function () { + assert.deepEqual( + toHtml(u('comment', matrix[index][0])), + '' + ) + } ) } }) diff --git a/test/core.js b/test/core.js index f40a85e..38129e7 100644 --- a/test/core.js +++ b/test/core.js @@ -1,39 +1,35 @@ import assert from 'node:assert/strict' import test from 'node:test' -import {u} from 'unist-builder' import {h} from 'hastscript' -import {toHtml} from '../index.js' -import * as mod from '../index.js' +import {toHtml} from 'hast-util-to-html' +import {u} from 'unist-builder' -test('toHtml()', () => { - assert.deepEqual( - Object.keys(mod).sort(), - ['toHtml'], - 'should expose the public api' - ) +test('toHtml()', async function (t) { + await t.test('should expose the public api', async function () { + assert.deepEqual(Object.keys(await import('hast-util-to-html')).sort(), [ + 'toHtml' + ]) + }) - assert.throws( - () => { - // @ts-expect-error runtime. + await t.test('should throw on non-nodes', async function () { + assert.throws(function () { + // @ts-expect-error: check how the runtime handles a non-node. toHtml(true) - }, - /Expected node, not `true`/, - 'should throw on non-nodes' - ) + }, /Expected node, not `true`/) + }) - assert.throws( - () => { - // @ts-expect-error runtime. + await t.test('should throw on unknown nodes', async function () { + assert.throws(function () { + // @ts-expect-error: check how the runtime handles an unknown node. toHtml(u('foo', [])) - }, - /Cannot compile unknown node `foo`/, - 'should throw on unknown nodes' - ) + }, /Cannot compile unknown node `foo`/) + }) + + await t.test('should support a node', async function () { + assert.equal(toHtml(h('')), '
') + }) - assert.equal(toHtml(h('')), '
', 'should support a node') - assert.equal( - toHtml([h('b'), h('i')]), - '', - 'should support an array' - ) + await t.test('should support an array', async function () { + assert.equal(toHtml([h('b'), h('i')]), '') + }) }) diff --git a/test/doctype.js b/test/doctype.js index 6e26220..031e23b 100644 --- a/test/doctype.js +++ b/test/doctype.js @@ -1,27 +1,30 @@ import assert from 'node:assert/strict' import test from 'node:test' +import {toHtml} from 'hast-util-to-html' import {u} from 'unist-builder' -import {toHtml} from '../index.js' -test('`doctype`', () => { - assert.deepEqual( - // @ts-expect-error hast types out of date. - toHtml(u('doctype')), - '', - 'should serialize doctypes' - ) +test('`doctype`', async function (t) { + await t.test('should serialize doctypes', async function () { + assert.deepEqual(toHtml(u('doctype')), '') + }) - assert.deepEqual( - // @ts-expect-error hast types out of date. - toHtml(u('doctype'), {tightDoctype: true}), - '', - 'should serialize doctypes tightly in `tightDoctype` mode' + await t.test( + 'should serialize doctypes tightly in `tightDoctype` mode', + async function () { + assert.deepEqual( + toHtml(u('doctype'), {tightDoctype: true}), + '' + ) + } ) - assert.deepEqual( - // @ts-expect-error hast types out of date. - toHtml(u('doctype'), {upperDoctype: true}), - '', - 'should serialize uppercase doctypes in `upperDoctype` mode' + await t.test( + 'should serialize uppercase doctypes in `upperDoctype` mode', + async function () { + assert.deepEqual( + toHtml(u('doctype'), {upperDoctype: true}), + '' + ) + } ) }) diff --git a/test/element.js b/test/element.js index 00dd8af..4654269 100644 --- a/test/element.js +++ b/test/element.js @@ -1,87 +1,97 @@ import assert from 'node:assert/strict' import test from 'node:test' import {h} from 'hastscript' -import {toHtml} from '../index.js' +import {toHtml} from 'hast-util-to-html' -test('`element`', () => { - assert.deepEqual( - toHtml(h('i', 'bravo')), - 'bravo', - 'should serialize `element`s' - ) +test('`element`', async function (t) { + await t.test('should serialize `element`s', async function () { + assert.deepEqual(toHtml(h('i', 'bravo')), 'bravo') + }) - assert.deepEqual( - toHtml(h('foo')), - '', - 'should serialize unknown `element`s' - ) + await t.test('should serialize unknown `element`s', async function () { + assert.deepEqual(toHtml(h('foo')), '') + }) - assert.deepEqual( - toHtml(h('img')), - '', - 'should serialize void `element`s' - ) + await t.test('should serialize void `element`s', async function () { + assert.deepEqual(toHtml(h('img')), '') + }) - assert.deepEqual( - toHtml(h('foo'), {voids: ['foo']}), - '', - 'should serialize given void `element`s' - ) + await t.test('should serialize given void `element`s', async function () { + assert.deepEqual(toHtml(h('foo'), {voids: ['foo']}), '') + }) - assert.deepEqual( - toHtml(h('img'), {closeSelfClosing: true}), - '', - 'should serialize with ` /` in `closeSelfClosing` mode' + await t.test( + 'should serialize with ` /` in `closeSelfClosing` mode', + async function () { + assert.deepEqual(toHtml(h('img'), {closeSelfClosing: true}), '') + } ) - assert.deepEqual( - toHtml(h('img'), {closeSelfClosing: true, tightSelfClosing: true}), - '', - 'should serialize voids with `/` in `closeSelfClosing` and `tightSelfClosing` mode' + await t.test( + 'should serialize voids with `/` in `closeSelfClosing` and `tightSelfClosing` mode', + async function () { + assert.deepEqual( + toHtml(h('img'), {closeSelfClosing: true, tightSelfClosing: true}), + '' + ) + } ) - assert.deepEqual( - toHtml(h('input', {type: 'checkbox'}), { - preferUnquoted: true, - tightSelfClosing: true, - closeSelfClosing: true - }), - '', - 'should serialize voids with `/` in `closeSelfClosing` and `tightSelfClosing` mode, w/ space after an unquoted attribute (1)' + await t.test( + 'should serialize voids with `/` in `closeSelfClosing` and `tightSelfClosing` mode, w/ space after an unquoted attribute (1)', + async function () { + assert.deepEqual( + toHtml(h('input', {type: 'checkbox'}), { + preferUnquoted: true, + tightSelfClosing: true, + closeSelfClosing: true + }), + '' + ) + } ) - assert.deepEqual( - toHtml(h('img', {src: 'index.jpg'}), { - preferUnquoted: true, - closeSelfClosing: true, - tightSelfClosing: true - }), - '', - 'should serialize voids with `/` in `closeSelfClosing` and `tightSelfClosing` mode, w/ space after an unquoted attribute (2)' + await t.test( + 'should serialize voids with `/` in `closeSelfClosing` and `tightSelfClosing` mode, w/ space after an unquoted attribute (2)', + async function () { + assert.deepEqual( + toHtml(h('img', {src: 'index.jpg'}), { + preferUnquoted: true, + closeSelfClosing: true, + tightSelfClosing: true + }), + '' + ) + } ) - assert.deepEqual( - toHtml(h('img', {title: '/'}), { - preferUnquoted: true, - closeSelfClosing: true, - tightSelfClosing: true - }), - '', - 'should serialize voids with a ` /` in if an unquoted attribute ends with `/`' + await t.test( + 'should serialize voids with a ` /` in if an unquoted attribute ends with `/`', + async function () { + assert.deepEqual( + toHtml(h('img', {title: '/'}), { + preferUnquoted: true, + closeSelfClosing: true, + tightSelfClosing: true + }), + '' + ) + } ) - assert.deepEqual( - toHtml({ - type: 'element', - tagName: 'template', - properties: {}, - children: [], - content: { - type: 'root', - children: [h('p', [h('b', 'Bold'), ' and ', h('i', 'italic'), '.'])] - } - }), - '', - 'should support `