Skip to content

Commit

Permalink
Adds HtmlElement pretty-format plugin. (jestjs#3230)
Browse files Browse the repository at this point in the history
* Adds HtmlElement pretty-format plugin.

* Adds missing copyright and 'use strict'

* Fixes variable naming / formatting.
Reworks short circuiting logic for `isHTMLElement` function.

* Improves performance for isHtmlElement.

* Updating snapshots

* Revert "Updating snapshots"

This reverts commit 55f797f.

* Fixes node 4 syntax

* - Switches to tagName rather than constructor.

* Adds HTMLElement Plugin usage.

* Removes `HTMLElement` fallback in favor of tagName
  • Loading branch information
mute authored and cpojer committed Apr 17, 2017
1 parent 46e8fd0 commit 7f68960
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 48 deletions.
2 changes: 2 additions & 0 deletions packages/jest-diff/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {DiffOptions} from './diffStrings';
const ReactElementPlugin = require('pretty-format/build/plugins/ReactElement');
const ReactTestComponentPlugin = require('pretty-format/build/plugins/ReactTestComponent');
const AsymmetricMatcherPlugin = require('pretty-format/build/plugins/AsymmetricMatcher');
const HTMLElementPlugin = require('pretty-format/build/plugins/HTMLElement');
const ImmutablePlugins = require('pretty-format/build/plugins/ImmutablePlugins');

const chalk = require('chalk');
Expand All @@ -31,6 +32,7 @@ const PLUGINS = [
ReactTestComponentPlugin,
ReactElementPlugin,
AsymmetricMatcherPlugin,
HTMLElementPlugin,
].concat(ImmutablePlugins);
const FORMAT_OPTIONS = {
plugins: PLUGINS,
Expand Down
9 changes: 6 additions & 3 deletions packages/jest-matcher-utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ const chalk = require('chalk');
const prettyFormat = require('pretty-format');
const AsymmetricMatcherPlugin = require('pretty-format/build/plugins/AsymmetricMatcher');
const ReactElementPlugin = require('pretty-format/build/plugins/ReactElement');
const HTMLElementPlugin = require('pretty-format/build/plugins/HTMLElement');
const ImmutablePlugins = require('pretty-format/build/plugins/ImmutablePlugins');

const PLUGINS = [AsymmetricMatcherPlugin, ReactElementPlugin].concat(
ImmutablePlugins,
);
const PLUGINS = [
AsymmetricMatcherPlugin,
ReactElementPlugin,
HTMLElementPlugin,
].concat(ImmutablePlugins);

export type ValueType =
| 'array'
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-snapshot/src/__tests__/plugins-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const testPath = names => {
it('gets plugins', () => {
const {getSerializers} = require('../plugins');
const plugins = getSerializers();
expect(plugins.length).toBe(8);
expect(plugins.length).toBe(9);
});

it('adds plugins from an empty array', () => testPath([]));
Expand Down
9 changes: 6 additions & 3 deletions packages/jest-snapshot/src/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@

const ReactElementPlugin = require('pretty-format/build/plugins/ReactElement');
const ReactTestComponentPlugin = require('pretty-format/build/plugins/ReactTestComponent');
const HTMLElementPlugin = require('pretty-format/build/plugins/HTMLElement');
const ImmutablePlugins = require('pretty-format/build/plugins/ImmutablePlugins');

let PLUGINS = [ReactElementPlugin, ReactTestComponentPlugin].concat(
ImmutablePlugins,
);
let PLUGINS = [
ReactElementPlugin,
ReactTestComponentPlugin,
HTMLElementPlugin,
].concat(ImmutablePlugins);

// Prepend to list so the last added is the first tested.
exports.addSerializer = (plugin: any) => {
Expand Down
94 changes: 94 additions & 0 deletions packages/pretty-format/src/__tests__/HTMLElementPlugin-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @jest-environment jsdom
*/
/* eslint-disable max-len */
/* eslint-env browser*/

'use strict';

const HTMLElementPlugin = require('../plugins/HTMLElement');
const toPrettyPrintTo = require('./expect-util').getPrettyPrint([
HTMLElementPlugin,
]);

expect.extend({toPrettyPrintTo});

describe('HTMLElement Plugin', () => {
it('supports a single HTML element', () => {
expect(document.createElement('div')).toPrettyPrintTo('<div />');
});

it('supports an HTML element with a class property', () => {
const parent = document.createElement('div');
parent.className = 'classy';

expect(parent).toPrettyPrintTo('<div\n class="classy"\n/>');
});

it('supports an HTML element with a title property', () => {
const parent = document.createElement('div');
parent.title = 'title text';

expect(parent).toPrettyPrintTo('<div\n title="title text"\n/>');
});

it('supports an HTML element with a single attribute', () => {
const parent = document.createElement('div');
parent.setAttribute('class', 'classy');

expect(parent).toPrettyPrintTo('<div\n class="classy"\n/>');
});

it('supports an HTML element with multiple attributes', () => {
const parent = document.createElement('div');
parent.setAttribute('id', 123);
parent.setAttribute('class', 'classy');

expect(parent).toPrettyPrintTo('<div\n id="123"\n class="classy"\n/>', {
});
});

it('supports an element with text content', () => {
const parent = document.createElement('div');
parent.innerHTML = 'texty texty';

expect(parent).toPrettyPrintTo('<div>\n texty texty\n</div>');
});

it('supports nested elements', () => {
const parent = document.createElement('div');
const child = document.createElement('span');
parent.appendChild(child);
expect(parent).toPrettyPrintTo('<div>\n <span />\n</div>');
});

it('supports nested elements with attributes', () => {
const parent = document.createElement('div');
const child = document.createElement('span');
parent.appendChild(child);

child.setAttribute('id', 123);
child.setAttribute('class', 'classy');

expect(parent).toPrettyPrintTo(
'<div>\n <span\n id="123"\n class="classy"\n />\n</div>',
);
});

it('supports nested elements with text content', () => {
const parent = document.createElement('div');
const child = document.createElement('span');
parent.appendChild(child);
child.textContent = 'texty texty';

expect(parent).toPrettyPrintTo(
'<div>\n <span>\n texty texty\n </span>\n</div>',
);
});
});
45 changes: 4 additions & 41 deletions packages/pretty-format/src/__tests__/ImmutablePlugins-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,52 +10,15 @@
'use strict';

const React = require('react');
const diff = require('jest-diff');
const prettyFormat = require('../');
const Immutable = require('immutable');
const ReactElementPlugin = require('../plugins/ReactElement');
const ReactTestComponentPlugin = require('../plugins/ReactTestComponent');
const ImmutablePlugins = require('../plugins/ImmutablePlugins');
const toPrettyPrintTo = require('./expect-util').getPrettyPrint(
[ReactElementPlugin, ReactTestComponentPlugin].concat(ImmutablePlugins),
);

expect.extend({
toPrettyPrintTo(received, expected, opts) {
const prettyPrintImmutable = prettyFormat(
received,
Object.assign(
{
plugins: [ReactElementPlugin, ReactTestComponentPlugin].concat(
ImmutablePlugins,
),
},
opts,
),
);
const pass = prettyPrintImmutable === expected;

const message = pass
? () =>
this.utils.matcherHint('.not.toBe') +
'\n\n' +
`Expected value to not be:\n` +
` ${this.utils.printExpected(expected)}\n` +
`Received:\n` +
` ${this.utils.printReceived(prettyPrintImmutable)}`
: () => {
const diffString = diff(expected, prettyPrintImmutable, {
expand: this.expand,
});
return this.utils.matcherHint('.toBe') +
'\n\n' +
`Expected value to be:\n` +
` ${this.utils.printExpected(expected)}\n` +
`Received:\n` +
` ${this.utils.printReceived(prettyPrintImmutable)}` +
(diffString ? `\n\nDifference:\n\n${diffString}` : '');
};

return {actual: prettyPrintImmutable, message, pass};
},
});
expect.extend({toPrettyPrintTo});

describe('Immutable.OrderedSet plugin', () => {
it('supports an empty set', () => {
Expand Down
52 changes: 52 additions & 0 deletions packages/pretty-format/src/__tests__/expect-util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

'use strict';

const diff = require('jest-diff');
const prettyFormat = require('../');

module.exports = {
getPrettyPrint: plugins =>
(received, expected, opts) => {
const prettyFormatted = prettyFormat(
received,
Object.assign(
{
plugins,
},
opts,
),
);
const pass = prettyFormatted === expected;

const message = pass
? () =>
this.utils.matcherHint('.not.toBe') +
'\n\n' +
`Expected value to not be:\n` +
` ${this.utils.printExpected(expected)}\n` +
`Received:\n` +
` ${this.utils.printReceived(prettyFormatted)}`
: () => {
const diffString = diff(expected, prettyFormatted, {
expand: this.expand,
});
return this.utils.matcherHint('.toBe') +
'\n\n' +
`Expected value to be:\n` +
` ${this.utils.printExpected(expected)}\n` +
`Received:\n` +
` ${this.utils.printReceived(prettyFormatted)}` +
(diffString ? `\n\nDifference:\n\n${diffString}` : '');
};

return {actual: prettyFormatted, message, pass};
},
};
102 changes: 102 additions & 0 deletions packages/pretty-format/src/plugins/HTMLElement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/

'use strict';

import type {Colors, Indent, Options, Print, Plugin} from '../types.js';

const escapeHTML = require('./lib/escapeHTML');
const HTML_ELEMENT_REGEXP = /(HTML\w*?Element)/;
const test = isHTMLElement;

function isHTMLElement(value: any) {
return value !== undefined &&
value !== null &&
value.nodeType === 1 &&
value.constructor !== undefined &&
value.constructor.name !== undefined &&
HTML_ELEMENT_REGEXP.test(value.constructor.name);
}

function printChildren(flatChildren, print, indent, colors, opts) {
return flatChildren
.map(node => {
if (typeof node === 'object') {
return print(node, print, indent, colors, opts);
} else if (typeof node === 'string') {
return colors.content.open + escapeHTML(node) + colors.content.close;
} else {
return print(node);
}
})
.join(opts.edgeSpacing);
}

function printAttributes(attributes, print, indent, colors, opts) {
return attributes
.sort()
.map(attribute => {
return opts.spacing +
indent(colors.prop.open + attribute.name + colors.prop.close + '=') +
colors.value.open +
`"${attribute.value}"` +
colors.value.close;
})
.join('');
}

const print = (
element: any,
print: Print,
indent: Indent,
opts: Options,
colors: Colors,
) => {
let result = colors.tag.open + '<';
const elementName = element.tagName.toLowerCase();
result += elementName + colors.tag.close;

const hasAttributes = element.attributes && element.attributes.length;
if (hasAttributes) {
const attributes = Array.prototype.slice.call(element.attributes);
result += printAttributes(attributes, print, indent, colors, opts);
}

const flatChildren = Array.prototype.slice.call(element.children);
if (!flatChildren.length && element.textContent) {
flatChildren.push(element.textContent);
}

const closeInNewLine = hasAttributes && !opts.min;
if (flatChildren.length) {
const children = printChildren(flatChildren, print, indent, colors, opts);
result += colors.tag.open +
(closeInNewLine ? '\n' : '') +
'>' +
colors.tag.close +
opts.edgeSpacing +
indent(children) +
opts.edgeSpacing +
colors.tag.open +
'</' +
elementName +
'>' +
colors.tag.close;
} else {
result += colors.tag.open +
(closeInNewLine ? '\n' : ' ') +
'/>' +
colors.tag.close;
}

return result;
};

module.exports = ({print, test}: Plugin);

0 comments on commit 7f68960

Please sign in to comment.