Commits (25)
......@@ -13,3 +13,4 @@
# nunjucks templates
/docs/dgeni/inline-validate/templates
/docs/dgeni/schema/templates
# html-validate changelog
# [2.9.0](https://gitlab.com/html-validate/html-validate/compare/v2.8.2...v2.9.0) (2020-01-17)
### Features
- **jest:** add `toHTMLValidate()` ([44388ea](https://gitlab.com/html-validate/html-validate/commit/44388ea0f759a33831967859386299d95b528c63))
- **rules:** check references from `aria-controls` ([9e9805d](https://gitlab.com/html-validate/html-validate/commit/9e9805dc0e89e92411f7845a4fedc7ade0ca8cdd))
## [2.8.2](https://gitlab.com/html-validate/html-validate/compare/v2.8.1...v2.8.2) (2020-01-09)
### Bug Fixes
......
......@@ -9,7 +9,7 @@ HTML-validate was created by David Sveningsson in early 2016 with a few goals in
- Enterprise and privacy friendly: no data should leave the machine.
- Pluggable and extendable: must be easy to extend with own domain-specific
functionallity and rules.
functionality and rules.
- Strict and non-forgiving: should never try to autocorrect or guess anything.
- First-class support for views, components and templates, including when using
javascript frameworks.
......
.alert.alert-info {
ul,
p {
margin-top: 0.5rem;
}
}
......@@ -7,11 +7,21 @@ $icon-font-path: "fonts/";
@import "anchorlink";
@import "frontpage";
@import "alert";
main {
padding-bottom: 3rem;
}
header {
.nav > li > a {
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
padding-left: 10px;
padding-right: 10px;
}
}
}
footer {
border-top: 1px solid $navbar-default-border;
padding: 3rem 0;
......
......@@ -85,7 +85,7 @@ about to be closed.
}
```
Emitted when an elemnet is fully constructed (including its children). `target`
Emitted when an element is fully constructed (including its children). `target`
will be the the element.
## `attr`
......@@ -130,5 +130,5 @@ Emitted when inter-element, leading and trailing whitespace is parsed.
```
Emitted when a conditional comment `<![conditional]>` is parsed. The parser
ignores and condition and run all possbile branches but raises the event for any
ignores and condition and run all possible branches but raises the event for any
rules that wishes to do anything with it.
......@@ -145,4 +145,4 @@ both the key and value. If the attribute is processed with scripting
### `processElement`
Called after element is fully created but before children are parsed. Can be
used to manipluate elements (e.g. add dynamic text from frameworks).
used to manipulate elements (e.g. add dynamic text from frameworks).
......@@ -19,7 +19,7 @@ if (!report.valid) {
}
```
`validateFile` is a highlevel API which automatically locates configuration
`validateFile` is a high-level API which automatically locates configuration
files, load plugins, runs any transformations etc and is very similar to using
the CLI tool (in fact, the CLI tool uses this very API).
......@@ -143,7 +143,7 @@ htmlvalidate.flushConfigCache();
htmlvalidate.flushConfigCache("myfile.html");
```
## Unittesting
## Unit testing
If using jest to write tests there is a couple of helpers to assist writing
tests:
......
......@@ -118,7 +118,7 @@ e.g. rules that requires initialization.
If needed the callback may setup event listeners for [parser
events](/dev/events.html) (same as rules).
The callback may not manpiulate the source object.
The callback may not manipulate the source object.
## Configuration presets
......@@ -163,7 +163,7 @@ module.exports = {
};
```
This makes the rules accessable as usual when configuring in
This makes the rules accessible as usual when configuring in
`.htmlvalidate.json`:
```js
......
......@@ -30,6 +30,10 @@ module.exports = new Package("html-validate-docs", [
readFilesProcessor.fileReaders.push(changelogFileReader);
})
.config(function(getLinkInfo) {
getLinkInfo.relativeLinks = true;
})
.config(function(log, readFilesProcessor, writeFilesProcessor, copySchema) {
log.level = "info";
......@@ -48,7 +52,7 @@ module.exports = new Package("html-validate-docs", [
},
];
copySchema.outputFolder = "public/schemas";
copySchema.outputFolder = "schemas";
copySchema.files = ["src/schema/elements.json", "src/schema/config.json"];
writeFilesProcessor.outputFolder = "public";
......@@ -135,4 +139,11 @@ module.exports = new Package("html-validate-docs", [
.config(function(checkAnchorLinksProcessor) {
checkAnchorLinksProcessor.ignoredLinks.push(/^\/$/);
checkAnchorLinksProcessor.ignoredLinks.push(/^\/changelog$/);
checkAnchorLinksProcessor.checkDoc = doc => {
return (
doc.path &&
doc.outputPath &&
[".html", ".json"].includes(path.extname(doc.outputPath))
);
};
});
const path = require("canonical-path");
const packagePath = __dirname;
const Package = require("dgeni").Package;
module.exports = new Package("schema", [])
.processor(require("./processors/copy-schema-processor"))
.factory(require("./services/copy-schema"));
.factory(require("./services/copy-schema"))
.config(function(computeIdsProcessor, computePathsProcessor, templateFinder) {
templateFinder.templateFolders.push(path.resolve(packagePath, "templates"));
computeIdsProcessor.idTemplates.push({
docTypes: ["schema"],
getAliases: function(doc) {
return [doc.id, doc.name];
},
});
computePathsProcessor.pathTemplates.push({
docTypes: ["schema"],
outputPathTemplate: "${path}",
});
});
const path = require("path");
const fs = require("fs");
const mkdirp = require("mkdirp");
module.exports = function copySchemaProcessor(copySchema, readFilesProcessor) {
return {
......@@ -8,15 +7,20 @@ module.exports = function copySchemaProcessor(copySchema, readFilesProcessor) {
$process,
};
function $process() {
function $process(docs) {
const root = readFilesProcessor.basePath;
const outputFolder = path.join(root, copySchema.outputFolder);
mkdirp.sync(outputFolder);
const outputFolder = copySchema.outputFolder;
for (const src of copySchema.files) {
const name = path.basename(src);
fs.copyFileSync(path.join(root, src), path.join(outputFolder, name));
const { base, name } = path.parse(src);
docs.push({
id: `schema:${name}`,
name: `schemas/${name}`,
docType: "schema",
fileContents: fs.readFileSync(path.join(root, src), "utf-8"),
path: path.join(outputFolder, base),
template: "schema.json",
});
}
}
};
......@@ -31,23 +31,38 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">HTML-validate</a>
<a class="navbar-brand" href="/">HTML-validate <small class="visible-xs-inline visible-sm-inline">v{{ pkg.version }}</small></a>
</div>
<div class="collapse navbar-collapse" id="navbar">
<ul class="nav navbar-nav">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">User guide <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/usage">Gettings started</a></li>
<li><a href="/usage">Getting started</a></li>
<li><a href="/usage/cli.html">Using CLI</a></li>
<li><a href="/usage/elements.html">Elements</a></li>
<li><a href="/usage/transformers.html">Transfomers</a></li>
<li role="separator" class="divider"></li>
<li><a href="/usage/vscode.html">VS Code</a></li>
<li role="separator" class="divider"></li>
<li><a href="/frameworks/angularjs.html">AngularJS</a></li>
<li><a href="/usage/grunt.html">Grunt</a></li>
<li><a href="/frameworks/jest.html">Jest</a></li>
<li><a href="/usage/protractor.html">Protractor</a></li>
<li><a href="/frameworks/vue.html">Vue.js</a></li>
</ul>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Elements <span class="caret"></span></a>
<ul class="dropdown-menu">
<li>{@link guide/metadata/simple-component A simple component}</li>
<li>{@link guide/metadata/restrict-content Restricting element content}</li>
<li>{@link guide/metadata/restrict-attributes Restricting element attributes}</li>
<li>{@link guide/metadata/inheritance Inheritance}</li>
<li>{@link guide/metadata/best-practice Best practice}</li>
<li>{@link guide/metadata/writing-tests Writing tests}</li>
</ul>
</li>
<li><a href="/rules">Rules</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Developers guide <span class="caret"></span></a>
......@@ -65,7 +80,7 @@
<li><a href="/changelog">Changelog</a></li>
<li><a href="/about">About</a></li>
</ul>
<p class="navbar-text navbar-right">{{ pkg.name }}-{{ pkg.version }}</p>
<p class="navbar-text navbar-right hidden-xs hidden-sm">{{ pkg.name }}-{{ pkg.version }}</p>
</div>
</div>
</nav>
......
---
docType: content
title: AngularJS
---
# Usage with AngularJS
npm install html-validate-angular
[html-validate-angular](https://www.npmjs.com/package/html-validate-angular) is needed to transform `.html` and `.js` files using AngularJS.
Configure with:
```json
{
"elements": ["html5"],
"transform": {
"^.*\\.js$": "html-validate-angular/js",
"^.*\\.html$": "html-validate-angular/html"
}
}
```
---
docType: content
title: Usage with Jest
---
# Usage with Jest
`html-validate` comes with Jest support built-in.
In you test import `matchers`:
```js
import "html-validate/build/matchers";
```
This makes all the custom matchers available.
## API
### `toHTMLValidate(config?: ConfigData, filename?: string)`
Validates a string of HTML and passes the assertion if the markup is valid.
```js
expect("<p></p>").toHTMLValidate();
expect("<p></i>").not.toHTMLValidate();
```
You can also pass jsdom elements:
```js
const elem = document.createElement("div");
expect(elem).toHTMLValidate();
```
If needed a custom configuration can be passed:
```js
expect("<p></i>").toHTMLValidate({
rules: {
"close-order": "off",
},
});
```
By default configuration is also read from `.htmlvalidate.json` files where the test-case filename is used to match.
If you need to override this (perhaps because the test-case isn't in the same folder) you can pass in a custom filename as the third argument:
```js
expect("<p></i>").toHTMLValidate(null, "path/to/my-file.html");
```
Additionally, the `root` configuration property can be used to skip loading from `.htmlvalidate.json` but remember to actually include the rules you need:
```js
expect("<p></i>").toHTMLValidate({
extends: ["html-validate:recommended"],
root: true,
});
```
### `toBeValid()`
Assert that a HTML-Validate report is valid.
```js
const htmlvalidate = new HtmlValidate();
const report = htmlvalidate.validateString("<p></p>");
expect(report).toBeValid();
```
### `toBeInvalid()`
Assert that a HTML-Validate report is invalid.
Inverse of `toBeValid()`.
```js
const htmlvalidate = new HtmlValidate();
const report = htmlvalidate.validateString("<p></i>");
expect(report).toBeInvalid();
```
### `toHaveError(ruleId: string, message: string, context?: any)`
Assert that a specific error is present in an HTML-Validate report.
```js
const htmlvalidate = new HtmlValidate();
const report = htmlvalidate.validateString("<p></i>");
expect(report).toHaveError(
"close-order",
"Mismatched close-tag, expected '</p>' but found '</i>'"
);
```
### `toHaveErrors(errors: Array<[string, string] | object>)`
Similar to `toHaveError` but but asserts multiple errors.
The passed list must have the same length as the report.
Each error must either be `[ruleId, message]` or an object passed to `expect.objectContaining`.
```js
const htmlvalidate = new HtmlValidate();
const report = htmlvalidate.validateString("<p></i>");
expect(report).toHaveErrors([
["close-order", "Mismatched close-tag, expected '</p>' but found '</i>'"],
]);
```
or with object syntax:
```js
expect(report).toHaveErrors([
{
ruleId: "close-order",
message: "Mismatched close-tag, expected '</p>' but found '</i>'",
},
]);
```
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs/guide/metadata/inheritance.md inline validation: inheritance 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 4,
"context": undefined,
"line": 5,
"message": "Element <div> is not permitted as content in <my-component>",
"offset": 76,
"ruleId": "element-permitted-content",
"severity": 2,
"size": 3,
},
],
"source": "<my-component>
<span>lorem ipsum</span>
</my-component>
<my-component>
<div>lorem ipsum</div>
</my-component>",
"warningCount": 0,
},
]
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs/guide/metadata/restrict-attributes.md inline validation: boolean 1`] = `
Array [
Object {
"errorCount": 2,
"filePath": "inline",
"messages": Array [
Object {
"column": 15,
"context": undefined,
"line": 2,
"message": "Attribute \\"quacks\\" should omit value",
"offset": 54,
"ruleId": "attribute-boolean-style",
"severity": 2,
"size": 6,
},
Object {
"column": 23,
"context": Object {
"allowed": Array [],
"attribute": "quacks",
"element": "my-component",
"value": "duck",
},
"line": 2,
"message": "Attribute \\"quacks\\" has invalid value \\"duck\\"",
"offset": 62,
"ruleId": "attribute-allowed-values",
"severity": 2,
"size": 4,
},
],
"source": "<my-component quacks>...</my-component>
<my-component quacks=\\"duck\\">...</my-component>",
"warningCount": 0,
},
]
`;
exports[`docs/guide/metadata/restrict-attributes.md inline validation: deprecated 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 15,
"context": undefined,
"line": 1,
"message": "Attribute \\"duck\\" is deprecated on <my-component> element",
"offset": 14,
"ruleId": "no-deprecated-attr",
"severity": 2,
"size": 4,
},
],
"source": "<my-component duck=\\"dewey\\">...</my-component>
<my-component>...</my-component>",
"warningCount": 0,
},
]
`;
exports[`docs/guide/metadata/restrict-attributes.md inline validation: enum 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 21,
"context": Object {
"allowed": Array [
"huey",
"dewey",
"louie",
],
"attribute": "duck",
"element": "my-component",
"value": "flintheart",
},
"line": 2,
"message": "Attribute \\"duck\\" has invalid value \\"flintheart\\"",
"offset": 66,
"ruleId": "attribute-allowed-values",
"severity": 2,
"size": 10,
},
],
"source": "<my-component duck=\\"dewey\\">...</my-component>
<my-component duck=\\"flintheart\\">...</my-component>",
"warningCount": 0,
},
]
`;
exports[`docs/guide/metadata/restrict-attributes.md inline validation: regexp 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 22,
"context": Object {
"allowed": Array [
/\\\\d\\+/,
],
"attribute": "ducks",
"element": "my-component",
"value": "huey",
},
"line": 2,
"message": "Attribute \\"ducks\\" has invalid value \\"huey\\"",
"offset": 64,
"ruleId": "attribute-allowed-values",
"severity": 2,
"size": 4,
},
],
"source": "<my-component ducks=\\"3\\">...</my-component>
<my-component ducks=\\"huey\\">...</my-component>",
"warningCount": 0,
},
]
`;
exports[`docs/guide/metadata/restrict-attributes.md inline validation: required 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 2,
"context": Object {
"attribute": "duck",
"element": "my-component",
},
"line": 2,
"message": "<my-component> is missing required \\"duck\\" attribute",
"offset": 47,
"ruleId": "element-required-attributes",
"severity": 2,
"size": 12,
},
],
"source": "<my-component duck=\\"dewey\\">...</my-component>
<my-component>...</my-component>",
"warningCount": 0,
},
]
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs/guide/metadata/restrict-content.md inline validation: descendants 1`] = `
Array [
Object {
"errorCount": 2,
"filePath": "inline",
"messages": Array [
Object {
"column": 6,
"context": undefined,
"line": 4,
"message": "Element <footer> is not permitted as descendant of <my-component>",
"offset": 63,
"ruleId": "element-permitted-content",
"severity": 2,
"size": 6,
},
Object {
"column": 6,
"context": undefined,
"line": 7,
"message": "Element <my-component> is not permitted as descendant of <my-component>",
"offset": 137,
"ruleId": "element-permitted-content",
"severity": 2,
"size": 12,
},
],
"source": "<my-component>
<!-- the div itself is allowed -->
<div>
<footer>
sectioning element can no longer be used
</footer>
<my-component>
nor can the component be nested
</my-component>
</div>
<span>also allowed</span>
<h1>not allowed</h1>
</my-component>",
"warningCount": 0,
},
]
`;
exports[`docs/guide/metadata/restrict-content.md inline validation: exclude 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 4,
"context": undefined,
"line": 4,
"message": "Element <h1> is not permitted as content in <my-component>",
"offset": 67,
"ruleId": "element-permitted-content",
"severity": 2,
"size": 2,
},
],
"source": "<my-component>
<div>allowed</div>
<span>also allowed</span>
<h1>not allowed</h1>
</my-component>",
"warningCount": 0,
},
]
`;
exports[`docs/guide/metadata/restrict-content.md inline validation: tags 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 4,
"context": undefined,
"line": 2,
"message": "Element <button> is not permitted as content in <my-component>",
"offset": 18,
"ruleId": "element-permitted-content",
"severity": 2,
"size": 6,
},
],
"source": "<my-component>
<button type=\\"button\\">click me!</button>
</my-component>",
"warningCount": 0,
},
]
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs/guide/metadata/simple-component.md inline validation: basic-metadata 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 4,
"context": undefined,
"line": 2,
"message": "Element <my-component> is not permitted as content in <div>",
"offset": 9,
"ruleId": "element-permitted-content",
"severity": 2,
"size": 12,
},
],
"source": "<div>
<my-component>lorem ipsum</my-component>
</div>",
"warningCount": 0,
},
]
`;
exports[`docs/guide/metadata/simple-component.md inline validation: flow--metadata-2 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 4,
"context": undefined,
"line": 2,
"message": "Element <my-component> is not permitted as content in <span>",
"offset": 10,
"ruleId": "element-permitted-content",
"severity": 2,
"size": 12,
},
],
"source": "<span>
<my-component>lorem ipsum</my-component>
</span>",
"warningCount": 0,
},
]
`;
exports[`docs/guide/metadata/simple-component.md inline validation: flow-metadata-1 1`] = `Array []`;
exports[`docs/guide/metadata/simple-component.md inline validation: no-metadata-1 1`] = `Array []`;
exports[`docs/guide/metadata/simple-component.md inline validation: no-metadata-2 1`] = `Array []`;
exports[`docs/guide/metadata/simple-component.md inline validation: no-metadata-3 1`] = `Array []`;
exports[`docs/guide/metadata/simple-component.md inline validation: phrasing-metadata 1`] = `Array []`;