Skip to content

Instantly share code, notes, and snippets.

@adamscybot
Last active November 2, 2024 00:11
Show Gist options
  • Save adamscybot/b76a751ed5da6daaed33065d2832add8 to your computer and use it in GitHub Desktop.
Save adamscybot/b76a751ed5da6daaed33065d2832add8 to your computer and use it in GitHub Desktop.
Cypress example to check if a set of elements changed in a meaningful way via `isEqualNode`. Useful for detecting a change in a list of elements (positionally) according to each of their defining characteristics. Avoids detecting recreated/change elements that are really the same (e.g. ignore attr order, like for like replacements)
<body>
<div>initial element</div>
<button onclick="change()">Change element</button>
<script>
function change() {
// WILL NOT be detected as a change
setTimeout(() => {
const div1 = document.querySelector('div')
const div2 = document.createElement('div')
div2.innerText = 'initial element'
div1.replaceWith(div2)
}, 1000)
// WILL be detected as a change
setTimeout(() => {
const div1 = document.querySelector('div')
const div2 = document.createElement('div')
div2.innerText = 'changed element'
div1.replaceWith(div2)
}, 2000)
}
</script>
</body>
// This method of detecting changes in a list of elements is a sledgehammer and probably memory intense if your DOM is already memory intense.
// But leaning on the semantics of the web platforms `isEqualNode` is useful if those semantics are what you are after.
//
// The semantics are much closer to a "meaningful change" than comparing element references. But dont go all the way to
// a "user visible change" guarantee since that depends if things like certain possible attribute changes amount
// to a visible change in your app.
//
// It differs from the usual way of detecting changes (which should remain the usual, this gist is for edge cases). The
// usual way being targetted comparison of some individual aspects of the elements, e.g. visible text. The difference is
// that this is a good middle ground for detecting "any characteristic change" of an element in cases where perhaps you need
// something less brittle or dont even know or care what those changes might be for some reason (e.g. its external arbitrary content).
//
// It differs from comparing via crude `innerHTML` comparisons, since `isEqualNode` ignores things like attribute order.
//
// See https://developer.mozilla.org/en-US/docs/Web/API/Node/isEqualNode.
// Get initial elements of the list. Cloning them ensures we snapshot them to avoid breaking cases where the node is not replaced
// but mutated. We want that to be subject to the semantics of `isEqualNode` too and to ensure that we cant accidentally compare an element
// reference to itself.
cy.get('div').invoke('clone').as('initial')
// Some action that causes a change in the list
cy.get('button').click()
// Get new elements of the list
cy.get('div').as('final')
cy.get('@initial').then((initial) => {
cy.get('@final').should('satisfy', (final) => {
// If the list is different length we alrady know theres a difference.
if (final.length !== initial.length) return true
// Otherwise, at least one node needs to have changed in a meaningful way, for each position in the list.
return final
.toArray()
.some((el, index) => !el.isEqualNode(initial.get(index)))
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment