Code Monkey home page Code Monkey logo

bore's Introduction

This has been moved to the skatejs monorepo!

bore

Enzyme-like testing utility built for the DOM and Web Components, that works both on the server and in browsers.

npm install @skatejs/bore --save-dev

Usage

Bore makes testing the DOM simpler in the same way Enzyme makes testing React simpler. It's built with Web Components in mind and follows similar conventions to Enzyme, but the APIs won't map 1:1.

/* @jsx h */

import { mount } from '@skatejs/bore';
import { h } from '@skatejs/val';

const wrapper = mount(<div><span /></div>);

// "span"
console.log(wrapper.one('span').node.localName);

You don't have to use @skatejs/val but it makes creating DOM a lot easier than using the native imperative APIs.

Testing with Jest / JSDOM / Node

Currently JSDOM doesn't have web component support, so you're limited to testing non-web-component DOM in JSDOM and Jest's default configuration.

To test your web components in Node or Jest, you'll have to use @skatejs/ssr. More information there.

Notes about web components

Since web components are an extension of the HTML standard, Bore inherently works with it. However there are a few things that it does underneath the hood that should be noted.

  • The custom element polyfill, if detected, is supported by calling flush() after mounting the nodes so things appear synchronous.
  • Nodes are mounted to a fixture that is always kept in the DOM (even if it's removed, it will put itself back). This is so that custom elements can go through their natural lifecycle.
  • The fixture is cleaned up on every mount, so there's no need to cleanup after your last mount.
  • The attachShadow() method is overridden to always provide an open shadow root so that there is always a shadowRoot property and it can be queried against.

API

There's no distinction between shallow rendering and full rendering as there's no significant performance implications and Shadow DOM negates the need for the distinction.

mount(htmlOrNode)

The mount function takes a node, or a string - and converts it to a node - and returns a wrapper around it.

/** @jsx h */

import { mount } from '@skatejs/bore';
import { h } from '@skatejs/val';

mount(<div><span /></div>);

Wrapper API

A wrapper is returned when you call mount():

const wrapper = mount(<div><span /></div>);

The wrapper contains several methods and properties that you can use to test your DOM.

node

Returns the node the wrapper is representing.

// div
mount(<div />).node.localName;

all(query)

You can search using pretty much anything and it will return an array of wrapped nodes that matched the query.

Element constructors

You can use element constructors to search for nodes in a tree.

mount(<div><span /></div>).all(HTMLSpanElement);

Since custom elements are just extensions of HTML elements, you can do it in the same exact way:

class MyElement extends HTMLElement {};
customElements.define('my-element', MyElement);

mount(<div><my-element /></div>).all(MyElement);

Custom filtering function

Custom filtering functions are simply functions that take a single node argument.

mount(<div><span /></div>).all(node => node.localName === 'span');

Diffing node trees

You can mount a node and search using a different node instance as long as it looks the same.

mount(<div><span /></div>).all(<span />);

The node trees must match exactly, so this will not work.

mount(<div><span>test</span></div>).all(<span />);

Using an object as criteria

You can pass an object and it will match the properties on the object to the properties on the element.

mount(<div><span id="test" /></div>).all({ id: 'test' });

The objects must completely match, so this will not work.

mount(<div><span id="test" /></div>).all({ id: 'test', somethingElse: true });

Selector

You can pass a string and it will try and use it as a selector.

mount(<div><span id="test" /></div>).all('#test');

one(query)

Same as all(query) but only returns a single wrapped node.

mount(<div><span /></div>).one(<span />);

has(query)

Same as all(query) but returns true or false if the query returned results.

mount(<div><span /></div>).has(<span />);

wait([then])

The wait() function returns a promise that waits for a shadow root to be present. Even though Bore ensures the constructor and connectedCallback are called synchronously, your component may not have a shadow root right away, for example, if it were to have an async renderer that automatically creates a shadow root. An example of this is Skate's renderer.

mount(<MyComponent />).wait().then(doSomething);

A slightly more concise form of the same thing could look like:

mount(<MyComponent />).wait(doSomething);

waitFor(funcReturnBool[, options = { delay: 1 }])

Similar to wait(), waitFor(callback) will return a Promise that polls the callback at the specified delay. When it returns truthy, the promise resolves with the wrapper as the value.

mount(<MyElement />).waitFor(wrapper => wrapper.has(<div />));

This is very usefull when coupled with a testing framework that supports promises, such as Mocha:

describe('my custom element', () => {
  it('should have an empty div', () => {
    return mount(<MyComponent />).waitFor(w => w.has(<div />));
  });
});

bore's People

Contributors

hopkins-tk avatar hotell avatar renovate-bot avatar renovate[bot] avatar treshugart avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

bore's Issues

Add deep flag for traversing down shadow trees.

This would effectively fill what the shadow-piercing combinator enabled. No matter how deep the element you're trying to select is, it should find and return it. The only catch is that selectors will still be bound to shadow boundaries, which I think is fair.

The deep flag should be added to the walk() function as an option. It will be passed by anything that uses walk() internally, such as: one(), all() and has().

mount(<my-component />).one('a.some-deep-element', { deep: true });

Where <my-component /> contains the a several shadow trees down.

To work on existing nodes:

mount(document.getElementById('app')).one('a.some-deep-element', { deep: true });

A more common use case might be with Selenium where you'd find something by text content, or by a query that cannot be expressed by a selector or complete node:

mount(document.getElementById('app').one({ textContent: 'Submit' }, { deep: true });

shallow rendering vs full rendering.

The README says

There's no distinction between shallow rendering and full rendering as there's no significant performance implications and Shadow DOM negates the need for the distinction.

What does that mean?

Add method for triggering events

This should trigger an event in the same manner skate does. This should probably be called something like emit(), trigger() or dispatch() instead of simulate(), because it's not really "simulating" an event.

// Should be able to pass a string and it will emit an Event or fallback go CustomEvent
mount(<something />).emit('my-event', options);

// Should be able to emit a user-provided event instance, too.
mount(<something />).emit(new MouseEvent());

Consider allowing light DOM and shadow DOM wrappers separately

Currently there's only mount(). There might be use cases for:

  • light() - Only traverses light DOM
  • shadow() - Current impl of mount(). Uses shadowRoot, but does not descend into descendant components contained in the shadow root (only sees their light DOM).
  • shadow({ deep: true }) traverses child shadow roots all the way down

Descendant trees get thrown out

For example, if you have the tree:

#shadow-root
  <div>
    <p />
  </div>

And you try and select the <p />, it will never select it because the <div /> is thrown out. This works just fine with elements without shadow roots.

Support HTML in querying functions

Currently we support HTML in mount() but it'd be nice if this was also supported in all() et al to match that. For example:

mount('<div><span></span></div>').one('<span></span>');

If we do this, the heuristic for seeing if it's a selector or HTML needs to be considered. Upon initial inspection, it seems that you can do something like:

// This would match HTML
query.trim().indexOf('<') === 0

The only downsides, which we can't do anything about other than trimming around the string is whitespace inside of the node, which has to match exactly. This can catch you up if you use template literals:

// Matches because trim()
mount('<div><span></span></div>').one(`
  <span></span>
`);

// Does not match because inner whitespace.
// JSX works because it ignores whitespace.
mount('<div><span></span></div>').one(`
  <span>
  </span>
`);

This would probably just need to be emphasised in the docs when documenting this form of the arguments.

readme inconsistencies

hi!
I just discovered this awesome project and wanted to got started in testing Web Components with Bore on Node.js, but unfortunately there are a couple of small gotchas on the readme file of this repo:

tomorrow I will slowly check out the docs to better understand, but if you could fix this I think it would be great for newcomers ๐Ÿ˜ƒ

thanks for the great work man!

Add way to wait for something (such as a shadow root)

References: skatejs/skatejs#939.

By default the wait getter should wait for a shadowRoot property. It should call a public waitFor() function which allows the user to specify a callback and poll for anything.

This allows custom elements to do async stuff and this to wait for a condition to be met. This is useful for rendering libraries, or debouncing property sets and rendering after.

EDIT

For simplicity, it would be nice to have a then() function that can be called which will only execute the callback after the Promise resolves, but also returning a promise itself. This means for the 95% use case (shadowRoot), one only has to call then() instead of wait.then(). This removes the need for wait but means we still need waitFor to return a Promise.

describe('my element', () => {
  it('should have a span', () => {
    return mount(<MyElement />).then(wrapper =>
      expect(wrapper.has(<span />)).to.equal(true));
  });
});

This has the added bonus of playing well with Mocha (and others) because you can return a Promise in it().

allow simplified wait for multiple custom elements

We have encounter case where we need to test multiple elements in single test as you can see here https://github.com/wc-catalogue/blaze-elements/blob/master/packages/collapsible/Collapsible.test.tsx#L53

So I have got idea that we can enhance waitFor functionality by allowing array of selectors for each element we want to wait for as argument.

Something like this:

    mount(
      <div>
        <SomeElement id="first"/>
        <SomeElement id="second"/>
      </div>
    ).waitFor(['#first', '#second'])
    .then(...)

as an alternative we can also add new method(waitForAll?) with will just use waitFor under the hood.

Let me know if you like the idea and I can try simple pull request for that.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.