chai-dom
chai-dom copied to clipboard
Support document fragments
Hi,
I'm wondering if you'd be open to supporting checks of the HTML (or text) on document fragments?
I'm currently using this awkward hack:
const container = (string) => {
const dummyContainer = document.createElement('div');
dummyContainer.append(string);
return dummyContainer;
};
expect(container(frag)).to.have.html(...)
Thanks!
Seems easy enough to do your own fragment chai assertion chain. Why would 70-90% of chai-dom users care about this use case?
In every library, some people won't need everything. Some already might not need text() but need html(), or vice versa. And your library is named "chai-dom" so I thought it aimed itself as being generically useful for testing the DOM. I don't think a fragment is that obscure of a use case; it's not like asking to test attribute nodes or something. But hey, it's obviously your call...
FWIW, I've adapted your own html method, and this seems to work all right:
chai.use(function (_chai, utils) {
const {flag} = utils;
const container = (contents) => {
const dummyContainer = document.createElement('div');
dummyContainer.append(contents);
return dummyContainer;
};
_chai.Assertion.addMethod('fragmentHtml', function (html) {
const frag = flag(this, 'object'),
actual = container(flag(this, 'object')).innerHTML;
if (flag(this, 'contains')) {
this.assert(
actual.includes(html),
'expected #{act} to contain HTML #{exp}',
'expected #{act} not to contain HTML #{exp}',
html,
actual
);
} else {
this.assert(
actual === html,
'expected ' + String(frag) +
' to have HTML #{exp}, but the HTML was #{act}',
'expected ' + String(frag) + ' not to have HTML #{exp}',
html,
actual
);
}
});
});
So to understand your use case, you have a string of HTML, and you want to check that it's parsed correctly?
I have DocumentFragment nodes (as created by document.createDocumentFragment()) and want to check their contents, but there is no innerHTML for fragments, making it harder to check their contents.
I have an i18n (internationalization) library, intl-dom which returns a string by default if there is no injecting of DOM elements/nodes, while returning a fragment otherwise.
As an example, the i18n function my app produces can accept DOM objects for safe injection into locale strings (without allowing the locale to include arbitrary HTML) like this:
const result = _('translationWithLineBreak', {
lb: $('<br>')[0]
});
with the locale file:
{
"translationWithLineBreak": "here is a translation with a{lb}line break"
}
And the resulting DOM fragment (represented here as a string):
here is a translation with a<br/>line break
I don't need or want to return this encapsulated in a <div>, as the user might want to directly inject the resulting fragment into some other element (and I don't want to trouble my users to need to add innerHTML to get at the inner contents in addition to creating an unnecessary container element for them like div).
Sometimes, however, a project may use our i18n API but not have any DOM to inject. In such cases, we just return a Javascript string rather than a DOM fragment (unless the user opts to always get a fragment).
We return different types by default because:
- The new HTML methods,
appendandprepend(which are simplifications ofappendChildandinsertBefore) as well asbeforeandafternow accept strings (which are converted by these methods to text nodes before appending)--not only Nodes. This promotes a polymorphism between fragments and strings in the context of being able to add either or both to the DOM in the same method call (e.g., as part of a template).
E.g., in my templating tool, jamilih, one can embed fragments or strings:
jml('section', [
'some text: ',
_('translationWithLineBreak', {
lb: $('<br>')[0]
}),
['div', [
'and some text in an element
]]
])
...giving this DOM:
<section>some text: here is a translation with a<br/>line break<div>and some text in an element</div></section>
- Strings can be easier to manipulate and introspect (when one knows one hasn't injected any DOM), but fragments are necessary when the string contains DOM elements.
So with the above, whether a string or fragment is returned, one can always do:
desiredParentContainer.append(result);
TL;DR: Even if your project doesn't allow checking of strings in the same manner as fragments, it would be nice to explicitly be able to check document fragment contents out of the box since fragments have long been a part of the DOM, and as per the above, they continue to have their uses.
What might be nice is to have html() handle DocumentFragments so it stays clean and readable, and very little change to the existing assertion.
That'd work!