Render bracket references as superscript text
This commit improves markdown rendering to convert reference labels (e.g., `[1]`) to superscripts, improving document readability without cluttering the text. This improvement applies documentation of all scripts and categories. Changes: - Implement superscript conversion for reference labels within markdown content, ensuring a cleaner presentation of textual references. - Enable HTML content within markdown, necessary for inserting `<sup>` elements due to limitations in `markdown-it`, see markdown-it/markdown-it#999 for details. - Refactor markdown rendering process for improved testability and adherence to the Single Responsibility Principle. - Create `_typography.scss` with font size definitions, facilitating better control over text presentation. - Adjust external URL indicator icon sizing for consistency, aligning images with the top of the text to maintain a uniform appearence. - Use normal font-size explicitly for documentation text to ensure consistency. - Remove text size specification in `markdown-styles` mixin, using `1em` for spacing to simplify styling. - Rename font sizing variables for clarity, distinguishing between absolute and relative units. - Change `font-size-relative-smaller` to be `80%`, browser default for `font-size: smaller;` CSS style and use it with `<sup>` elements. - Improve the logic for converting plain URLs to hyperlinks, removing trailing whitespace for cleaner link generation. - Fix plain URL to hyperlink (autolinking) logic removing trailing whitespace from the original markdown content. This was revealed by tests after separating its logic. - Increase test coverage with more tests. - Add types for `markdown-it` through `@types/markdown-it` package for better editor support and maintainability. - Simplify implementation of adding custom anchor attributes in `markdown-it` using latest documentation.
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { MarkdownRendererStub } from '@tests/unit/shared/Stubs/MarkdownRendererStub';
|
||||
import type { MarkdownRenderer } from '@/presentation/components/Scripts/View/Tree/NodeContent/Markdown/MarkdownRenderer';
|
||||
import { CompositeMarkdownRenderer } from '@/presentation/components/Scripts/View/Tree/NodeContent/Markdown/CompositeMarkdownRenderer';
|
||||
|
||||
describe('CompositeMarkdownRenderer', () => {
|
||||
describe('constructor', () => {
|
||||
it('throws error without renderers', () => {
|
||||
// arrange
|
||||
const expectedError = 'missing renderers';
|
||||
const renderers = [];
|
||||
const context = new MarkdownRendererTestBuilder()
|
||||
.withMarkdownRenderers(renderers);
|
||||
// act
|
||||
const act = () => context.render();
|
||||
// assert
|
||||
expect(act).to.throw(expectedError);
|
||||
});
|
||||
});
|
||||
describe('render', () => {
|
||||
describe('applies modifications', () => {
|
||||
describe('with single renderer', () => {
|
||||
it('calls the renderer', () => {
|
||||
// arrange
|
||||
const expectedInput = 'initial content';
|
||||
const renderer = new MarkdownRendererStub();
|
||||
const context = new MarkdownRendererTestBuilder()
|
||||
.withMarkdownInput(expectedInput)
|
||||
.withMarkdownRenderers([renderer]);
|
||||
// act
|
||||
context.render();
|
||||
// assert
|
||||
renderer.assertRenderWasCalledOnceWith(expectedInput);
|
||||
});
|
||||
it('matches single renderer output', () => {
|
||||
// arrange
|
||||
const expectedOutput = 'expected output';
|
||||
const renderer = new MarkdownRendererStub()
|
||||
.withRenderOutput(expectedOutput);
|
||||
const context = new MarkdownRendererTestBuilder()
|
||||
.withMarkdownRenderers([renderer]);
|
||||
// act
|
||||
const actualOutput = context.render();
|
||||
// assert
|
||||
expect(actualOutput).to.equal(expectedOutput);
|
||||
});
|
||||
});
|
||||
describe('with multiple renderers', () => {
|
||||
it('calls all renderers in the provided order', () => {
|
||||
// arrange
|
||||
const initialInput = 'initial content';
|
||||
const firstRendererOutput = 'initial content';
|
||||
const firstRenderer = new MarkdownRendererStub()
|
||||
.withRenderOutput(firstRendererOutput);
|
||||
const secondRenderer = new MarkdownRendererStub();
|
||||
const context = new MarkdownRendererTestBuilder()
|
||||
.withMarkdownInput(initialInput)
|
||||
.withMarkdownRenderers([firstRenderer, secondRenderer]);
|
||||
// act
|
||||
context.render();
|
||||
// assert
|
||||
firstRenderer.assertRenderWasCalledOnceWith(initialInput);
|
||||
secondRenderer.assertRenderWasCalledOnceWith(firstRendererOutput);
|
||||
});
|
||||
it('matches final output from sequence', () => {
|
||||
// arrange
|
||||
const expectedOutput = 'final content';
|
||||
const firstRenderer = new MarkdownRendererStub();
|
||||
const secondRenderer = new MarkdownRendererStub()
|
||||
.withRenderOutput(expectedOutput);
|
||||
const context = new MarkdownRendererTestBuilder()
|
||||
.withMarkdownRenderers([firstRenderer, secondRenderer]);
|
||||
// act
|
||||
const actualOutput = context.render();
|
||||
// assert
|
||||
expect(actualOutput).to.equal(expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class MarkdownRendererTestBuilder {
|
||||
private markdownInput = `[${MarkdownRendererTestBuilder.name}] Markdown text`;
|
||||
|
||||
private markdownRenderers: readonly MarkdownRenderer[] = [
|
||||
new MarkdownRendererStub(),
|
||||
];
|
||||
|
||||
public withMarkdownInput(markdownInput: string): this {
|
||||
this.markdownInput = markdownInput;
|
||||
return this;
|
||||
}
|
||||
|
||||
public withMarkdownRenderers(markdownRenderers: readonly MarkdownRenderer[]): this {
|
||||
this.markdownRenderers = markdownRenderers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public render(): ReturnType<MarkdownRenderer['render']> {
|
||||
const renderer = new CompositeMarkdownRenderer(this.markdownRenderers);
|
||||
return renderer.render(this.markdownInput);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { InlineReferenceLabelsToSuperscriptConverter } from '@/presentation/components/Scripts/View/Tree/NodeContent/Markdown/Renderers/InlineReferenceLabelsToSuperscriptConverter';
|
||||
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
||||
import { renderMarkdownUsingRenderer } from './MarkdownRenderingTester';
|
||||
|
||||
describe('InlineReferenceLabelsToSuperscriptConverter', () => {
|
||||
describe('modify', () => {
|
||||
describe('retains original content where no conversion is required', () => {
|
||||
const testScenarios: ReadonlyArray<{
|
||||
readonly description: string;
|
||||
readonly markdownContent: string;
|
||||
}> = [
|
||||
{
|
||||
description: 'text without references',
|
||||
markdownContent: 'No references here to convert.',
|
||||
},
|
||||
{
|
||||
description: 'numeric references outside brackets',
|
||||
markdownContent: [
|
||||
'This is a test 1.',
|
||||
'Please refer to note 2.',
|
||||
'1: Reference I',
|
||||
'1: Reference II',
|
||||
].join('\n'),
|
||||
},
|
||||
{
|
||||
description: 'references without definitions',
|
||||
markdownContent: [
|
||||
'This is a test [1].',
|
||||
'Please refer to note [2].',
|
||||
].join('\n'),
|
||||
},
|
||||
];
|
||||
testScenarios.forEach(({ description, markdownContent }) => {
|
||||
it(description, () => {
|
||||
// arrange
|
||||
const expectedOutput = markdownContent; // No change expected
|
||||
|
||||
// act
|
||||
const convertedContent = renderMarkdownUsingRenderer(
|
||||
InlineReferenceLabelsToSuperscriptConverter,
|
||||
markdownContent,
|
||||
);
|
||||
|
||||
// assert
|
||||
expect(convertedContent).to.equal(expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('converts references in square brackets to superscript', () => {
|
||||
const testScenarios: ReadonlyArray<{
|
||||
readonly description: string;
|
||||
readonly markdownContent: string;
|
||||
readonly expectedOutput: string;
|
||||
}> = [
|
||||
{
|
||||
description: 'converts a single numeric reference',
|
||||
markdownContent: [
|
||||
'See reference [1].',
|
||||
createMarkdownLinkReferenceDefinition('1'),
|
||||
].join('\n'),
|
||||
expectedOutput: [
|
||||
'See reference <sup>[1]</sup>.',
|
||||
createMarkdownLinkReferenceDefinition('1'),
|
||||
].join('\n'),
|
||||
},
|
||||
{
|
||||
description: 'converts a single non-numeric reference',
|
||||
markdownContent: [
|
||||
'For more information, check [Reference A].',
|
||||
createMarkdownLinkReferenceDefinition('Reference A'),
|
||||
].join('\n'),
|
||||
expectedOutput: [
|
||||
'For more information, check <sup>[Reference A]</sup>.',
|
||||
createMarkdownLinkReferenceDefinition('Reference A'),
|
||||
].join('\n'),
|
||||
},
|
||||
{
|
||||
description: 'converts multiple numeric references on the same line',
|
||||
markdownContent: [
|
||||
'Refer to [1], [2], and [3] for more details.',
|
||||
createMarkdownLinkReferenceDefinition('1'), createMarkdownLinkReferenceDefinition('2'), createMarkdownLinkReferenceDefinition('3'),
|
||||
].join('\n'),
|
||||
expectedOutput: [
|
||||
'Refer to <sup>[1]</sup>, <sup>[2]</sup>, and <sup>[3]</sup> for more details.',
|
||||
createMarkdownLinkReferenceDefinition('1'), createMarkdownLinkReferenceDefinition('2'), createMarkdownLinkReferenceDefinition('3'),
|
||||
].join('\n'),
|
||||
},
|
||||
{
|
||||
description: 'converts multiple numeric references on different lines',
|
||||
markdownContent: [
|
||||
'Details can be found in [5].', 'Additional data in [6].',
|
||||
createMarkdownLinkReferenceDefinition('5'), createMarkdownLinkReferenceDefinition('6'),
|
||||
].join('\n'),
|
||||
expectedOutput: [
|
||||
'Details can be found in <sup>[5]</sup>.', 'Additional data in <sup>[6]</sup>.',
|
||||
createMarkdownLinkReferenceDefinition('5'), createMarkdownLinkReferenceDefinition('6'),
|
||||
].join('\n'),
|
||||
},
|
||||
{
|
||||
description: 'handles adjacent references without spaces',
|
||||
markdownContent: [
|
||||
'start[first][2][3]end',
|
||||
createMarkdownLinkReferenceDefinition('first'), createMarkdownLinkReferenceDefinition('2'), createMarkdownLinkReferenceDefinition('3'),
|
||||
].join('\n'),
|
||||
expectedOutput: [
|
||||
'start<sup>[first]</sup><sup>[2]</sup><sup>[3]</sup>end',
|
||||
createMarkdownLinkReferenceDefinition('first'), createMarkdownLinkReferenceDefinition('2'), createMarkdownLinkReferenceDefinition('3'),
|
||||
].join('\n'),
|
||||
},
|
||||
{
|
||||
description: 'handles references with special characters',
|
||||
markdownContent: [
|
||||
'[reference-name!]',
|
||||
createMarkdownLinkReferenceDefinition('reference-name!'),
|
||||
].join('\n'),
|
||||
expectedOutput: [
|
||||
'<sup>[reference-name!]</sup>',
|
||||
createMarkdownLinkReferenceDefinition('reference-name!'),
|
||||
].join('\n'),
|
||||
},
|
||||
{
|
||||
description: 'handles colon after reference without mistaking for definition',
|
||||
markdownContent: [
|
||||
'It said [1]: "No I\'m not AI!"',
|
||||
createMarkdownLinkReferenceDefinition('1'),
|
||||
].join('\n'),
|
||||
expectedOutput: [
|
||||
'It said <sup>[1]</sup>: "No I\'m not AI!"',
|
||||
createMarkdownLinkReferenceDefinition('1'),
|
||||
].join('\n'),
|
||||
},
|
||||
];
|
||||
testScenarios.forEach(({ description, markdownContent, expectedOutput }) => {
|
||||
it(description, () => {
|
||||
// act
|
||||
const convertedContent = renderMarkdownUsingRenderer(
|
||||
InlineReferenceLabelsToSuperscriptConverter,
|
||||
markdownContent,
|
||||
);
|
||||
|
||||
// assert
|
||||
expect(convertedContent).to.equal(expectedOutput, formatAssertionMessage([
|
||||
`Expected output: ${expectedOutput}`,
|
||||
`Actual output: ${expectedOutput}`,
|
||||
]));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createMarkdownLinkReferenceDefinition(label: string): string {
|
||||
return `[${label}]: https://test.url`;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { MarkdownItHtmlRenderer } from '@/presentation/components/Scripts/View/Tree/NodeContent/Markdown/Renderers/MarkdownItHtmlRenderer';
|
||||
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
||||
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
|
||||
import { parseHtml } from '@tests/shared/HtmlParser';
|
||||
import { renderMarkdownUsingRenderer } from './MarkdownRenderingTester';
|
||||
|
||||
describe('MarkdownItHtmlRenderer', () => {
|
||||
describe('modify', () => {
|
||||
describe('sets default anchor attributes', () => {
|
||||
const testScenarios: ReadonlyArray<{
|
||||
readonly attributeName: string;
|
||||
readonly expectedValue: string;
|
||||
readonly markdownWithNonCompliantAnchorAttributes: string;
|
||||
}> = [
|
||||
{
|
||||
attributeName: 'target',
|
||||
expectedValue: '_blank',
|
||||
markdownWithNonCompliantAnchorAttributes: '[URL](https://undergroundwires.dev){ target="_self" }',
|
||||
},
|
||||
{
|
||||
attributeName: 'rel',
|
||||
expectedValue: 'noopener noreferrer',
|
||||
markdownWithNonCompliantAnchorAttributes: '[URL](https://undergroundwires.dev){ rel="nooverride" }',
|
||||
},
|
||||
];
|
||||
testScenarios.forEach(({
|
||||
attributeName, expectedValue, markdownWithNonCompliantAnchorAttributes,
|
||||
}) => {
|
||||
it(`adds "${attributeName}" attribute to anchors`, () => {
|
||||
// arrange
|
||||
const markdown = '[undergroundwires.dev](https://undergroundwires.dev)';
|
||||
|
||||
// act
|
||||
const renderedOutput = renderMarkdownUsingRenderer(MarkdownItHtmlRenderer, markdown);
|
||||
|
||||
// assert
|
||||
assertAnchorElementAttribute({
|
||||
renderedOutput,
|
||||
attributeName,
|
||||
expectedValue,
|
||||
markdownContent: markdown,
|
||||
});
|
||||
});
|
||||
|
||||
it(`overrides existing "${attributeName}" attribute`, () => {
|
||||
// arrange & act
|
||||
const renderedOutput = renderMarkdownUsingRenderer(
|
||||
MarkdownItHtmlRenderer,
|
||||
markdownWithNonCompliantAnchorAttributes,
|
||||
);
|
||||
|
||||
// assert
|
||||
assertAnchorElementAttribute({
|
||||
renderedOutput,
|
||||
attributeName,
|
||||
expectedValue,
|
||||
markdownContent: markdownWithNonCompliantAnchorAttributes,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it('does not convert single line breaks to <br> elements', () => {
|
||||
// arrange
|
||||
const markdown = 'Text with\nSingle\nLinebreaks';
|
||||
// act
|
||||
const renderedOutput = renderMarkdownUsingRenderer(MarkdownItHtmlRenderer, markdown);
|
||||
// assert
|
||||
const html = parseHtml(renderedOutput);
|
||||
const totalBrElements = html.getElementsByTagName('br').length;
|
||||
expect(totalBrElements).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function assertAnchorElementAttribute(context: {
|
||||
readonly renderedOutput: string;
|
||||
readonly attributeName: string;
|
||||
readonly expectedValue: string;
|
||||
readonly markdownContent: string;
|
||||
}) {
|
||||
const html = parseHtml(context.renderedOutput);
|
||||
const aElement = html.getElementsByTagName('a')[0];
|
||||
expectExists(aElement, formatAssertionMessage([
|
||||
'Missing expected `<a>` element',
|
||||
`Markdown input to render: ${context.markdownContent}`,
|
||||
`Actual render output: ${context.renderedOutput}`,
|
||||
]));
|
||||
const actualValue = aElement.getAttribute(context.attributeName);
|
||||
expect(context.expectedValue).to.equal(actualValue, formatAssertionMessage([
|
||||
`Expected attribute value: ${context.expectedValue}`,
|
||||
`Actual attribute value: ${actualValue}`,
|
||||
`Attribute name: ${context.attributeName}`,
|
||||
`Markdown input to render: ${context.markdownContent}`,
|
||||
`Actual render output:\n${context.renderedOutput}`,
|
||||
`Actual \`<a>\` element HTML: ${aElement.outerHTML}`,
|
||||
]));
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { MarkdownRenderer } from '@/presentation/components/Scripts/View/Tree/NodeContent/Markdown/MarkdownRenderer';
|
||||
|
||||
type RenderFunction = MarkdownRenderer['render'];
|
||||
|
||||
export function renderMarkdownUsingRenderer(
|
||||
MarkdownRendererClass: { new(): MarkdownRenderer ; },
|
||||
...renderArgs: Parameters<RenderFunction>
|
||||
): ReturnType<RenderFunction> {
|
||||
const rendererInstance = new MarkdownRendererClass();
|
||||
return rendererInstance.render(...renderArgs);
|
||||
}
|
||||
@@ -1,87 +1,47 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createMarkdownRenderer } from '@/presentation/components/Scripts/View/Tree/NodeContent/Markdown/MarkdownRenderer';
|
||||
import { PlainTextUrlsToHyperlinksConverter } from '@/presentation/components/Scripts/View/Tree/NodeContent/Markdown/Renderers/PlainTextUrlsToHyperlinksConverter';
|
||||
import { renderMarkdownUsingRenderer } from './MarkdownRenderingTester';
|
||||
|
||||
describe('MarkdownRenderer', () => {
|
||||
describe('createMarkdownRenderer', () => {
|
||||
it('creates renderer instance', () => {
|
||||
// arrange & act
|
||||
const renderer = createMarkdownRenderer();
|
||||
// assert
|
||||
expect(renderer !== undefined);
|
||||
});
|
||||
describe('sets default anchor attributes', () => {
|
||||
const attributes: ReadonlyArray<{
|
||||
readonly attributeName: string,
|
||||
readonly expectedValue: string,
|
||||
readonly invalidMarkdown: string
|
||||
describe('PlainTextUrlsToHyperlinksConverter', () => {
|
||||
describe('modify', () => {
|
||||
describe('retains original content where no conversion is required', () => {
|
||||
const testScenarios: ReadonlyArray<{
|
||||
readonly description: string;
|
||||
readonly markdownContent: string;
|
||||
}> = [
|
||||
{
|
||||
attributeName: 'target',
|
||||
expectedValue: '_blank',
|
||||
invalidMarkdown: '<a href="https://undergroundwires.dev" target="_self">example</a>',
|
||||
description: 'URLs within markdown link syntax',
|
||||
markdownContent: 'URL: [privacy.sexy](https://privacy.sexy).',
|
||||
},
|
||||
{
|
||||
attributeName: 'rel',
|
||||
expectedValue: 'noopener noreferrer',
|
||||
invalidMarkdown: '<a href="https://undergroundwires.dev" rel="nooverride">example</a>',
|
||||
description: 'URLs within inline code blocks',
|
||||
markdownContent: 'URL as code: `https://privacy.sexy`.',
|
||||
},
|
||||
{
|
||||
description: 'reference-style links',
|
||||
markdownContent: [
|
||||
'This content has reference-style link [1].',
|
||||
'[1]: https://privacy.sexy',
|
||||
].join('\n'),
|
||||
},
|
||||
];
|
||||
for (const attribute of attributes) {
|
||||
const { attributeName, expectedValue, invalidMarkdown } = attribute;
|
||||
|
||||
it(`adds "${attributeName}" attribute to anchors`, () => {
|
||||
testScenarios.forEach(({ description, markdownContent }) => {
|
||||
it(description, () => {
|
||||
// arrange
|
||||
const renderer = createMarkdownRenderer();
|
||||
const markdown = '[undergroundwires.dev](https://undergroundwires.dev)';
|
||||
const expectedOutput = markdownContent; // No change expected
|
||||
|
||||
// act
|
||||
const htmlString = renderer.render(markdown);
|
||||
const convertedContent = renderMarkdownUsingRenderer(
|
||||
PlainTextUrlsToHyperlinksConverter,
|
||||
markdownContent,
|
||||
);
|
||||
|
||||
// assert
|
||||
const html = parseHtml(htmlString);
|
||||
const aElement = html.getElementsByTagName('a')[0];
|
||||
expect(aElement.getAttribute(attributeName)).to.equal(expectedValue);
|
||||
expect(convertedContent).to.equal(expectedOutput);
|
||||
});
|
||||
|
||||
it(`overrides existing "${attributeName}" attribute`, () => {
|
||||
// arrange
|
||||
const renderer = createMarkdownRenderer();
|
||||
|
||||
// act
|
||||
const htmlString = renderer.render(invalidMarkdown);
|
||||
|
||||
// assert
|
||||
const html = parseHtml(htmlString);
|
||||
const aElement = html.getElementsByTagName('a')[0];
|
||||
expect(aElement.getAttribute(attributeName)).to.equal(expectedValue);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
it('does not convert single line breaks to <br> elements', () => {
|
||||
// arrange
|
||||
const renderer = createMarkdownRenderer();
|
||||
const markdown = 'Text with\nSingle\nLinebreaks';
|
||||
// act
|
||||
const htmlString = renderer.render(markdown);
|
||||
// assert
|
||||
const html = parseHtml(htmlString);
|
||||
const totalBrElements = html.getElementsByTagName('br').length;
|
||||
expect(totalBrElements).to.equal(0);
|
||||
});
|
||||
it('converts plain URLs into hyperlinks', () => {
|
||||
// arrange
|
||||
const renderer = createMarkdownRenderer();
|
||||
const expectedUrl = 'https://privacy.sexy/';
|
||||
const markdown = `Visit ${expectedUrl} now!`;
|
||||
// act
|
||||
const htmlString = renderer.render(markdown);
|
||||
// assert
|
||||
const html = parseHtml(htmlString);
|
||||
const aElement = html.getElementsByTagName('a')[0];
|
||||
const href = aElement.getAttribute('href');
|
||||
expect(href).to.equal(expectedUrl);
|
||||
});
|
||||
describe('generates readable labels for automatically linked URLs', () => {
|
||||
describe('converts plain URLs into hyperlinks', () => {
|
||||
const testScenarios: ReadonlyArray<{
|
||||
readonly description: string;
|
||||
readonly urlText: string;
|
||||
@@ -158,22 +118,17 @@ describe('MarkdownRenderer', () => {
|
||||
}) => {
|
||||
it(description, () => {
|
||||
// arrange
|
||||
const renderer = createMarkdownRenderer();
|
||||
const markdown = `Visit ${urlText} now!`;
|
||||
const expectedOutput = `Visit [${expectedLabel}](${urlText}) now!`;
|
||||
// act
|
||||
const htmlString = renderer.render(markdown);
|
||||
const actualOutput = renderMarkdownUsingRenderer(
|
||||
PlainTextUrlsToHyperlinksConverter,
|
||||
markdown,
|
||||
);
|
||||
// assert
|
||||
const html = parseHtml(htmlString);
|
||||
const aElement = html.getElementsByTagName('a')[0];
|
||||
expect(aElement.text).to.equal(expectedLabel);
|
||||
expect(actualOutput).to.equal(expectedOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function parseHtml(htmlString: string): Document {
|
||||
const parser = new window.DOMParser();
|
||||
const htmlDoc = parser.parseFromString(htmlString, 'text/html');
|
||||
return htmlDoc;
|
||||
}
|
||||
Reference in New Issue
Block a user