GitLab Commit is coming up on August 3-4. Learn how to innovate together using GitLab, the DevOps platform. Register for free: gitlabcommitvirtual2021.com

Commit 7b3c30e6 authored by David Sveningsson's avatar David Sveningsson
Browse files

feat(jest): `toHTMLValidate()` supports passing expected errors

parent 4be48fa1
Pipeline #182891985 passed with stages
in 9 minutes and 40 seconds
......@@ -17,7 +17,7 @@ This makes all the custom matchers available.
## API
### `toHTMLValidate(config?: ConfigData, filename?: string)`
### `toHTMLValidate([error?: Message], [config?: ConfigData], filename?: string)`
Validates a string of HTML and passes the assertion if the markup is valid.
......@@ -55,7 +55,7 @@ This means you can apply transformations using patterns such as `^.*\\.(spec|tes
If you need to override the filename (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");
expect("<p></i>").toHTMLValidate("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:
......@@ -67,6 +67,23 @@ expect("<p></i>").toHTMLValidate({
});
```
To test for presence of an error always use the negative form `expect(..).not.toHTMLValidate()`.
If you pass in an expected error as the first argument it will be matched using `objectContaining` when an error is present.
```js
/* OK - error matches */
expect("<p></i>").not.toHTMLValidate({
ruleId: "close-order",
message: expect.stringContaining("Mismatched close-tag"),
});
/* Fail - wrong error */
expect("<p></i>").not.toHTMLValidate({
ruleId: "void-style",
message: expect.stringContaining("Expected omitted end tag"),
});
```
### `toBeValid()`
Assert that a HTML-Validate report is valid.
......
......@@ -266,4 +266,53 @@ describe("toHTMLValidate()", () => {
const button = doc.createElement("button");
expect(button).not.toHTMLValidate();
});
it("should pass if markup has correct error", () => {
expect.assertions(1);
expect("<u></i>").not.toHTMLValidate({
ruleId: "close-order",
message: expect.stringContaining("Mismatched close-tag"),
});
});
it("should fail if markup has wrong error", async () => {
expect.assertions(3);
let error: Error;
try {
await expect("<u></i>").not.toHTMLValidate({
ruleId: "wrong-error",
message: expect.stringContaining("Some other error"),
});
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(stripAnsi(error.message)).toMatchInlineSnapshot(`
"expect(received).not.toHTMLValidate(expected) // expected error
Expected error to be present:
{\\"message\\": StringContaining \\"Some other error\\", \\"ruleId\\": \\"wrong-error\\"}
- Expected error
+ Actual error
- ArrayContaining [
- ObjectContaining {
- \\"message\\": StringContaining \\"Some other error\\",
- \\"ruleId\\": \\"wrong-error\\",
+ Array [
+ Object {
+ \\"column\\": 5,
+ \\"context\\": undefined,
+ \\"line\\": 1,
+ \\"message\\": \\"Mismatched close-tag, expected '</u>' but found '</i>'.\\",
+ \\"offset\\": 4,
+ \\"ruleId\\": \\"close-order\\",
+ \\"selector\\": null,
+ \\"severity\\": 2,
+ \\"size\\": 2,
},
]"
`);
});
});
......@@ -34,7 +34,18 @@ declare global {
* @param filename - Optional filename used when matching transformer and
* loading configuration.
*/
toHTMLValidate(config?: ConfigData, filename?: string): R;
toHTMLValidate(): R;
toHTMLValidate(filename: string): R;
toHTMLValidate(config: ConfigData): R;
toHTMLValidate(config: ConfigData, filename: string): R;
toHTMLValidate(error: Partial<Message>): R;
toHTMLValidate(error: Partial<Message>, filename: string): R;
toHTMLValidate(error: Partial<Message>, config: ConfigData): R;
toHTMLValidate(
error: Partial<Message>,
config: ConfigData,
filename: string
): R;
}
}
}
......@@ -146,10 +157,62 @@ function toHaveErrors(
return { pass, message: resultMessage };
}
function isMessage(arg: any): arg is Partial<Message> {
return (
arg &&
(arg.ruleId ||
arg.severity ||
arg.message ||
arg.offset ||
arg.line ||
arg.column ||
arg.size ||
arg.selector ||
arg.context)
);
}
function isConfig(arg: any): arg is ConfigData {
return (
arg &&
(arg.root ||
arg.extends ||
arg.elements ||
arg.plugin ||
arg.transform ||
arg.rules)
);
}
function isString(arg: any): arg is string {
return typeof arg === "string";
}
function toHTMLValidate(
this: jest.MatcherUtils,
// @ts-ignore DOM library not available
actual: string | HTMLElement,
arg0?: Partial<Message> | ConfigData | string,
arg1?: ConfigData | string,
arg2?: string
): jest.CustomMatcherResult {
// @ts-ignore DOM library not available
if (actual instanceof HTMLElement) {
actual = actual.outerHTML;
}
const message = isMessage(arg0) ? arg0 : undefined;
const config = isConfig(arg0) ? arg0 : isConfig(arg1) ? arg1 : undefined;
const filename = isString(arg0) ? arg0 : isString(arg1) ? arg1 : arg2;
return toHTMLValidateImpl.call(this, actual, message, config, filename);
}
function toHTMLValidateImpl(
this: jest.MatcherUtils,
// @ts-ignore DOM library not available
actual: string | HTMLElement,
expectedError?: Partial<Message>,
userConfig?: ConfigData,
filename?: string
): jest.CustomMatcherResult {
......@@ -174,6 +237,33 @@ function toHTMLValidate(
if (pass) {
return { pass, message: () => "HTML is valid when an error was expected" };
} else {
if (expectedError) {
const matcher = expect.arrayContaining([
expect.objectContaining(expectedError),
]);
const errorPass = this.equals(report.results[0].messages, matcher);
const diffString = diff(matcher, report.results[0].messages, {
expand: this.expand,
aAnnotation: "Expected error",
bAnnotation: "Actual error",
});
const hint = this.utils.matcherHint(
".not.toHTMLValidate",
undefined,
undefined,
{ comment: "expected error" }
);
const expectedErrorMessage = (): string =>
[
hint,
"",
"Expected error to be present:",
this.utils.printExpected(expectedError),
/* istanbul ignore next */ diffString ? `\n${diffString}` : "",
].join("\n");
return { pass: !errorPass, message: expectedErrorMessage };
}
const errors = report.results[0].messages.map(
(message) => ` ${message.message} [${message.ruleId}]`
);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment