npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@open-wc/semantic-dom-diff

v0.20.1

Published

To compare dom and shadow dom trees. Part of open-wc recommendations

Downloads

254,831

Readme

Testing >> Semantic Dom Diff ||40

semantic-dom-diff allows diffing chunks of dom or HTML for semantic equality:

  • whitespace and newlines are normalized
  • tags and attributes are printed on individual lines
  • comments are removed
  • style, script and SVG contents are removed
  • tags, attributes or element's light dom can be ignored through configuration

Manual Setup

npm i -D @open-wc/semantic-dom-diff

Chai Plugin

While semantic-dom-diff can be used standalone (see below), it most commonly used as a Chai plugin.

If you are using @open-wc/testing this is already done for you.

import 'chai/chai.js';
import { chaiDomDiff } from '@open-wc/semantic-dom-diff';

window.chai.use(chaiDomDiff);

Assertion Styles

The Chai plugin supports both the BDD (expect) and TDD (assert) APIs.

expect(el).dom.to.equal('<div></div>');
assert.dom.equal(el, '<div></div>');

expect(el).dom.to.equal('<div foo="bar"></div>', { ignoreAttributes: ['foo'] });
assert.dom.equal(el, '<div foo="bar"></div>', { ignoreAttributes: ['foo'] });

expect(el).lightDom.to.equal('<div></div>');
assert.lightDom.equal(el, '<div></div>');

expect(el).shadowDom.to.equal('<div></div>');
assert.shadowDom.equal(el, '<div></div>');

Setting up your dom for diffing

You can set up our chai plugin to diff different types of DOM:

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = '<p> shadow content </p>';
  }
}

customElements.define('my-element', MyElement);

it('my test', async () => {
  const el = await fixture(`
    <my-element>
      <div> light dom content </div>
    </my-element>
  `);

  expect(el).dom; // dom is <my-element><div>light dom content</div></my-element>
  expect(el).lightDom; // dom is <div>light dom content</div>
  expect(el).shadowDom; // dom is <p>shadow content</p>
});

Manual diffing

You can use the chai plugin to manually diff chunks of dom. The dom is diffed semantically: whitespace, newlines, etc. are normalized.

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = '<p> shadow content </p>';
  }
}

customElements.define('my-element', MyElement);

it('my test', async () => {
  const el = await fixture(`
    <my-element>
      <div> light dom content </div>
    </my-element>
  `);

  expect(el).dom.to.equal('<my-element><div>light dom content</div></my-element>');
  expect(el).lightDom.to.equal('<div>light dom content</div>');
  expect(el).shadowDom.to.equal('<p>shadow content</p>');
});

Snapshot testing

semantic-dom-diff supports managing snapshots of your components. Snapshot testing is supported in @web/test-runner with mocha, or karma with karma-snapshot and karma-mocha-snapshot.

When using Web Test Runner, snapshot tests are async and the assertion must be awaited.

Setting up a snapshot

Snapshots are created by setting up your component in a specific state, and then calling .to.equalSnapshot(). You can use .dom, .lightDom or .shadowDom to set up the dom of your element:

import { fixture } from '@open-wc/testing';

describe('my-message', () => {
  it('renders message foo correctly', async () => {
    const element = await fixture(`
      <my-message message="Foo"></my-element>
    `);

    await expect(element).shadowDom.to.equalSnapshot();
  });

  it('renders message bar correctly', async () => {
    const element = await fixture(`
      <my-message message="Bar"></my-element>
    `);

    await expect(element).shadowDom.to.equalSnapshot();
  });

  it('renders a capitalized message correctly', async () => {
    const element = await fixture(`
      <my-message message="Bar" capitalized></my-element>
    `);

    await expect(element).shadowDom.to.equalSnapshot();
  });

  it('allows rendering a message from a slot', async () => {
    const element = await fixture(`
      <my-message capitalized>Bar</my-element>
    `);

    await expect(element).lightDom.to.equalSnapshot();
  });
});

Updating a snapshot

When your tests run for the first time the snapshot files are generated. On subsequent test runs your element is compared with the stored snapshots. If the element and the snapshots differ the test fails.

If the difference was an intended change, you can update the snapshots by passing the --update-snapshots flag.

Ignoring tags and attributes

When working with libraries or custom elements there might be parts of the rendered dom which is random or otherwise outside of your control. In those cases, you might want to ignore certain attributes or tags entirely. This is possible by passing an options object.

it('renders correctly', async () => {
  const el = await fixture(`
    <div my-random-attribute="${Math.random()}">
      Hey
    </div>
  `);

  await expect(el).dom.to.equal('<div>Hey</div>', {
    ignoreAttributes: ['my-random-attribute'],
  });

  await expect(el).dom.to.equalSnapshot({
    ignoreAttributes: ['my-random-attribute'],
  });
});

Ignoring an attribute only for certain tags

Randomly generated ids are often used, throwing off your diffs. You can ignore attributes on specific tags:

it('renders correctly', async () => {
  const el = await fixture(`
    <input id="customInput${Math.random()}">
  `);

  // ignore id attributes on input elements
  await expect(el).dom.to.equal('<div>Hey</div>', {
    ignoreAttributes: [{ tags: ['input'], attributes: ['id'] }],
  });

  await expect(el).dom.to.equalSnapshot({
    ignoreAttributes: [{ tags: ['input'], attributes: ['id'] }],
  });
});

Ignoring tags

You can tell the diff to ignore certain tags entirely:

it('renders correctly', async () => {
  const el = await fixture(`
    <div>
      <my-custom-element></my-custom-element>
      foo
    </div>
  `);

  // ignore id attributes on input elements
  await expect(el).dom.to.equal('<div>Hey</div>', {
    ignoreTags: ['my-custom-element'],
  });

  await expect(el).dom.to.equalSnapshot({
    ignoreTags: ['my-custom-element'],
  });
});

Ignoring children

When working with web components you may find that they sometimes render to their light dom, for example, to meet some accessibility requirements. We don't want to ignore the tag completely, as we would then not be able to test if we did render the tag.

We can ignore just it's light dom:

it('renders correctly', async () => {
  const el = await fixture(`
    <div>
      <my-custom-input id="myInput">
        <input id="inputRenderedInLightDom">
        Some text rendered in the light dom
      </my-custom-input>
      foo
    </div>
  `);

  // ignore id attributes on input elements
  await expect(el).dom.to.equal(
    `
    <div>
      <my-custom-input id="myInput"></my-custom-input>
      foo
    </div>
  `,
    { ignoreChildren: ['my-custom-input'] },
  );

  await expect(el).dom.to.equalSnapshot({
    ignoreChildren: ['my-custom-input'],
  });
});