Skip to content

Commit a31d5b4

Browse files
authored
feat: Add more form element serialization (#146)
* Update ember input serialization * Add version to `package.json` * Add select test
1 parent e387464 commit a31d5b4

File tree

3 files changed

+80
-33
lines changed

3 files changed

+80
-33
lines changed

addon/snapshot.js

+63-32
Original file line numberDiff line numberDiff line change
@@ -20,41 +20,70 @@ function getDoctype() {
2020
return doctype;
2121
}
2222

23+
const FORM_ELEMENTS_SELECTOR = 'input, textarea, select';
24+
25+
function mutateOriginalDOM(dom) {
26+
function createUID($el) {
27+
const ID = `_${Math.random().toString(36).substr(2, 9)}`;
28+
29+
$el.setAttribute('data-percy-element-id', ID)
30+
}
31+
32+
let formNodes = dom.querySelectorAll(FORM_ELEMENTS_SELECTOR)
33+
let formElements = Array.from(formNodes);
34+
35+
// loop through each form element and apply an ID for serialization later
36+
formElements.forEach((elem) => {
37+
if (!elem.getAttribute('data-percy-element-id')) {
38+
createUID(elem)
39+
}
40+
})
41+
}
42+
2343
// Set the property value into the attribute value for snapshotting inputs
24-
function setAttributeValues(dom) {
25-
// List of input types here https://www.w3.org/TR/html5/forms.html#the-input-element
26-
27-
// Limit scope to inputs only as textareas do not retain their value when cloned
28-
let elems = dom.find(
29-
`input[type=text], input[type=search], input[type=tel], input[type=url], input[type=email],
30-
input[type=password], input[type=number], input[type=checkbox], input[type=radio]`
31-
);
32-
33-
percyJQuery(elems).each(function() {
34-
let elem = percyJQuery(this);
35-
switch(elem.attr('type')) {
44+
function setAttributeValues(originalDOM, clonedDOM) {
45+
let formNodes = originalDOM.querySelectorAll(FORM_ELEMENTS_SELECTOR)
46+
let formElements = Array.from(formNodes);
47+
48+
formElements.forEach(elem => {
49+
let inputId = elem.getAttribute('data-percy-element-id')
50+
let selector = `[data-percy-element-id="${inputId}"]`;
51+
let cloneEl = clonedDOM.querySelector(selector)
52+
53+
if(!cloneEl) return;
54+
55+
switch (elem.type) {
3656
case 'checkbox':
3757
case 'radio':
38-
if (elem.is(':checked')) {
39-
elem.attr('checked', '');
58+
if (elem.checked) {
59+
cloneEl.setAttribute('checked', '')
60+
}
61+
break
62+
case 'select-one':
63+
if (elem.selectedIndex !== -1) {
64+
cloneEl.options[elem.selectedIndex].setAttribute('selected', 'true');
65+
}
66+
break
67+
case 'select-multiple':
68+
let selectedOptions = Array.from(elem.selectedOptions); // eslint-disable-line
69+
let clonedOptions = Array.from(cloneEl.options); // eslint-disable-line
70+
71+
if (selectedOptions.length) {
72+
selectedOptions.forEach((option) => {
73+
const matchingOption = clonedOptions.find((cloneOption) => option.text === cloneOption.text)
74+
matchingOption.setAttribute('selected', 'true')
75+
})
4076
}
41-
break;
77+
78+
break
79+
case 'textarea':
80+
// setting text or value does not work but innerHTML does
81+
cloneEl.innerHTML = elem.value
82+
break
4283
default:
43-
elem.attr('value', elem.val());
84+
cloneEl.setAttribute('value', elem.value)
4485
}
45-
});
46-
47-
return dom;
48-
}
49-
50-
// jQuery clone() does not copy textarea contents, so we explicitly do it here.
51-
function setTextareaContent(dom) {
52-
dom.find('textarea').each(function() {
53-
let elem = percyJQuery(this);
54-
elem.text(elem.val());
55-
});
56-
57-
return dom;
86+
})
5887
}
5988

6089
// Copy attributes from Ember's rootElement to the DOM snapshot <body> tag. Some applications rely
@@ -95,7 +124,9 @@ export function percySnapshot(name, options) {
95124
let scope = options.scope;
96125

97126
// Create a full-page DOM snapshot from the current testing page.
98-
let domCopy = percyJQuery('html').clone();
127+
let dom = percyJQuery('html');
128+
mutateOriginalDOM(dom[0]);
129+
let domCopy = dom.clone();
99130
let bodyCopy = domCopy.find('body');
100131
let testingContainer = domCopy.find('#ember-testing');
101132

@@ -107,8 +138,8 @@ export function percySnapshot(name, options) {
107138
snapshotRoot = testingContainer;
108139
}
109140

110-
snapshotRoot = setAttributeValues(snapshotRoot);
111-
snapshotRoot = setTextareaContent(snapshotRoot);
141+
// Pass the actual DOM nodes, not the jquery object
142+
setAttributeValues(dom[0], snapshotRoot[0]);
112143

113144
let snapshotHtml = snapshotRoot.html();
114145

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"name": "ember-percy",
3+
"version": "1.5.1",
34
"keywords": [
45
"ember-addon"
56
],

tests/integration/components/dummy-box-test.js

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { module, test } from 'qunit';
22
import { setupRenderingTest } from 'ember-qunit';
3-
import { render, findAll, click } from '@ember/test-helpers';
3+
import { render, findAll, click, fillIn } from '@ember/test-helpers';
44
import hbs from 'htmlbars-inline-precompile';
55
import { percySnapshot } from 'ember-percy';
66

@@ -84,6 +84,21 @@ module('Integration | Component | dummy box', function(hooks) {
8484
percySnapshot('textarea with value');
8585
});
8686

87+
test('it snapshots select values', async function(assert) {
88+
await render(
89+
hbs`<select>
90+
<option value="one">One</option>
91+
<option value="two">Two</option>
92+
</select>
93+
`);
94+
95+
await percySnapshot('select without value');
96+
await fillIn('select', 'two');
97+
await percySnapshot('select with value');
98+
99+
assert.ok(true);
100+
});
101+
87102
test('it handles identical assets with different paths', async function(assert) {
88103
await render(hbs`
89104
{{#dummy-box}}

0 commit comments

Comments
 (0)