Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: md reporter #702

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ The command-line tool can provide test results in a few different ways using the
- `html`: output test results as an HTML page
- `json`: output test results as a JSON array
- `tsv`: output test results as tab-separated values
- `md`: output test results as markdown

You can also write and publish your own reporters. Pa11y looks for reporters in your `node_modules` folder (with a naming pattern), and the current working directory. The first reporter found will be loaded. So with this command:

Expand Down
2 changes: 1 addition & 1 deletion bin/pa11y.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ function loadReporter(name) {
let reporterMethods;

try {
if (['json', 'cli', 'csv', 'tsv', 'html'].includes(name)) {
if (['json', 'cli', 'csv', 'tsv', 'html', 'md'].includes(name)) {
reporterMethods = require(`../lib/reporters/${name}`);
} else {
reporterMethods = requireFirst([
Expand Down
55 changes: 55 additions & 0 deletions lib/reporters/md.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use strict';

const fs = require('fs');
const mustache = require('mustache');
const path = require('path');
const {promisify} = require('util');
const readFile = promisify(fs.readFile);

const report = module.exports = {};

// Pa11y version support
report.supports = '^8.0.0 || ^8.0.0-alpha || ^8.0.0-beta';

// Compile template and output formatted results
report.results = async results => {
if (results.issues.length === 0) {
return `✅ Aucune issue détectée par pa11y sur ${results.pageUrl}`;
}
const templateString = await readFile(path.resolve(`${__dirname}/report.md`), 'utf-8');
return mustache.render(templateString, {
// The current date
date: new Date(),

// Result information
issues: results.issues.map(issue => {
issue.typeLabel = upperCaseFirst(issue.type);
const wcagIssue = issue.code && issue.code.match(
/^WCAG2AA.*\.(\w\d+)\.*/
);
issue.emoji = {Error: '🔴',
Warning: '🟡'}[issue.typeLabel] || '🟢';
issue.codeUrl =
wcagIssue &&
`https://www.w3.org/TR/WCAG20-TECHS/${wcagIssue[1]}`;
return issue;
}),
pageUrl: results.pageUrl,

// Issue counts
errorCount: results.issues.filter(issue => issue.type === 'error').length,
warningCount: results.issues.filter(issue => issue.type === 'warning').length,
noticeCount: results.issues.filter(issue => issue.type === 'notice').length

});
};

// Output error messages
report.error = message => {
return message;
};

// Utility function to uppercase the first character of a string
function upperCaseFirst(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
27 changes: 27 additions & 0 deletions lib/reporters/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Accessibility Report For "{{& pageUrl}}"

> 🗓️ Generated at: {{date}}

| Type | Total |
| ----------- | ---------------- |
| 🔴 Error | {{errorCount}} |
| 🟡 Warnings | {{warningCount}} |
| 🟢 Notice | {{noticeCount}} |

## Details

{{#issues}}

### {{emoji}} {{typeLabel}} [{{code}}]({{codeUrl}})

{{message}}

```
{{{selector}}}
```

```
{{{context}}}
```

{{/issues}}
47 changes: 47 additions & 0 deletions test/integration/cli/reporter-md.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict';

const assert = require('proclaim');
const runPa11yCli = require('../helper/pa11y-cli');

describe('CLI reporter Markdown', function() {
let pa11yResponse;

describe('when the `--reporter` flag is set to "md"', function() {

before(async function() {
pa11yResponse = await runPa11yCli(`${global.mockWebsiteAddress}/errors`, {
arguments: [
'--reporter', 'md'
]
});
});

it('outputs issues in Markdown format', function() {
assert.include(pa11yResponse.stdout,
`# Accessibility Report For "${global.mockWebsiteAddress}/errors"`);
assert.match(pa11yResponse.stdout, /| 🔴 Error {4}| 1 {3}|/);
assert.match(pa11yResponse.stdout, /### 🔴 Error/);
});

});

describe('when the `reporter` config is set to "md"', function() {

before(async function() {
pa11yResponse = await runPa11yCli(`${global.mockWebsiteAddress}/errors`, {
arguments: [
'--config', './mock/config/reporter-md.json'
]
});
});

it('outputs issues in Markdown format', function() {
assert.include(pa11yResponse.stdout,
`# Accessibility Report For "${global.mockWebsiteAddress}/errors"`);
assert.match(pa11yResponse.stdout, /| 🔴 Error {4}| 1 {3}|/);
assert.match(pa11yResponse.stdout, /### 🔴 Error/);
});

});

});
3 changes: 3 additions & 0 deletions test/integration/mock/config/reporter-md.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"reporter": "md"
}
100 changes: 100 additions & 0 deletions test/unit/lib/reporters/md.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
'use strict';

const assert = require('proclaim');
const mockery = require('mockery');
const path = require('path');

describe('lib/reporters/md', function() {
let fs;
let mustache;
let reporter;

beforeEach(function() {
fs = require('../../mocks/fs.mock');
mockery.registerMock('fs', fs);
mustache = require('../../mocks/mustache.mock');
mockery.registerMock('mustache', mustache);
reporter = require('../../../../lib/reporters/md');
});

it('is an object', function() {
assert.isObject(reporter);
});

it('has a `supports` property', function() {
assert.isString(reporter.supports);
});

it('has a `results` method', function() {
assert.isFunction(reporter.results);
});

describe('.results(pa11yResults)', function() {
let mockPa11yResults;
let resolvedValue;

beforeEach(async function() {
mockPa11yResults = {
documentTitle: 'mock title',
pageUrl: 'http://mock-url/',
issues: [
{
type: 'error'
}
]
};
fs.readFile.yieldsAsync(null, 'mock template content');
mustache.render.returns('mock rendered template');
resolvedValue = await reporter.results(mockPa11yResults);
});

it('reads the report Markdown template', function() {
assert.calledOnce(fs.readFile);
assert.calledWith(fs.readFile, path.resolve(`${__dirname}/../../../../lib/reporters/report.md`), 'utf-8');
});

it('renders the template with a context object that uses the Pa11y results', function() {
assert.calledOnce(mustache.render);
assert.isString(mustache.render.firstCall.args[0]);

const renderContext = mustache.render.firstCall.args[1];
assert.instanceOf(renderContext.date, Date);
assert.strictEqual(renderContext.pageUrl, mockPa11yResults.pageUrl);
assert.strictEqual(renderContext.errorCount, 1);
assert.strictEqual(renderContext.warningCount, 0);
assert.strictEqual(renderContext.noticeCount, 0);
assert.strictEqual(renderContext.issues[0], mockPa11yResults.issues[0]);
assert.strictEqual(renderContext.issues[0].typeLabel, 'Error');
});

it('resolves with the rendered template', function() {
assert.strictEqual(resolvedValue, 'mock rendered template');
});

});

it('has an `error` method', function() {
assert.isFunction(reporter.error);
});

describe('.error(message)', function() {

it('returns the message unchanged', function() {
assert.strictEqual(reporter.error('mock message'), 'mock message');
});

});

it('does not have a `begin` method', function() {
assert.isUndefined(reporter.begin);
});

it('does not have a `debug` method', function() {
assert.isUndefined(reporter.debug);
});

it('does not have an `info` method', function() {
assert.isUndefined(reporter.info);
});

});