diff --git a/__tests__/client/index.test.ts b/__tests__/client/index.test.ts index be3073aa..37fa5da8 100644 --- a/__tests__/client/index.test.ts +++ b/__tests__/client/index.test.ts @@ -1,61 +1,74 @@ -// @vitest-environment jsdom -import htmlToDOM from '../../src/client/html-to-dom'; -import htmlCases from '../cases/html'; -import { - isBrowser, - parseDOM, - runTests, - testCaseSensitiveTags, - throwErrors, -} from '../helpers'; - -describe('client parser', () => { - // @ts-expect-error argument of type is not assignable - throwErrors(htmlToDOM); - // @ts-expect-error argument of type is not assignable - runTests(htmlToDOM, parseDOM, htmlCases); - testCaseSensitiveTags(htmlToDOM); - - if (isBrowser()) { - describe('trustedTypePolicy', () => { - it('uses policy before setting template innerHTML', () => { - const trustedTypePolicy = { - createHTML: vi.fn((input: string) => input), - }; - - htmlToDOM('
test
', { trustedTypePolicy }); - - expect(trustedTypePolicy.createHTML).toHaveBeenCalledOnce(); - expect(trustedTypePolicy.createHTML).toHaveBeenCalledWith( - '
test
', - ); - }); - - it('uses policy before setting document innerHTML', () => { - const trustedTypePolicy = { - createHTML: vi.fn((input: string) => input), - }; - - htmlToDOM('
test
', { trustedTypePolicy }); - - expect(trustedTypePolicy.createHTML).toHaveBeenCalledOnce(); - expect(trustedTypePolicy.createHTML).toHaveBeenCalledWith( - '
test
', - ); - }); - }); - - describe('performance', () => { - it('executes 1000 times in less than 50ms', () => { - let times = 1000; - const start = performance.now(); - while (--times) { - htmlToDOM('
test
'); - } - const end = performance.now(); - const elapsed = end - start; - expect(elapsed).below(50); - }); - }); - } -}); +// @vitest-environment jsdom +import htmlToDOM from '../../src/client/html-to-dom'; +import htmlCases from '../cases/html'; +import { + isBrowser, + parseDOM, + runTests, + testCaseSensitiveTags, + throwErrors, +} from '../helpers'; + +describe('client parser', () => { + // @ts-expect-error argument of type is not assignable + throwErrors(htmlToDOM); + // @ts-expect-error argument of type is not assignable + runTests(htmlToDOM, parseDOM, htmlCases); + testCaseSensitiveTags(htmlToDOM); + + it('uses policy before setting innerHTML', () => { + const trustedTypePolicy = { + createHTML: vi.fn((input: string) => input), + }; + + htmlToDOM('
test
', { trustedTypePolicy }); + + expect(trustedTypePolicy.createHTML).toHaveBeenCalledOnce(); + expect(trustedTypePolicy.createHTML).toHaveBeenCalledWith( + '
test
', + ); + }); + + if (isBrowser()) { + describe('trustedTypePolicy', () => { + it('uses policy before setting template innerHTML', () => { + const trustedTypePolicy = { + createHTML: vi.fn((input: string) => input), + }; + + htmlToDOM('
test
', { trustedTypePolicy }); + + expect(trustedTypePolicy.createHTML).toHaveBeenCalledOnce(); + expect(trustedTypePolicy.createHTML).toHaveBeenCalledWith( + '
test
', + ); + }); + + it('uses policy before setting document innerHTML', () => { + const trustedTypePolicy = { + createHTML: vi.fn((input: string) => input), + }; + + htmlToDOM('
test
', { trustedTypePolicy }); + + expect(trustedTypePolicy.createHTML).toHaveBeenCalledOnce(); + expect(trustedTypePolicy.createHTML).toHaveBeenCalledWith( + '
test
', + ); + }); + }); + + describe('performance', () => { + it('executes 1000 times in less than 50ms', () => { + let times = 1000; + const start = performance.now(); + while (--times) { + htmlToDOM('
test
'); + } + const end = performance.now(); + const elapsed = end - start; + expect(elapsed).below(50); + }); + }); + } +}); diff --git a/eslint.config.mts b/eslint.config.mts index 2796fd68..854ac0b2 100644 --- a/eslint.config.mts +++ b/eslint.config.mts @@ -62,4 +62,11 @@ export default defineConfig([ '@typescript-eslint/no-require-imports': 'off', }, }, + { + files: ['__tests__/types/**'], + + rules: { + '@typescript-eslint/no-unsafe-call': 'off', + }, + }, ]); diff --git a/src/client/domparser.ts b/src/client/domparser.ts index 5f995b25..47f95142 100644 --- a/src/client/domparser.ts +++ b/src/client/domparser.ts @@ -1,5 +1,5 @@ import { escapeSpecialCharacters, hasOpenTag } from './utilities'; -import type { TrustedTypePolicyLike } from '../types'; +import type { TrustedTypePolicy } from '../types'; // constants const HTML = 'html'; @@ -9,7 +9,7 @@ const FIRST_TAG_REGEX = /<([a-zA-Z]+[0-9]?)/; // e.g.,

function getHTMLForInnerHTML( html: string, - trustedTypePolicy?: TrustedTypePolicyLike, + trustedTypePolicy?: TrustedTypePolicy, ) { return trustedTypePolicy ? trustedTypePolicy.createHTML(html) : html; } @@ -20,7 +20,7 @@ function getHTMLForInnerHTML( let parseFromDocument = ( html: string, tagName?: string, - trustedTypePolicy?: TrustedTypePolicyLike, + trustedTypePolicy?: TrustedTypePolicy, ): Document => { throw new Error( 'This browser does not support `document.implementation.createHTMLDocument`', @@ -30,7 +30,7 @@ let parseFromDocument = ( let parseFromString = ( html: string, tagName?: string, - trustedTypePolicy?: TrustedTypePolicyLike, + trustedTypePolicy?: TrustedTypePolicy, ): Document => { void trustedTypePolicy; throw new Error( @@ -59,7 +59,7 @@ if (typeof DOMParser === 'function') { parseFromString = ( html: string, tagName?: string, - trustedTypePolicy?: TrustedTypePolicyLike, + trustedTypePolicy?: TrustedTypePolicy, ): Document => { void trustedTypePolicy; if (tagName) { @@ -91,7 +91,7 @@ if (typeof document === 'object' && document.implementation) { parseFromDocument = function ( html: string, tagName?: string, - trustedTypePolicy?: TrustedTypePolicyLike, + trustedTypePolicy?: TrustedTypePolicy, ): Document { if (tagName) { const element = htmlDocument.documentElement.querySelector(tagName); @@ -124,7 +124,7 @@ const template = let parseFromTemplate: ( html: string, - trustedTypePolicy?: TrustedTypePolicyLike, + trustedTypePolicy?: TrustedTypePolicy, ) => NodeList; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition @@ -137,7 +137,7 @@ if (template && template.content) { */ parseFromTemplate = ( html: string, - trustedTypePolicy?: TrustedTypePolicyLike, + trustedTypePolicy?: TrustedTypePolicy, ): NodeList => { template.innerHTML = getHTMLForInnerHTML( html, @@ -159,7 +159,7 @@ const createNodeList = () => document.createDocumentFragment().childNodes; */ export default function domparser( html: string, - trustedTypePolicy?: TrustedTypePolicyLike, + trustedTypePolicy?: TrustedTypePolicy, ): NodeList { // Escape special characters before parsing html = escapeSpecialCharacters(html); diff --git a/src/types.ts b/src/types.ts index ca0be218..37e142ae 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,11 +6,11 @@ export type { Comment, Element, ProcessingInstruction, Text }; export type DOMNode = Comment | Element | ProcessingInstruction | Text; -export interface TrustedTypePolicyLike { +export interface TrustedTypePolicy { createHTML(input: string): unknown; } export type HTMLDOMParserOptions = ParserOptions & DomHandlerOptions & { - trustedTypePolicy?: TrustedTypePolicyLike; + trustedTypePolicy?: TrustedTypePolicy; };