Skip to content

Commit

Permalink
feat: Add SerialVirtualNode class
Browse files Browse the repository at this point in the history
  • Loading branch information
WilcoFiers committed Aug 30, 2019
1 parent 2725781 commit 82d01b0
Show file tree
Hide file tree
Showing 2 changed files with 253 additions and 0 deletions.
80 changes: 80 additions & 0 deletions lib/core/base/virtual-node/serial-virtual-node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// eslint-disable-next-line no-unused-vars
class SerialVirtualNode extends axe.AbstractVirtualNode {
/**
* Convert a serialised node into a VirtualNode object
* @param {Object} node Serialised node
*/
constructor (serialNode) {
super()
this._props = normaliseProps(serialNode)
this._attrs = normaliseAttrs(serialNode)
}

// Accessof for DOM node properties
get props () {
return this._props
}

/**
* Get the value of the given attribute name.
* @param {String} attrName The name of the attribute.
* @return {String|null} The value of the attribute or null if the attribute does not exist
*/
attr(attrName) {
return this._attrs[attrName] || null
}

/**
* Determine if the element has the given attribute.
* @param {String} attrName The name of the attribute
* @return {Boolean} True if the element has the attribute, false otherwise.
*/
hasAttr(attrName) {
return this._attrs[attrName] !== undefined;
}
}

/**
* Convert between serialised props and DOM-like properties
* @param {Object} serialNode
* @return {Object} normalProperties
*/
function normaliseProps(serialNode) {
let { nodeName, nodeType = 1 } = serialNode
axe.utils.assert(nodeType !== undefined || nodeType !== 1,
`SerialVirtualNode expects nodeType of undefined or 1, got '${nodeType}'`)
axe.utils.assert(typeof nodeName === 'string',
`SerialVirtualNode expects nodeName to be a string, got '${nodeName}'`)

const props = {
...serialNode,
nodeType,
nodeName: nodeName.toLowerCase()
}
delete props.attributes
return Object.freeze(props)
}

/**
* Convert between serialised attributes and DOM-like attributes
* @param {Object} serialNode
* @return {Object} normalAttributes
*/
function normaliseAttrs({ attributes = {} }) {
const attrMap = {
'htmlFor': 'for',
'className': 'class'
}

return Object.keys(attributes).reduce((attrs, attrName) => {
const value = attributes[attrName];
axe.utils.assert(typeof value !== 'object' || value === null,
`SerialVirtualNode expects attributes not to be an object, '${attrName}' was`)

if (value !== undefined) {
const mappedName = attrMap[attrName] || attrName
attrs[mappedName] = value !== null ? String(value) : null
}
return attrs
}, {})
}
173 changes: 173 additions & 0 deletions test/core/base/virtual-node/serial-virtual-node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*global axe, SerialVirtualNode */
describe('SerialVirtualNode', function() {
it('extends AbstractVirtualNode', function () {
var vNode = new SerialVirtualNode({
nodeName: 'div'
});
assert.instanceOf(vNode, axe.AbstractVirtualNode);
});

describe('props', function () {
it('assigns any properties to .props', function () {
var props = {
nodeType: 1,
nodeName: 'div',
someType: 'bar',
somethingElse: 'baz'
}
var vNode = new SerialVirtualNode(props);
assert.deepEqual(vNode.props, props);
})

it('returns a frozen object', function () {
var vNode = new SerialVirtualNode({ nodeName: 'div' });
assert.isTrue(Object.isFrozen(vNode.props), 'Expect object to be frozen');
})

it('has a default nodeType of 1', function () {
var vNode = new SerialVirtualNode({ nodeName: 'div' });
assert.equal(vNode.props.nodeType, 1)
})

it('converts nodeNames to lower case', function () {
var htmlNodes = [
'DIV',
'SPAN',
'INPUT',
'HeAdEr',
'TABLE',
'TITLE',
'BUTTON',
'Foo'
]
htmlNodes.forEach(function (nodeName) {
var vNode = new SerialVirtualNode({ nodeName: nodeName });
assert.equal(vNode.props.nodeName, nodeName.toLowerCase())
})
})

it('ignores the `attributes` property', function () {
var vNode = new SerialVirtualNode({
nodeName: 'div',
attributes: {
'foo': 'foo',
'bar': 'bar',
'baz': 'baz'
}
});
assert.isUndefined(vNode.props.attributes)
})
})

describe('attr', function () {
it('returns a string value for the attribute', function () {
var vNode = new SerialVirtualNode({
nodeName: 'div',
attributes: {
'foo': 'foo',
'bar': 123,
'baz': true
}
});
assert.equal(vNode.attr('foo'), 'foo');
assert.equal(vNode.attr('bar'), '123');
assert.equal(vNode.attr('baz'), 'true');
})

it('returns null if the attribute is null', function () {
var vNode = new SerialVirtualNode({
nodeName: 'div',
attributes: { 'foo': null }
});
assert.isNull(vNode.attr('foo'));
})

it('returns null if the attribute is not set', function () {
var vNode = new SerialVirtualNode({
nodeName: 'div'
});
assert.isNull(vNode.attr('foo'));
})

it('converts `className` to `class`', function () {
var vNode = new SerialVirtualNode({
nodeName: 'div',
attributes: {
className: 'foo bar baz'
}
});
assert.equal(vNode.attr('class'), 'foo bar baz');
})

it('converts `htmlFor` to `for`', function () {
var vNode = new SerialVirtualNode({
nodeName: 'div',
attributes: {
htmlFor: 'foo'
}
});
assert.equal(vNode.attr('for'), 'foo');
})
})

describe('hasAttr', function () {
it('returns true if the attribute has a value', function () {
var vNode = new SerialVirtualNode({
nodeName: 'div',
attributes: {
'foo': '',
'bar': 0,
'baz': false
}
});
assert.isTrue(vNode.hasAttr('foo'));
assert.isTrue(vNode.hasAttr('bar'));
assert.isTrue(vNode.hasAttr('baz'));
})

it('returns true if the attribute is null', function () {
var vNode = new SerialVirtualNode({
nodeName: 'div',
attributes: { 'foo': null }
});
assert.isTrue(vNode.hasAttr('foo'));
})

it('returns false if the attribute is undefined', function () {
var vNode = new SerialVirtualNode({
nodeName: 'div',
attributes: { 'foo': undefined }
});
assert.isFalse(vNode.hasAttr('foo'));
assert.isFalse(vNode.hasAttr('bar'));
})

it('converts `htmlFor` to `for`', function () {
var nodeWithoutFor = new SerialVirtualNode({
nodeName: 'div',
attributes: {}
});
var nodeWithFor = new SerialVirtualNode({
nodeName: 'div',
attributes: { 'htmlFor': 'foo' }
});

assert.isFalse(nodeWithoutFor.hasAttr('for'));
assert.isTrue(nodeWithFor.hasAttr('for'));
})

it('converts `className` to `class`', function () {
var nodeWithoutClass = new SerialVirtualNode({
nodeName: 'div',
attributes: {}
});
var nodeWithClass = new SerialVirtualNode({
nodeName: 'div',
attributes: { 'className': 'foo bar baz' }
});

assert.isFalse(nodeWithoutClass.hasAttr('class'));
assert.isTrue(nodeWithClass.hasAttr('class'));
})
})
});

0 comments on commit 82d01b0

Please sign in to comment.