[ci] format

This commit is contained in:
bholmesdev 2023-07-24 23:36:32 +00:00 committed by astrobot-houston
parent 7461e82c81
commit 25e04a2ecb
13 changed files with 711 additions and 724 deletions

View file

@ -413,13 +413,11 @@ By default, Markdoc will not recognize HTML markup as semantic content.
To achieve a more Markdown-like experience, where HTML elements can be included alongside your content, set `allowHTML:true` as a `markdoc` integration option. This will enable HTML parsing in Markdoc markup. To achieve a more Markdown-like experience, where HTML elements can be included alongside your content, set `allowHTML:true` as a `markdoc` integration option. This will enable HTML parsing in Markdoc markup.
> **Warning** > **Warning**
> When `allowHTML` is enabled, HTML markup inside Markdoc documents will be rendered as actual HTML elements (including `<script>`), making attack vectors like XSS possible. > When `allowHTML` is enabled, HTML markup inside Markdoc documents will be rendered as actual HTML elements (including `<script>`), making attack vectors like XSS possible.
> >
> Ensure that any HTML markup comes from trusted sources. > Ensure that any HTML markup comes from trusted sources.
```js {7} "allowHTML: true" ```js {7} "allowHTML: true"
// astro.config.mjs // astro.config.mjs
import { defineConfig } from 'astro/config'; import { defineConfig } from 'astro/config';

View file

