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:
undergroundwires
2023-11-27 05:17:58 +01:00
parent bcad357017
commit d328f08952
5 changed files with 274 additions and 167 deletions

View File

@@ -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

View File

@@ -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);
});
});
});
});
});