@ -1,8 +1,20 @@
import { assert } from 'vitest' ;
import { assert } from 'vitest' ;
/** @param {Element} node */
/ * *
function clean _children ( node ) {
* @ param { Element } node
* @ param { { preserveComments : boolean } } opts
* /
function clean _children ( node , opts ) {
let previous = null ;
let previous = null ;
let has _element _children = false ;
let template =
node . nodeName === 'TEMPLATE' ? /** @type {HTMLTemplateElement} */ ( node ) : undefined ;
if ( template ) {
const div = document . createElement ( 'div' ) ;
div . append ( template . content ) ;
node = div ;
}
// sort attributes
// sort attributes
const attributes = Array . from ( node . attributes ) . sort ( ( a , b ) => {
const attributes = Array . from ( node . attributes ) . sort ( ( a , b ) => {
@ -14,6 +26,11 @@ function clean_children(node) {
} ) ;
} ) ;
attributes . forEach ( ( attr ) => {
attributes . forEach ( ( attr ) => {
// Strip out the special onload/onerror hydration events from the test output
if ( ( attr . name === 'onload' || attr . name === 'onerror' ) && attr . value === 'this.__e=event' ) {
return ;
}
node . setAttribute ( attr . name , attr . value ) ;
node . setAttribute ( attr . name , attr . value ) ;
} ) ;
} ) ;
@ -27,24 +44,43 @@ function clean_children(node) {
node . tagName !== 'tspan'
node . tagName !== 'tspan'
) {
) {
node . removeChild ( child ) ;
node . removeChild ( child ) ;
continue ;
}
}
text . data = text . data . replace ( /[ \t\n\r\f]+/g, '\n ') ;
text . data = text . data . replace ( /[ ^\S]+/g, ' ') ;
if ( previous && previous . nodeType === 3 ) {
if ( previous && previous . nodeType === 3 ) {
const prev = /** @type {Text} */ ( previous ) ;
const prev = /** @type {Text} */ ( previous ) ;
prev . data += text . data ;
prev . data += text . data ;
prev . data = prev . data . replace ( /[ \t\n\r\f]+/g , '\n' ) ;
node . removeChild ( text ) ;
node . removeChild ( text ) ;
text = prev ;
text = prev ;
text . data = text . data . replace ( /[^\S]+/g , ' ' ) ;
continue ;
}
}
}
} else if ( child . nodeType === 8 ) {
if ( child . nodeType === 8 && ! opts . preserveComments ) {
// comment
// comment
// do nothing
child . remove ( ) ;
} else {
continue ;
clean _children ( /** @type {Element} */ ( child ) ) ;
}
// add newlines for better readability and potentially recurse into children
if ( child . nodeType === 1 || child . nodeType === 8 ) {
if ( previous ? . nodeType === 3 ) {
const prev = /** @type {Text} */ ( previous ) ;
prev . data = prev . data . replace ( /^[^\S]+$/ , '\n' ) ;
} else if ( previous ? . nodeType === 1 || previous ? . nodeType === 8 ) {
node . insertBefore ( document . createTextNode ( '\n' ) , child ) ;
}
if ( child . nodeType === 1 ) {
has _element _children = true ;
clean _children ( /** @type {Element} */ ( child ) , opts ) ;
}
}
}
previous = child ;
previous = child ;
@ -53,37 +89,36 @@ function clean_children(node) {
// collapse whitespace
// collapse whitespace
if ( node . firstChild && node . firstChild . nodeType === 3 ) {
if ( node . firstChild && node . firstChild . nodeType === 3 ) {
const text = /** @type {Text} */ ( node . firstChild ) ;
const text = /** @type {Text} */ ( node . firstChild ) ;
text . data = text . data . replace ( /^[ \t\n\r\f]+/ , '' ) ;
text . data = text . data . trimStart ( ) ;
if ( ! text . data . length ) node . removeChild ( text ) ;
}
}
if ( node . lastChild && node . lastChild . nodeType === 3 ) {
if ( node . lastChild && node . lastChild . nodeType === 3 ) {
const text = /** @type {Text} */ ( node . lastChild ) ;
const text = /** @type {Text} */ ( node . lastChild ) ;
text . data = text . data . replace ( /[ \t\n\r\f]+$/ , '' ) ;
text . data = text . data . trimEnd ( ) ;
if ( ! text . data . length ) node . removeChild ( text ) ;
}
// indent code for better readability
if ( has _element _children && node . parentNode ) {
node . innerHTML = ` \n \ ${ node . innerHTML . replace ( /\n/g , '\n ' ) } \n ` ;
}
if ( template ) {
template . innerHTML = node . innerHTML ;
}
}
}
}
/ * *
/ * *
* @ param { Window } window
* @ param { Window } window
* @ param { string } html
* @ param { string } html
* @ param { { removeDataSvelte ? : boolean , preserveComments ? : boolean } } param2
* @ param { { preserveComments? : boolean } } opts
* /
* /
export function normalize _html (
export function normalize _html ( window , html , { preserveComments = false } = { } ) {
window ,
html ,
{ removeDataSvelte = false , preserveComments = false }
) {
try {
try {
const node = window . document . createElement ( 'div' ) ;
const node = window . document . createElement ( 'div' ) ;
node . innerHTML = html
. replace ( /(<!(--)?.*?\2>)/g , preserveComments ? '$1' : '' )
node . innerHTML = html . trim ( ) ;
. replace ( /(data-svelte-h="[^"]+")/g , removeDataSvelte ? '' : '$1' )
clean _children ( node , { preserveComments } ) ;
. replace ( />[ \t\n\r\f]+</g , '><' )
// Strip out the special onload/onerror hydration events from the test output
. replace ( /\s?onerror="this.__e=event"|\s?onload="this.__e=event"/g , '' )
. trim ( ) ;
clean _children ( node ) ;
return node . innerHTML ;
return node . innerHTML ;
} catch ( err ) {
} catch ( err ) {
throw new Error ( ` Failed to normalize HTML: \n ${ html } \n Cause: ${ err } ` ) ;
throw new Error ( ` Failed to normalize HTML: \n ${ html } \n Cause: ${ err } ` ) ;
@ -99,53 +134,47 @@ export function normalize_new_line(html) {
}
}
/ * *
/ * *
* @ param { { removeDataSvelte ? : boolean } } options
* /
export function setup _html _equal ( options = { } ) {
/ * *
* @ param { string } actual
* @ param { string } actual
* @ param { string } expected
* @ param { string } expected
* @ param { string } [ message ]
* @ param { string } [ message ]
* /
* /
const assert _html _equal = ( actual , expected , message ) => {
export const assert _html _equal = ( actual , expected , message ) => {
try {
try {
assert . deepEqual (
assert . deepEqual ( normalize _html ( window , actual ) , normalize _html ( window , expected ) , message ) ;
normalize _html ( window , actual , options ) ,
normalize _html ( window , expected , options ) ,
message
) ;
} catch ( e ) {
} catch ( e ) {
if ( Error . captureStackTrace )
if ( Error . captureStackTrace )
Error . captureStackTrace ( /** @type {Error} */ ( e ) , assert _html _equal ) ;
Error . captureStackTrace ( /** @type {Error} */ ( e ) , assert _html _equal ) ;
throw e ;
throw e ;
}
}
} ;
} ;
/ * *
/ * *
*
*
* @ param { string } actual
* @ param { string } actual
* @ param { string } expected
* @ param { string } expected
* @ param { { preserveComments ? : boolean , withoutNormalizeHtml ? : boolean } } param2
* @ param { { preserveComments ? : boolean , withoutNormalizeHtml ? : boolean } } param2
* @ param { string } [ message ]
* @ param { string } [ message ]
* /
* /
const assert _html _equal _with _options = (
export const assert _html _equal _with _options = (
actual ,
actual ,
expected ,
expected ,
{ preserveComments , withoutNormalizeHtml } ,
{ preserveComments , withoutNormalizeHtml } ,
message
message
) => {
) => {
try {
try {
assert . deepEqual (
assert . deepEqual (
withoutNormalizeHtml
withoutNormalizeHtml
? normalize _new _line ( actual . trim ( ) )
? normalize _new _line ( actual . trim ( ) ) . replace (
. replace ( /(\sdata-svelte-h="[^"]+")/g , options . removeDataSvelte ? '' : '$1' )
/(<!(--)?.*?\2>)/g ,
. replace ( /(<!(--)?.*?\2>)/g , preserveComments !== false ? '$1' : '' )
preserveComments !== false ? '$1' : ''
: normalize _html ( window , actual . trim ( ) , { ... options , preserveComments } ) ,
)
: normalize _html ( window , actual . trim ( ) , { preserveComments } ) ,
withoutNormalizeHtml
withoutNormalizeHtml
? normalize _new _line ( expected . trim ( ) )
? normalize _new _line ( expected . trim ( ) ) . replace (
. replace ( /(\sdata-svelte-h="[^"]+")/g , options . removeDataSvelte ? '' : '$1' )
/(<!(--)?.*?\2>)/g ,
. replace ( /(<!(--)?.*?\2>)/g , preserveComments !== false ? '$1' : '' )
preserveComments !== false ? '$1' : ''
: normalize _html ( window , expected . trim ( ) , { ... options , preserveComments } ) ,
)
: normalize _html ( window , expected . trim ( ) , { preserveComments } ) ,
message
message
) ;
) ;
} catch ( e ) {
} catch ( e ) {
@ -153,13 +182,4 @@ export function setup_html_equal(options = {}) {
Error . captureStackTrace ( /** @type {Error} */ ( e ) , assert _html _equal _with _options ) ;
Error . captureStackTrace ( /** @type {Error} */ ( e ) , assert _html _equal _with _options ) ;
throw e ;
throw e ;
}
}
} ;
} ;
return {
assert _html _equal ,
assert _html _equal _with _options
} ;
}
// Common case without options
export const { assert _html _equal , assert _html _equal _with _options } = setup _html _equal ( ) ;