Skip to content

Instantly share code, notes, and snippets.

@cferdinandi
Last active December 23, 2024 14:55
Show Gist options
  • Save cferdinandi/8ebc5e189a42827edc0020628bd84bc9 to your computer and use it in GitHub Desktop.
Save cferdinandi/8ebc5e189a42827edc0020628bd84bc9 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Mocha Tests</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="https://unpkg.com/chai@4/chai.js"></script>
<script src="https://unpkg.com/mocha/mocha.js"></script>
<script class="mocha-init">
mocha.setup('bdd');
mocha.checkLeaks();
window.addEventListener('load', function () {
mocha.run();
});
</script>
<script src="show-hide.js"></script>
<script src="tests.dom-helpers.js"></script>
<script src="test.show-hide.js"></script>
</body>
</html>
customElements.define('show-hide', class extends HTMLElement {
/**
* Instantiate the Web Component
*/
constructor () {
// Get parent class properties
super();
// Get the elements
this.trigger = this.querySelector('[trigger]');
this.content = this.querySelector('[content]');
if (!this.trigger || !this.content) return;
// Setup default UI
this.trigger.removeAttribute('hidden');
this.trigger.setAttribute('aria-expanded', false);
this.content.setAttribute('hidden', '');
// Listen for click events
if (!this.trigger || !this.content) return;
this.trigger.addEventListener('click', this);
}
/**
* Handle events
* @param {Event} event The event object
*/
handleEvent (event) {
// Don't let the button trigger other actions
event.preventDefault();
// If the content is expanded, hide it
// Otherwise, show it
if (this.trigger.getAttribute('aria-expanded') === 'true') {
this.trigger.setAttribute('aria-expanded', false);
this.content.setAttribute('hidden', '');
} else {
this.trigger.setAttribute('aria-expanded', true);
this.content.removeAttribute('hidden');
}
}
});
const {expect} = chai;
describe('The <show-hide> Web Component', function () {
//
// Test Setup
//
// Create a DOM node to inject content into
let app = document.createElement('div');
document.body.append(app);
// Get the Web Component elements from the DOM
function getElements () {
return {
wc: app.querySelector('show-hide'),
trigger: app.querySelector('[trigger]'),
content: app.querySelector('[content]'),
};
}
// Clean up UI
after(function () {
app.remove();
});
// Inject fresh Web Component
beforeEach(function () {
app.innerHTML =
`<show-hide>
<button trigger hidden>Show Content</button>
<div content>
<p>Now you see me, now you don't!</p>
</div>
</show-hide>`;
});
//
// Tests
//
it('Should hide content and make button visible on load', function () {
let {wc, trigger, content} = getElements();
expect(trigger.hasAttribute('hidden')).to.equal(false);
expect(content.hasAttribute('hidden')).to.equal(true);
});
it('Should add required ARIA attributes on load', function () {
let {trigger} = getElements();
expect(trigger.getAttribute('aria-expanded')).to.equal('false');
});
it('Should show and hide content on click events', function () {
// Get elements
let {trigger, content} = getElements();
// Show content on click
simulateClick(trigger);
expect(content.hasAttribute('hidden')).to.equal(false);
// Hide content if clicked again
simulateClick(trigger);
expect(content.hasAttribute('hidden')).to.equal(true);
});
it('Should update ARIA on click events', function () {
// Get elements
let {trigger} = getElements();
// Show content on click
simulateClick(trigger);
expect(trigger.getAttribute('aria-expanded')).to.equal('true');
// Hide content if clicked again
simulateClick(trigger);
expect(trigger.getAttribute('aria-expanded')).to.equal('false');
});
});
//
// Helper functions for DOM tests
//
/**
* Simulate an event
* @param {Element} elem The element to simulate a click on
* @param {String} type The event type to simulate
*/
function simulateEvent (elem, type) {
// Create a new event
var event = new Event(type, {
bubbles: true,
cancellable: true
});
// Dispatch the event
return elem.dispatchEvent(event);
}
/**
* Simulate a click event
* @param {Element} elem the element to simulate a click on
*/
function simulateClick (elem) {
elem.click();
}
/**
* Simulate a click event
* @param {String} key The key to simulate
* @param {Element} elem the element to simulate a click on
* @param {String} type The type of keyboard event to simulate
*/
function simulateKeyboard (key, elem = document, type = 'keydown') {
let event = new KeyboardEvent(type, {key});
return elem.dispatchEvent(event);
}
@tbrlpld
Copy link

tbrlpld commented Dec 22, 2024

Super interesting to see this in-browser only testing approach.

@cferdinandi One question about that last lines of the test helper: Why do you assign the return of the dispatch to a variable?

	let canceled = !elem.dispatchEvent(event);

It does not seem to be used or returned. Is this for potential extension?

@cferdinandi
Copy link
Author

@tbrlpld Good catch! That was a copy/paste error from a previous bit of code. I also updated the simulateClick() method to use the more modern Element.click().

@tbrlpld
Copy link

tbrlpld commented Dec 23, 2024

Thanks for explaining 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment