Fix incorrect URL rendering in documentation texts
This commit addresses incorrect URL rendering within documentation text
by improving auto-linkified URL labels, handling `+` symbols as spaces,
enhancing readability of encoded path segments and manually updating
some of the documetation.
Key improvements:
- Parse `+` as whitespace in URLs for accurate script labeling.
- Interpret multiple whitespaces as single for robustness.
- Decode path segments for clearer links.
- Refactor markdown renderer.
- Expand unit tests for comprehensive coverage.
Documentation has been updated to fix inline URL references and improve
linkification across several scripts, ensuring more readable and
user-friendly content.
Affected files and documentation sections have been adjusted
accordingly, including script and category names for consistency and
clarity.
Some of the script/category documentation changing fixing URL rendering
includes:
- 'Disable sending information to Customer Experience Improvement
Program':
- Fix reference URLs being inlined.
- 'Disable "Secure boot" button in "Windows Security"':
- Fix rendering issue due to auto-linkification of `markdown-it`.
- 'Clear Internet Explorer DOMStore':
- Fix rendering issue due to auto-linkification of `markdown-it`.
- 'Disable "Windows Defender Firewall" service':
- Fix rendering issue due to auto-linkification of `markdown-it`.
- Convert YAML comments to markdown comments visible by users.
- Add breaking behavior to script name, changing script name to.
- 'Disable Microsoft Defender Firewall services and drivers':
- Remove information about breaking behavior to avoid duplication and
be consistent with the documentation of the rest of the collections.
- Use consistent styling for warning texts starting with `Caution:`.
- Rename 'Remove extensions' category to 'Remove extension apps' for
consistency with names of its sibling categories.
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { parseApplication } from '@/application/Parser/ApplicationParser';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { createRenderer } from '@/presentation/components/Scripts/View/Tree/NodeContent/Documentation/MarkdownRenderer';
|
||||
import { createMarkdownRenderer } from '@/presentation/components/Scripts/View/Tree/NodeContent/Documentation/MarkdownRenderer';
|
||||
|
||||
describe('MarkdownRenderer', () => {
|
||||
describe('can render all docs', () => {
|
||||
// arrange
|
||||
const renderer = createRenderer();
|
||||
const renderer = createMarkdownRenderer();
|
||||
for (const node of collectAllDocumentableNodes()) {
|
||||
it(`${node.nodeLabel}`, () => {
|
||||
// act
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createRenderer } from '@/presentation/components/Scripts/View/Tree/NodeContent/Documentation/MarkdownRenderer';
|
||||
import { createMarkdownRenderer } from '@/presentation/components/Scripts/View/Tree/NodeContent/Documentation/MarkdownRenderer';
|
||||
|
||||
describe('MarkdownRenderer', () => {
|
||||
describe('createRenderer', () => {
|
||||
it('can create', () => {
|
||||
describe('createMarkdownRenderer', () => {
|
||||
it('creates renderer instance', () => {
|
||||
// arrange & act
|
||||
const renderer = createRenderer();
|
||||
const renderer = createMarkdownRenderer();
|
||||
// assert
|
||||
expect(renderer !== undefined);
|
||||
});
|
||||
describe('sets expected anchor attributes', () => {
|
||||
describe('sets default anchor attributes', () => {
|
||||
const attributes: ReadonlyArray<{
|
||||
readonly name: string,
|
||||
readonly attributeName: string,
|
||||
readonly expectedValue: string,
|
||||
readonly invalidMarkdown: string
|
||||
}> = [
|
||||
{
|
||||
name: 'target',
|
||||
attributeName: 'target',
|
||||
expectedValue: '_blank',
|
||||
invalidMarkdown: '<a href="https://undergroundwires.dev" target="_self">example</a>',
|
||||
},
|
||||
{
|
||||
name: 'rel',
|
||||
attributeName: 'rel',
|
||||
expectedValue: 'noopener noreferrer',
|
||||
invalidMarkdown: '<a href="https://undergroundwires.dev" rel="nooverride">example</a>',
|
||||
},
|
||||
];
|
||||
for (const attribute of attributes) {
|
||||
const { name, expectedValue, invalidMarkdown } = attribute;
|
||||
const { attributeName, expectedValue, invalidMarkdown } = attribute;
|
||||
|
||||
it(`adds "${name}" attribute to anchor elements`, () => {
|
||||
it(`adds "${attributeName}" attribute to anchors`, () => {
|
||||
// arrange
|
||||
const renderer = createRenderer();
|
||||
const renderer = createMarkdownRenderer();
|
||||
const markdown = '[undergroundwires.dev](https://undergroundwires.dev)';
|
||||
|
||||
// act
|
||||
@@ -40,12 +40,12 @@ describe('MarkdownRenderer', () => {
|
||||
// assert
|
||||
const html = parseHtml(htmlString);
|
||||
const aElement = html.getElementsByTagName('a')[0];
|
||||
expect(aElement.getAttribute(name)).to.equal(expectedValue);
|
||||
expect(aElement.getAttribute(attributeName)).to.equal(expectedValue);
|
||||
});
|
||||
|
||||
it(`overrides existing "${name}" attribute`, () => {
|
||||
it(`overrides existing "${attributeName}" attribute`, () => {
|
||||
// arrange
|
||||
const renderer = createRenderer();
|
||||
const renderer = createMarkdownRenderer();
|
||||
|
||||
// act
|
||||
const htmlString = renderer.render(invalidMarkdown);
|
||||
@@ -53,13 +53,13 @@ describe('MarkdownRenderer', () => {
|
||||
// assert
|
||||
const html = parseHtml(htmlString);
|
||||
const aElement = html.getElementsByTagName('a')[0];
|
||||
expect(aElement.getAttribute(name)).to.equal(expectedValue);
|
||||
expect(aElement.getAttribute(attributeName)).to.equal(expectedValue);
|
||||
});
|
||||
}
|
||||
});
|
||||
it('does not convert single linebreak to <br>', () => {
|
||||
it('does not convert single line breaks to <br> elements', () => {
|
||||
// arrange
|
||||
const renderer = createRenderer();
|
||||
const renderer = createMarkdownRenderer();
|
||||
const markdown = 'Text with\nSingle\nLinebreaks';
|
||||
// act
|
||||
const htmlString = renderer.render(markdown);
|
||||
@@ -68,9 +68,9 @@ describe('MarkdownRenderer', () => {
|
||||
const totalBrElements = html.getElementsByTagName('br').length;
|
||||
expect(totalBrElements).to.equal(0);
|
||||
});
|
||||
it('creates links for plain URL', () => {
|
||||
it('converts plain URLs into hyperlinks', () => {
|
||||
// arrange
|
||||
const renderer = createRenderer();
|
||||
const renderer = createMarkdownRenderer();
|
||||
const expectedUrl = 'https://privacy.sexy/';
|
||||
const markdown = `Visit ${expectedUrl} now!`;
|
||||
// act
|
||||
@@ -81,18 +81,93 @@ describe('MarkdownRenderer', () => {
|
||||
const href = aElement.getAttribute('href');
|
||||
expect(href).to.equal(expectedUrl);
|
||||
});
|
||||
it('it generates beautiful labels for auto-linkified URL', () => {
|
||||
// arrange
|
||||
const renderer = createRenderer();
|
||||
const url = 'https://privacy.sexy';
|
||||
const expectedText = 'privacy.sexy';
|
||||
const markdown = `Visit ${url} now!`;
|
||||
// act
|
||||
const htmlString = renderer.render(markdown);
|
||||
// assert
|
||||
const html = parseHtml(htmlString);
|
||||
const aElement = html.getElementsByTagName('a')[0];
|
||||
expect(aElement.text).to.equal(expectedText);
|
||||
describe('generates readable labels for automatically linked URLs', () => {
|
||||
const testScenarios: ReadonlyArray<{
|
||||
readonly description: string;
|
||||
readonly urlText: string;
|
||||
readonly expectedLabel: string;
|
||||
}> = [
|
||||
{
|
||||
description: 'displays only domain name for URLs without path or query',
|
||||
urlText: 'https://privacy.sexy',
|
||||
expectedLabel: 'privacy.sexy',
|
||||
},
|
||||
{
|
||||
description: 'includes subdomains in labels',
|
||||
urlText: 'https://subdomain.privacy.sexy',
|
||||
expectedLabel: 'subdomain.privacy.sexy',
|
||||
},
|
||||
{
|
||||
description: 'includes longer URL segment in label',
|
||||
urlText: 'https://privacy.sexy/LongerExpectedPart/ShorterPart',
|
||||
expectedLabel: 'privacy.sexy - LongerExpectedPart',
|
||||
},
|
||||
{
|
||||
description: 'capitalizes first letter of URL path in label',
|
||||
urlText: 'https://privacy.sexy/capitalized',
|
||||
expectedLabel: 'privacy.sexy - Capitalized',
|
||||
},
|
||||
...['-', '%20', '+'].map((urlSegmentDelimiter) => ({
|
||||
description: `parses \`${urlSegmentDelimiter}\` as a delimiter in URL`,
|
||||
urlText: `https://privacy.sexy/privacy${urlSegmentDelimiter}scripts`,
|
||||
expectedLabel: 'privacy.sexy - Privacy Scripts',
|
||||
})),
|
||||
{
|
||||
description: 'treats multiple spaces as single in URLs',
|
||||
urlText: 'https://privacy.sexy/word--with-multiple---spaces',
|
||||
expectedLabel: 'privacy.sexy - Word With Multiple Spaces',
|
||||
},
|
||||
{
|
||||
description: 'handles query parameters in URLs correctly',
|
||||
urlText: 'https://privacy.sexy/?query=parameter',
|
||||
expectedLabel: 'privacy.sexy - Parameter',
|
||||
},
|
||||
{
|
||||
description: 'truncates long hostnames to a readable length',
|
||||
urlText: 'https://averylongwebsitedomainnamethatexceedsthetypicalthreshold.com',
|
||||
expectedLabel: '…exceedsthetypicalthreshold.com',
|
||||
},
|
||||
{
|
||||
description: 'ignores purely numeric path segments in labels',
|
||||
urlText: 'https://privacy.sexy/20230302/expected',
|
||||
expectedLabel: 'privacy.sexy - Expected',
|
||||
},
|
||||
{
|
||||
description: 'ignores non-standard ports in labels',
|
||||
urlText: 'https://privacy.sexy:8080/configure',
|
||||
expectedLabel: 'privacy.sexy - Configure',
|
||||
},
|
||||
{
|
||||
description: 'removes common file extensions from labels',
|
||||
urlText: 'https://privacy.sexy/image.png',
|
||||
expectedLabel: 'privacy.sexy - Image',
|
||||
},
|
||||
{
|
||||
description: 'handles complex queries in URLs by selecting the most descriptive part',
|
||||
urlText: 'https://privacy.sexy/?product=123&name=PrivacyTool',
|
||||
expectedLabel: 'privacy.sexy - PrivacyTool',
|
||||
},
|
||||
{
|
||||
description: 'decodes special encoded characters in URLs for labels',
|
||||
urlText: 'https://privacy.sexy/privacy%2Fscripts',
|
||||
expectedLabel: 'privacy.sexy - Privacy/scripts',
|
||||
},
|
||||
];
|
||||
testScenarios.forEach(({
|
||||
description, urlText, expectedLabel,
|
||||
}) => {
|
||||
it(description, () => {
|
||||
// arrange
|
||||
const renderer = createMarkdownRenderer();
|
||||
const markdown = `Visit ${urlText} now!`;
|
||||
// act
|
||||
const htmlString = renderer.render(markdown);
|
||||
// assert
|
||||
const html = parseHtml(htmlString);
|
||||
const aElement = html.getElementsByTagName('a')[0];
|
||||
expect(aElement.text).to.equal(expectedLabel);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user