@ -11,11 +11,11 @@ import { MarkdocError, isComponentConfig, isValidUrl, prependForwardSlash } from
import { emitESMImage } from 'astro/assets/utils'; import { emitESMImage } from 'astro/assets/utils';
import path from 'node:path'; import path from 'node:path';
import type * as rollup from 'rollup'; import type * as rollup from 'rollup';
import { htmlTokenTransform } from './html/transform/html-token-transform.js';
import type { MarkdocConfigResult } from './load-config.js'; import type { MarkdocConfigResult } from './load-config.js';
import type { MarkdocIntegrationOptions } from './options.js';
import { setupConfig } from './runtime.js'; import { setupConfig } from './runtime.js';
import { getMarkdocTokenizer } from './tokenizer.js'; import { getMarkdocTokenizer } from './tokenizer.js';
import type { MarkdocIntegrationOptions } from './options.js';
import { htmlTokenTransform } from './html/transform/html-token-transform.js';
export async function getContentEntryType({ export async function getContentEntryType({
markdocConfigResult, markdocConfigResult,
@ -24,8 +24,7 @@ export async function getContentEntryType({
}: { }: {
astroConfig: AstroConfig; astroConfig: AstroConfig;
markdocConfigResult?: MarkdocConfigResult; markdocConfigResult?: MarkdocConfigResult;
options?: MarkdocIntegrationOptions, options?: MarkdocIntegrationOptions;
}): Promise<ContentEntryType> { }): Promise<ContentEntryType> {
return { return {
extensions: ['.mdoc'], extensions: ['.mdoc'],

View file

@ -1,8 +1,9 @@
import { styleToObject } from './style-to-object.js';
import { styleToObject } from "./style-to-object.js"; export function parseInlineCSSToReactLikeObject(
css: string | undefined | null
export function parseInlineCSSToReactLikeObject(css: string | undefined | null): React.CSSProperties | undefined { ): React.CSSProperties | undefined {
if (typeof css === "string") { if (typeof css === 'string') {
const cssObject: Record<string, string> = {}; const cssObject: Record<string, string> = {};
styleToObject(css, (originalCssDirective: string, value: string) => { styleToObject(css, (originalCssDirective: string, value: string) => {
const reactCssDirective = convertCssDirectiveNameToReactCamelCase(originalCssDirective); const reactCssDirective = convertCssDirectiveNameToReactCamelCase(originalCssDirective);
@ -16,7 +17,7 @@ export function parseInlineCSSToReactLikeObject(css: string | undefined | null):
function convertCssDirectiveNameToReactCamelCase(original: string): string { function convertCssDirectiveNameToReactCamelCase(original: string): string {
// capture group 1 is the character to capitalize, the hyphen is omitted by virtue of being outside the capture group // capture group 1 is the character to capitalize, the hyphen is omitted by virtue of being outside the capture group
const replaced = original.replace(/-([a-z0-9])/ig, (_match, char) => { const replaced = original.replace(/-([a-z0-9])/gi, (_match, char) => {
return char.toUpperCase(); return char.toUpperCase();
}); });
return replaced; return replaced;

View file

@ -117,9 +117,7 @@ export function parseInlineStyles(style, options) {
* @throws {Error} * @throws {Error}
*/ */
function error(msg) { function error(msg) {
const err = new Error( const err = new Error(options.source + ':' + lineno + ':' + column + ': ' + msg);
options.source + ':' + lineno + ':' + column + ': ' + msg
);
err.reason = msg; err.reason = msg;
err.filename = options.source; err.filename = options.source;
err.line = lineno; err.line = lineno;
@ -203,7 +201,7 @@ export function parseInlineStyles(style, options) {
return pos({ return pos({
type: TYPE_COMMENT, type: TYPE_COMMENT,
comment: str comment: str,
}); });
} }
@ -230,9 +228,7 @@ export function parseInlineStyles(style, options) {
const ret = pos({ const ret = pos({
type: TYPE_DECLARATION, type: TYPE_DECLARATION,
property: trim(prop[0].replace(COMMENT_REGEX, EMPTY_STRING)), property: trim(prop[0].replace(COMMENT_REGEX, EMPTY_STRING)),
value: val value: val ? trim(val[0].replace(COMMENT_REGEX, EMPTY_STRING)) : EMPTY_STRING,
? trim(val[0].replace(COMMENT_REGEX, EMPTY_STRING))
: EMPTY_STRING
}); });
// ; // ;
@ -265,7 +261,7 @@ export function parseInlineStyles(style, options) {
whitespace(); whitespace();
return declarations(); return declarations();
}; }
/** /**
* Trim `str`. * Trim `str`.

View file

@ -28,7 +28,7 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import { parseInlineStyles } from "./parse-inline-styles.js"; import { parseInlineStyles } from './parse-inline-styles.js';
/** /**
* Parses inline style to object. * Parses inline style to object.

View file

@ -1,2 +1,2 @@
export { htmlTokenTransform } from "./transform/html-token-transform"; export { htmlTag } from './tagdefs/html.tag';
export { htmlTag } from "./tagdefs/html.tag"; export { htmlTokenTransform } from './transform/html-token-transform';

View file

@ -1,19 +1,17 @@
import type { Config, Schema } from "@markdoc/markdoc"; import type { Config, Schema } from '@markdoc/markdoc';
import Markdoc from "@markdoc/markdoc"; import Markdoc from '@markdoc/markdoc';
// local // local
import { parseInlineCSSToReactLikeObject } from "../css/parse-inline-css-to-react.js"; import { parseInlineCSSToReactLikeObject } from '../css/parse-inline-css-to-react.js';
// a Markdoc tag that will render a given HTML element and its attributes, as produced by the htmlTokenTransform function // a Markdoc tag that will render a given HTML element and its attributes, as produced by the htmlTokenTransform function
export const htmlTag: Schema<Config, never> = { export const htmlTag: Schema<Config, never> = {
attributes: { attributes: {
name: { type: String, required: true }, name: { type: String, required: true },
attrs: { type: Object }, attrs: { type: Object },
}, },
transform(node, config) { transform(node, config) {
const { name, attrs: unsafeAttributes } = node.attributes; const { name, attrs: unsafeAttributes } = node.attributes;
const children = node.transformChildren(config); const children = node.transformChildren(config);
@ -21,7 +19,7 @@ export const htmlTag: Schema<Config, never> = {
const { style, ...safeAttributes } = unsafeAttributes as Record<string, unknown>; const { style, ...safeAttributes } = unsafeAttributes as Record<string, unknown>;
// if the inline "style" attribute is present we need to parse the HTML into a react-like React.CSSProperties object // if the inline "style" attribute is present we need to parse the HTML into a react-like React.CSSProperties object
if (typeof style === "string") { if (typeof style === 'string') {
const styleObject = parseInlineCSSToReactLikeObject(style); const styleObject = parseInlineCSSToReactLikeObject(style);
safeAttributes.style = styleObject; safeAttributes.style = styleObject;
} }

View file

@ -1,10 +1,8 @@
import type * as Token from 'markdown-it/lib/token';
import { Parser } from 'htmlparser2';
import { Tokenizer } from '@markdoc/markdoc'; import { Tokenizer } from '@markdoc/markdoc';
import { Parser } from 'htmlparser2';
import type * as Token from 'markdown-it/lib/token';
export function htmlTokenTransform(tokenizer: Tokenizer, tokens: Token[]): Token[] { export function htmlTokenTransform(tokenizer: Tokenizer, tokens: Token[]): Token[] {
const output: Token[] = []; const output: Token[] = [];
// hold a lazy buffer of text and process it only when necessary // hold a lazy buffer of text and process it only when necessary
@ -18,21 +16,24 @@ export function htmlTokenTransform(tokenizer: Tokenizer, tokens: Token[]): Token
// process the current text buffer w/ Markdoc's Tokenizer for tokens // process the current text buffer w/ Markdoc's Tokenizer for tokens
const processTextBuffer = () => { const processTextBuffer = () => {
if (textBuffer.length > 0) { if (textBuffer.length > 0) {
// tokenize the text buffer to look for structural markup tokens // tokenize the text buffer to look for structural markup tokens
const toks = tokenizer.tokenize(textBuffer); const toks = tokenizer.tokenize(textBuffer);
// when we tokenize some raw text content, it's basically treated like Markdown, and will result in a paragraph wrapper, which we don't want // when we tokenize some raw text content, it's basically treated like Markdown, and will result in a paragraph wrapper, which we don't want
// in this scenario, we just want to generate a text token, but, we have to tokenize it in case there's other structural markup // in this scenario, we just want to generate a text token, but, we have to tokenize it in case there's other structural markup
if (toks.length === 3) { if (toks.length === 3) {
const first = toks[0]; const first = toks[0];
const second = toks[1]; const second = toks[1];
const third: Token | undefined = toks.at(2); const third: Token | undefined = toks.at(2);
if (first.type === 'paragraph_open' && second.type === 'inline' && (third && third.type === 'paragraph_close') && Array.isArray(second.children)) { if (
first.type === 'paragraph_open' &&
second.type === 'inline' &&
third &&
third.type === 'paragraph_close' &&
Array.isArray(second.children)
) {
for (const tok of second.children as Token[]) { for (const tok of second.children as Token[]) {
// if the given token is a 'text' token and its trimmed content is the same as the pre-tokenized text buffer, use the original // if the given token is a 'text' token and its trimmed content is the same as the pre-tokenized text buffer, use the original
// text buffer instead to preserve leading/trailing whitespace that is lost during tokenization of pure text content // text buffer instead to preserve leading/trailing whitespace that is lost during tokenization of pure text content
@ -62,8 +63,8 @@ export function htmlTokenTransform(tokenizer: Tokenizer, tokens: Token[]): Token
}; };
// create an incremental HTML parser that tracks HTML tag open, close and text content // create an incremental HTML parser that tracks HTML tag open, close and text content
const parser = new Parser({ const parser = new Parser(
{
oncdatastart() { oncdatastart() {
inCDATA = true; inCDATA = true;
}, },
@ -74,7 +75,6 @@ export function htmlTokenTransform(tokenizer: Tokenizer, tokens: Token[]): Token
// when an HTML tag opens... // when an HTML tag opens...
onopentag(name, attrs) { onopentag(name, attrs) {
// process any buffered text to be treated as text node before the currently opening HTML tag // process any buffered text to be treated as text node before the currently opening HTML tag
processTextBuffer(); processTextBuffer();
@ -90,11 +90,9 @@ export function htmlTokenTransform(tokenizer: Tokenizer, tokens: Token[]): Token
], ],
}, },
} as Token); } as Token);
}, },
ontext(content: string | null | undefined) { ontext(content: string | null | undefined) {
if (inCDATA) { if (inCDATA) {
// ignore entirely while inside CDATA // ignore entirely while inside CDATA
return; return;
@ -108,7 +106,6 @@ export function htmlTokenTransform(tokenizer: Tokenizer, tokens: Token[]): Token
// when an HTML tag closes... // when an HTML tag closes...
onclosetag(name) { onclosetag(name) {
// process any buffered text to be treated as a text node inside the currently closing HTML tag // process any buffered text to be treated as a text node inside the currently closing HTML tag
processTextBuffer(); processTextBuffer();
@ -118,26 +115,22 @@ export function htmlTokenTransform(tokenizer: Tokenizer, tokens: Token[]): Token
nesting: -1, nesting: -1,
meta: { meta: {
tag: 'html-tag', tag: 'html-tag',
attributes: [ attributes: [{ type: 'attribute', name: 'name', value: name }],
{ type: 'attribute', name: 'name', value: name },
],
}, },
} as Token); } as Token);
}, },
},
}, { {
decodeEntities: false, decodeEntities: false,
recognizeCDATA: true, recognizeCDATA: true,
recognizeSelfClosing: true, recognizeSelfClosing: true,
}); }
);
// for every detected token... // for every detected token...
for (const token of tokens) { for (const token of tokens) {
// if it was an HTML token, write the HTML text into the HTML parser // if it was an HTML token, write the HTML text into the HTML parser
if (token.type.startsWith('html')) { if (token.type.startsWith('html')) {
// as the parser encounters opening/closing HTML tags, it will push Markdoc Tag nodes into the output stack // as the parser encounters opening/closing HTML tags, it will push Markdoc Tag nodes into the output stack
parser.write(token.content); parser.write(token.content);
@ -197,14 +190,12 @@ function mutateAndCollapseExtraParagraphsUnderHtml(tokens: Token[]): void {
} }
} }
/** /**
* *
* @param token * @param token
* @returns * @returns
*/ */
function findExtraParagraphUnderHtml(tokens: Token[]): number | null { function findExtraParagraphUnderHtml(tokens: Token[]): number | null {
if (tokens.length < 5) { if (tokens.length < 5) {
return null; return null;
} }
@ -226,15 +217,15 @@ function findExtraParagraphUnderHtml(tokens: Token[]): number | null {
} }
function isExtraParagraphPatternMatch(slice: Token[]): boolean { function isExtraParagraphPatternMatch(slice: Token[]): boolean {
const match = isHtmlTagOpen(slice[0]) const match =
&& isParagraphOpen(slice[1]) isHtmlTagOpen(slice[0]) &&
&& isInline(slice[2]) isParagraphOpen(slice[1]) &&
&& isParagraphClose(slice[3]) isInline(slice[2]) &&
&& isHtmlTagClose(slice[4]); isParagraphClose(slice[3]) &&
isHtmlTagClose(slice[4]);
return match; return match;
} }
function isHtmlTagOpen(token: Token): boolean { function isHtmlTagOpen(token: Token): boolean {
return token.type === 'tag_open' && token.meta && token.meta.tag === 'html-tag'; return token.type === 'tag_open' && token.meta && token.meta.tag === 'html-tag';
} }

View file

@ -25,7 +25,9 @@ export default function markdocIntegration(options?: MarkdocIntegrationOptions):
markdocConfigResult = await loadMarkdocConfig(astroConfig); markdocConfigResult = await loadMarkdocConfig(astroConfig);
addContentEntryType(await getContentEntryType({ markdocConfigResult, astroConfig, options })); addContentEntryType(
await getContentEntryType({ markdocConfigResult, astroConfig, options })
);
updateConfig({ updateConfig({
vite: { vite: {

View file

@ -10,15 +10,18 @@ import type { AstroInstance } from 'astro';
import { createComponent, renderComponent } from 'astro/runtime/server/index.js'; import { createComponent, renderComponent } from 'astro/runtime/server/index.js';
import type { AstroMarkdocConfig } from './config.js'; import type { AstroMarkdocConfig } from './config.js';
import { setupHeadingConfig } from './heading-ids.js'; import { setupHeadingConfig } from './heading-ids.js';
import type { MarkdocIntegrationOptions } from './options.js';
import { htmlTag } from './html/tagdefs/html.tag.js'; import { htmlTag } from './html/tagdefs/html.tag.js';
import type { MarkdocIntegrationOptions } from './options.js';
/** /**
* Merge user config with default config and set up context (ex. heading ID slugger) * Merge user config with default config and set up context (ex. heading ID slugger)
* Called on each file's individual transform. * Called on each file's individual transform.
* TODO: virtual module to merge configs per-build instead of per-file? * TODO: virtual module to merge configs per-build instead of per-file?
*/ */
export async function setupConfig(userConfig: AstroMarkdocConfig = {}, options: MarkdocIntegrationOptions | undefined): Promise<MergedConfig> { export async function setupConfig(
userConfig: AstroMarkdocConfig = {},
options: MarkdocIntegrationOptions | undefined
): Promise<MergedConfig> {
let defaultConfig: AstroMarkdocConfig = setupHeadingConfig(); let defaultConfig: AstroMarkdocConfig = setupHeadingConfig();
if (userConfig.extends) { if (userConfig.extends) {
@ -41,7 +44,10 @@ export async function setupConfig(userConfig: AstroMarkdocConfig = {}, options:
} }
/** Used for synchronous `getHeadings()` function */ /** Used for synchronous `getHeadings()` function */
export function setupConfigSync(userConfig: AstroMarkdocConfig = {}, options: MarkdocIntegrationOptions | undefined): MergedConfig { export function setupConfigSync(
userConfig: AstroMarkdocConfig = {},
options: MarkdocIntegrationOptions | undefined
): MergedConfig {
const defaultConfig: AstroMarkdocConfig = setupHeadingConfig(); const defaultConfig: AstroMarkdocConfig = setupHeadingConfig();
let merged = mergeConfig(defaultConfig, userConfig); let merged = mergeConfig(defaultConfig, userConfig);
@ -160,7 +166,11 @@ export function collectHeadings(
} }
} }
export function createGetHeadings(stringifiedAst: string, userConfig: AstroMarkdocConfig, options: MarkdocIntegrationOptions | undefined) { export function createGetHeadings(
stringifiedAst: string,
userConfig: AstroMarkdocConfig,
options: MarkdocIntegrationOptions | undefined
) {
return function getHeadings() { return function getHeadings() {
/* Yes, we are transforming twice (once from `getHeadings()` and again from <Content /> in case of variables). /* Yes, we are transforming twice (once from `getHeadings()` and again from <Content /> in case of variables).
TODO: propose new `render()` API to allow Markdoc variable passing to `render()` itself, TODO: propose new `render()` API to allow Markdoc variable passing to `render()` itself,
@ -200,6 +210,6 @@ export function createContentComponent(
// statically define a partial MarkdocConfig which registers the required "html-tag" Markdoc tag when the "allowHTML" feature is enabled // statically define a partial MarkdocConfig which registers the required "html-tag" Markdoc tag when the "allowHTML" feature is enabled
const HTML_CONFIG: AstroMarkdocConfig = { const HTML_CONFIG: AstroMarkdocConfig = {
tags: { tags: {
"html-tag": htmlTag, 'html-tag': htmlTag,
}, },
}; };

View file

@ -5,16 +5,14 @@ import type { MarkdocIntegrationOptions } from './options.js';
type TokenizerOptions = ConstructorParameters<typeof Tokenizer>[0]; type TokenizerOptions = ConstructorParameters<typeof Tokenizer>[0];
export function getMarkdocTokenizer(options: MarkdocIntegrationOptions | undefined): Tokenizer { export function getMarkdocTokenizer(options: MarkdocIntegrationOptions | undefined): Tokenizer {
const key = cacheKey(options); const key = cacheKey(options);
if (!_cachedMarkdocTokenizers[key]) { if (!_cachedMarkdocTokenizers[key]) {
const tokenizerOptions: TokenizerOptions = { const tokenizerOptions: TokenizerOptions = {
// Strip <!-- comments --> from rendered output // Strip <!-- comments --> from rendered output
// Without this, they're rendered as strings! // Without this, they're rendered as strings!
allowComments: true, allowComments: true,
} };
if (options?.allowHTML) { if (options?.allowHTML) {
// we want to allow indentation for Markdoc tags that are interleaved inside HTML block elements // we want to allow indentation for Markdoc tags that are interleaved inside HTML block elements
@ -27,7 +25,7 @@ export function getMarkdocTokenizer(options: MarkdocIntegrationOptions | undefin
} }
return _cachedMarkdocTokenizers[key]; return _cachedMarkdocTokenizers[key];
}; }
// create this on-demand when needed since it relies on the runtime MarkdocIntegrationOptions and may change during // create this on-demand when needed since it relies on the runtime MarkdocIntegrationOptions and may change during
// the life of module in certain scenarios (unit tests, etc.) // the life of module in certain scenarios (unit tests, etc.)

View file

@ -9,7 +9,6 @@ async function getFixture(name) {
} }
describe('Markdoc - render html', () => { describe('Markdoc - render html', () => {
let fixture; let fixture;
before(async () => { before(async () => {
@ -17,7 +16,6 @@ describe('Markdoc - render html', () => {
}); });
describe('dev', () => { describe('dev', () => {
let devServer; let devServer;
before(async () => { before(async () => {
@ -55,16 +53,13 @@ describe('Markdoc - render html', () => {
renderRandomlyCasedHTMLAttributesChecks(html); renderRandomlyCasedHTMLAttributesChecks(html);
}); });
}); });
describe('build', () => { describe('build', () => {
before(async () => { before(async () => {
await fixture.build(); await fixture.build();
}); });
it('renders content - simple', async () => { it('renders content - simple', async () => {
const html = await fixture.readFile('/simple/index.html'); const html = await fixture.readFile('/simple/index.html');
@ -88,7 +83,6 @@ describe('Markdoc - render html', () => {
renderRandomlyCasedHTMLAttributesChecks(html); renderRandomlyCasedHTMLAttributesChecks(html);
}); });
}); });
}); });
@ -115,7 +109,6 @@ function renderSimpleChecks(html) {
const p3 = document.querySelector('article > p:nth-of-type(3)'); const p3 = document.querySelector('article > p:nth-of-type(3)');
expect(p3.children.length).to.equal(1); expect(p3.children.length).to.equal(1);
expect(p3.textContent).to.equal('This is a span inside a paragraph!'); expect(p3.textContent).to.equal('This is a span inside a paragraph!');
} }
/** @param {string} html */ /** @param {string} html */
@ -164,7 +157,6 @@ function renderNestedHTMLChecks(html) {
expect(p5.id).to.equal('p5'); expect(p5.id).to.equal('p5');
expect(p5.textContent).to.equal('before inner after'); expect(p5.textContent).to.equal('before inner after');
expect(p5.children.length).to.equal(1); expect(p5.children.length).to.equal(1);
} }
/** /**
@ -180,17 +172,17 @@ function renderRandomlyCasedHTMLAttributesChecks(html) {
// all four <td>'s which had randomly cased variants of colspan/rowspan should all be rendered lowercased at this point // all four <td>'s which had randomly cased variants of colspan/rowspan should all be rendered lowercased at this point
expect(td1.getAttribute("colspan")).to.equal("3"); expect(td1.getAttribute('colspan')).to.equal('3');
expect(td1.getAttribute("rowspan")).to.equal("2"); expect(td1.getAttribute('rowspan')).to.equal('2');
expect(td2.getAttribute("colspan")).to.equal("3"); expect(td2.getAttribute('colspan')).to.equal('3');
expect(td2.getAttribute("rowspan")).to.equal("2"); expect(td2.getAttribute('rowspan')).to.equal('2');
expect(td3.getAttribute("colspan")).to.equal("3"); expect(td3.getAttribute('colspan')).to.equal('3');
expect(td3.getAttribute("rowspan")).to.equal("2"); expect(td3.getAttribute('rowspan')).to.equal('2');
expect(td4.getAttribute("colspan")).to.equal("3"); expect(td4.getAttribute('colspan')).to.equal('3');
expect(td4.getAttribute("rowspan")).to.equal("2"); expect(td4.getAttribute('rowspan')).to.equal('2');
} }
/** /**
@ -232,13 +224,15 @@ function renderComponentsHTMLChecks(html) {
expect(aside1Title.textContent.trim()).to.equal('Aside One'); expect(aside1Title.textContent.trim()).to.equal('Aside One');
const aside1Section = aside1.querySelector('section'); const aside1Section = aside1.querySelector('section');
const aside1SectionP1 = aside1Section.querySelector('p:nth-of-type(1)'); const aside1SectionP1 = aside1Section.querySelector('p:nth-of-type(1)');
expect(aside1SectionP1.textContent).to.equal('I\'m a Markdown paragraph inside an top-level aside tag'); expect(aside1SectionP1.textContent).to.equal(
"I'm a Markdown paragraph inside an top-level aside tag"
);
const aside1H2_1 = aside1Section.querySelector('h2:nth-of-type(1)'); const aside1H2_1 = aside1Section.querySelector('h2:nth-of-type(1)');
expect(aside1H2_1.id).to.equal('im-an-h2-via-markdown-markup'); // automatic slug expect(aside1H2_1.id).to.equal('im-an-h2-via-markdown-markup'); // automatic slug
expect(aside1H2_1.textContent).to.equal('I\'m an H2 via Markdown markup'); expect(aside1H2_1.textContent).to.equal("I'm an H2 via Markdown markup");
const aside1H2_2 = aside1Section.querySelector('h2:nth-of-type(2)'); const aside1H2_2 = aside1Section.querySelector('h2:nth-of-type(2)');
expect(aside1H2_2.id).to.equal('h-two'); expect(aside1H2_2.id).to.equal('h-two');
expect(aside1H2_2.textContent).to.equal('I\'m an H2 via HTML markup'); expect(aside1H2_2.textContent).to.equal("I'm an H2 via HTML markup");
const aside1SectionP2 = aside1Section.querySelector('p:nth-of-type(2)'); const aside1SectionP2 = aside1Section.querySelector('p:nth-of-type(2)');
expect(aside1SectionP2.textContent).to.equal('Markdown bold vs HTML bold'); expect(aside1SectionP2.textContent).to.equal('Markdown bold vs HTML bold');
expect(aside1SectionP2.children.length).to.equal(2); expect(aside1SectionP2.children.length).to.equal(2);
@ -279,7 +273,7 @@ function renderComponentsHTMLChecks(html) {
expect(section1Aside1SectionP5Span1.children.length).to.equal(1); expect(section1Aside1SectionP5Span1.children.length).to.equal(1);
const section1Aside1SectionP5Span1Span1 = section1Aside1SectionP5Span1.children[0]; const section1Aside1SectionP5Span1Span1 = section1Aside1SectionP5Span1.children[0];
expect(section1Aside1SectionP5Span1Span1.textContent).to.equal(' mark'); expect(section1Aside1SectionP5Span1Span1.textContent).to.equal(' mark');
}; }
/** @param {HTMLElement | null | undefined} el */ /** @param {HTMLElement | null | undefined} el */