Files
yoga/spec/LayoutSpec.js

510 lines
16 KiB
JavaScript
Raw Normal View History

2014-03-30 17:12:38 -07:00
var iframe = (function() {
2014-03-30 17:12:38 -07:00
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
var doc = iframe.contentDocument;
var style = document.createElement('style');
style.innerText = (function() {/*
body, div {
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
2014-04-14 10:29:04 -07:00
flex-shrink: 0;
margin: 0;
padding: 0;
2014-03-30 17:12:38 -07:00
}
*/} + '').slice(15, -4);
doc.head.appendChild(style);
2014-03-30 17:12:38 -07:00
return iframe;
})();
2014-03-30 17:12:38 -07:00
function computeDOMLayout(node) {
var body = iframe.contentDocument.body;
2014-03-31 11:09:33 -07:00
function transfer(div, node, name, ext) {
2014-03-30 17:12:38 -07:00
if (name in node.style) {
2014-03-31 11:09:33 -07:00
div.style[name] = node.style[name] + (ext || '');
2014-03-30 17:12:38 -07:00
}
}
function renderNode(parent, node) {
var div = document.createElement('div');
2014-03-31 11:09:33 -07:00
transfer(div, node, 'width', 'px');
transfer(div, node, 'height', 'px');
transfer(div, node, 'margin', 'px');
2014-04-06 10:19:53 -07:00
transfer(div, node, 'marginLeft', 'px');
transfer(div, node, 'marginTop', 'px');
transfer(div, node, 'marginBottom', 'px');
transfer(div, node, 'marginRight', 'px');
2014-03-31 11:09:33 -07:00
transfer(div, node, 'flexDirection');
2014-04-05 22:23:00 -07:00
transfer(div, node, 'flex');
2014-04-06 17:39:30 -07:00
transfer(div, node, 'justifyContent');
transfer(div, node, 'alignSelf');
transfer(div, node, 'alignItems');
2014-03-30 17:12:38 -07:00
parent.appendChild(div);
(node.children || []).forEach(function(child) {
renderNode(div, child);
});
return div;
}
var div = renderNode(body, node);
function buildLayout(absoluteRect, div) {
2014-03-30 17:12:38 -07:00
var rect = div.getBoundingClientRect();
var result = {
width: rect.width,
height: rect.height,
top: rect.top - absoluteRect.top,
left: rect.left - absoluteRect.left
2014-03-30 17:12:38 -07:00
};
var children = [];
for (var child = div.firstChild; child; child = child.nextSibling) {
children.push(buildLayout(rect, child));
2014-03-30 17:12:38 -07:00
}
if (children.length) {
result.children = children;
}
return result;
}
var layout = buildLayout({left: 0, top: 0}, div);
body.removeChild(div);
return layout;
}
2014-04-05 22:23:00 -07:00
function nameLayout(name, layout) {
var namedLayout = {name: name};
for (var key in layout) {
namedLayout[key] = layout[key];
}
return namedLayout;
}
function testNamedLayout(name, layoutA, layoutB) {
expect(nameLayout(name, layoutA))
.toEqual(nameLayout(name, layoutB));
}
2014-03-30 17:12:38 -07:00
function testLayout(node, expectedLayout) {
var layout = computeLayout(node);
var domLayout = computeDOMLayout(node);
2014-04-05 22:23:00 -07:00
testNamedLayout('expected-dom', expectedLayout, domLayout);
testNamedLayout('layout-dom', layout, domLayout);
2014-03-30 17:12:38 -07:00
}
describe('Layout', function() {
it('should layout a single node with width and height', function() {
testLayout({
style: {width: 100, height: 200}
}, {
2014-03-30 19:33:24 -07:00
width: 100, height: 200, top: 0, left: 0
2014-03-30 19:18:06 -07:00
});
});
it('should layout node with children', function() {
testLayout(
{style: {width: 1000, height: 1000}, children: [
2014-03-30 19:18:06 -07:00
{style: {width: 500, height: 500}},
{style: {width: 250, height: 250}},
{style: {width: 125, height: 125}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
2014-03-30 19:18:06 -07:00
{width: 500, height: 500, top: 0, left: 0},
{width: 250, height: 250, top: 500, left: 0},
{width: 125, height: 125, top: 750, left: 0}
]}
);
2014-03-30 17:12:38 -07:00
});
2014-03-30 19:33:24 -07:00
it('should layout node with nested children', function() {
testLayout(
{style: {width: 1000, height: 1000}, children: [
{style: {width: 500, height: 500}},
{style: {width: 500, height: 500}, children: [
2014-03-30 19:33:24 -07:00
{style: {width: 250, height: 250}},
{style: {width: 250, height: 250}}
]}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
{width: 500, height: 500, top: 0, left: 0},
{width: 500, height: 500, top: 500, left: 0, children: [
2014-03-30 19:33:24 -07:00
{width: 250, height: 250, top: 0, left: 0},
{width: 250, height: 250, top: 250, left: 0},
]}
]}
);
2014-03-30 19:33:24 -07:00
});
2014-03-30 19:51:14 -07:00
it('should layout node with margin', function() {
testLayout(
{style: {width: 100, height: 200, margin: 10}},
{width: 100, height: 200, top: 10, left: 10}
);
2014-03-30 19:51:14 -07:00
});
2014-03-30 20:33:40 -07:00
it('should layout node with several children', function() {
testLayout(
{style: {width: 1000, height: 1000, margin: 10}, children: [
2014-03-30 20:33:40 -07:00
{style: {width: 100, height: 100, margin: 50}},
{style: {width: 100, height: 100, margin: 25}},
{style: {width: 100, height: 100, margin: 10}}
]},
{width: 1000, height: 1000, top: 10, left: 10, children: [
2014-03-30 20:33:40 -07:00
{width: 100, height: 100, top: 50, left: 50},
{width: 100, height: 100, top: 225, left: 25},
{width: 100, height: 100, top: 360, left: 10}
]}
);
2014-03-30 20:33:40 -07:00
});
2014-03-31 11:09:33 -07:00
it('should layout node with row flex direction', function() {
testLayout(
{style: {width: 1000, height: 1000, flexDirection: 'row'}, children: [
2014-03-31 11:09:33 -07:00
{style: {width: 100, height: 200}},
{style: {width: 300, height: 150}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
2014-03-31 11:09:33 -07:00
{width: 100, height: 200, top: 0, left: 0},
{width: 300, height: 150, top: 0, left: 100}
]}
);
2014-03-31 11:09:33 -07:00
});
it('should layout node based on children main dimensions', function() {
testLayout(
{style: {width: 300}, children: [
{style: {width: 100, height: 200}},
{style: {width: 300, height: 150}}
]},
{width: 300, height: 350, top: 0, left: 0, children: [
{width: 100, height: 200, top: 0, left: 0},
{width: 300, height: 150, top: 200, left: 0}
]}
);
});
2014-04-05 22:23:00 -07:00
it('should layout node with flex', function() {
testLayout(
{style: {width: 1000, height: 1000}, children: [
2014-04-05 22:23:00 -07:00
{style: {width: 100, height: 200}},
{style: {width: 100, flex: 1}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
2014-04-05 22:23:00 -07:00
{width: 100, height: 200, top: 0, left: 0},
{width: 100, height: 800, top: 200, left: 0}
]}
);
2014-04-05 22:23:00 -07:00
});
2014-04-06 09:43:16 -07:00
it('should layout node with flex recursively', function() {
testLayout(
{style: {width: 1000, height: 1000}, children: [
{style: {width: 1000, flex: 1}, children: [
{style: {width: 1000, flex: 1}, children: [
{style: {width: 1000, flex: 1}}
]}
]}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
{width: 1000, height: 1000, top: 0, left: 0, children: [
{width: 1000, height: 1000, top: 0, left: 0, children: [
{width: 1000, height: 1000, top: 0, left: 0}
]}
]}
]}
);
2014-04-06 09:43:16 -07:00
});
2014-04-06 10:19:53 -07:00
2014-04-06 17:39:30 -07:00
it('should layout node with targeted margin', function() {
testLayout(
{style: {width: 1000, height: 1000, marginTop: 10, marginLeft: 5}, children: [
{style: {width: 100, height: 100, marginTop: 50, marginLeft: 15, marginBottom: 20}},
2014-04-06 10:19:53 -07:00
{style: {width: 100, height: 100, marginLeft: 30}}
]},
{width: 1000, height: 1000, top: 10, left: 5, children: [
2014-04-06 10:19:53 -07:00
{width: 100, height: 100, top: 50, left: 15},
{width: 100, height: 100, top: 170, left: 30}
]}
);
2014-04-06 10:19:53 -07:00
});
2014-04-06 17:39:30 -07:00
it('should layout node with justifyContent: flex-start', function() {
testLayout(
{style: {width: 1000, height: 1000, justifyContent: 'flex-start'}, children: [
2014-04-06 17:39:30 -07:00
{style: {width: 100, height: 100}},
{style: {width: 100, height: 100}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
2014-04-06 17:39:30 -07:00
{width: 100, height: 100, top: 0, left: 0},
{width: 100, height: 100, top: 100, left: 0}
]}
);
2014-04-06 17:39:30 -07:00
});
it('should layout node with justifyContent: flex-end', function() {
testLayout(
{style: {width: 1000, height: 1000, justifyContent: 'flex-end'}, children: [
2014-04-06 17:39:30 -07:00
{style: {width: 100, height: 100}},
{style: {width: 100, height: 100}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
2014-04-06 17:39:30 -07:00
{width: 100, height: 100, top: 800, left: 0},
{width: 100, height: 100, top: 900, left: 0}
]}
);
2014-04-06 17:39:30 -07:00
});
it('should layout node with justifyContent: space-between', function() {
testLayout(
{style: {width: 1000, height: 1000, justifyContent: 'space-between'}, children: [
2014-04-06 17:39:30 -07:00
{style: {width: 100, height: 100}},
{style: {width: 100, height: 100}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
2014-04-06 17:39:30 -07:00
{width: 100, height: 100, top: 0, left: 0},
{width: 100, height: 100, top: 900, left: 0}
]}
);
2014-04-06 17:39:30 -07:00
});
it('should layout node with justifyContent: space-around', function() {
testLayout(
{style: {width: 1000, height: 1000, justifyContent: 'space-around'}, children: [
2014-04-06 17:39:30 -07:00
{style: {width: 100, height: 100}},
{style: {width: 100, height: 100}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
2014-04-06 17:39:30 -07:00
{width: 100, height: 100, top: 200, left: 0},
{width: 100, height: 100, top: 700, left: 0}
]}
);
2014-04-06 17:39:30 -07:00
});
it('should layout node with justifyContent: center', function() {
testLayout(
{style: {width: 1000, height: 1000, justifyContent: 'center'}, children: [
2014-04-06 17:39:30 -07:00
{style: {width: 100, height: 100}},
{style: {width: 100, height: 100}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
2014-04-06 17:39:30 -07:00
{width: 100, height: 100, top: 400, left: 0},
{width: 100, height: 100, top: 500, left: 0}
]}
);
2014-04-06 17:39:30 -07:00
});
2014-04-06 19:21:06 -07:00
it('should layout node with flex override height', function() {
testLayout(
{style: {width: 1000, height: 1000}, children: [
2014-04-06 19:21:06 -07:00
{style: {width: 100, height: 100, flex: 1}},
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
2014-04-06 19:21:06 -07:00
{width: 100, height: 1000, top: 0, left: 0}
]}
);
2014-04-06 19:21:06 -07:00
});
it('should layout node with alignItems: flex-start', function() {
testLayout(
{style: {width: 1000, height: 1000, alignItems: 'flex-start'}, children: [
{style: {width: 200, height: 100}},
{style: {width: 100, height: 100}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
{width: 200, height: 100, top: 0, left: 0},
{width: 100, height: 100, top: 100, left: 0},
]}
);
});
it('should layout node with alignItems: center', function() {
testLayout(
{style: {width: 1000, height: 1000, alignItems: 'center'}, children: [
{style: {width: 200, height: 100}},
{style: {width: 100, height: 100}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
{width: 200, height: 100, top: 0, left: 400},
{width: 100, height: 100, top: 100, left: 450},
]}
);
});
it('should layout node with alignItems: flex-end', function() {
testLayout(
{style: {width: 1000, height: 1000, alignItems: 'flex-end'}, children: [
{style: {width: 200, height: 100}},
{style: {width: 100, height: 100}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
{width: 200, height: 100, top: 0, left: 800},
{width: 100, height: 100, top: 100, left: 900},
]}
);
});
it('should layout node with alignSelf overrides alignItems', function() {
testLayout(
{style: {width: 1000, height: 1000, alignItems: 'flex-end'}, children: [
{style: {width: 200, height: 100}},
{style: {width: 100, height: 100, alignSelf: 'center'}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
{width: 200, height: 100, top: 0, left: 800},
{width: 100, height: 100, top: 100, left: 450},
]}
);
});
2014-04-09 19:15:46 -07:00
it('should layout node with alignItem: stretch', function() {
testLayout(
{style: {width: 1000, height: 1000, alignItems: 'stretch'}, children: [
2014-04-09 19:15:46 -07:00
{style: {height: 100}}
]},
{width: 1000, height: 1000, top: 0, left: 0, children: [
2014-04-09 19:15:46 -07:00
{width: 1000, height: 100, top: 0, left: 0}
]}
);
2014-04-09 19:15:46 -07:00
});
2014-04-09 19:40:17 -07:00
it('should layout empty node', function() {
testLayout(
{style: {}, children: [
2014-04-09 19:40:17 -07:00
{style: {}}
]},
{width: 0, height: 0, top: 0, left: 0, children: [
2014-04-09 19:40:17 -07:00
{width: 0, height: 0, top: 0, left: 0}
]}
);
2014-04-09 19:40:17 -07:00
});
2014-04-10 09:29:06 -07:00
it('should layout child with margin', function() {
testLayout(
{style: {}, children: [
2014-04-10 09:29:06 -07:00
{style: {margin: 5}}
]},
{width: 10, height: 10, top: 0, left: 0, children: [
2014-04-10 09:29:06 -07:00
{width: 0, height: 0, top: 5, left: 5}
]}
);
2014-04-10 09:29:06 -07:00
});
2014-04-14 10:29:04 -07:00
it('should not shrink children if not enough space', function() {
testLayout(
{style: {height: 100}, children: [
2014-04-14 10:29:04 -07:00
{style: {height: 100}},
{style: {height: 200}},
]},
{width: 0, height: 100, top: 0, left: 0, children: [
2014-04-14 10:29:04 -07:00
{width: 0, height: 100, top: 0, left: 0},
{width: 0, height: 200, top: 100, left: 0}
]}
);
2014-04-14 10:29:04 -07:00
});
it('should layout for center', function() {
testLayout(
{style: {justifyContent: 'center'}},
{width: 0, height: 0, top: 0, left: 0}
);
});
it('should layout flex-end taking into account margin', function() {
testLayout(
{style: {height: 100, justifyContent: 'flex-end'}, children: [
{style: {marginTop: 10}}
]},
{width: 0, height: 100, top: 0, left: 0, children: [
{width: 0, height: 0, top: 100, left: 0}
]}
);
});
it('should layout alignItems with margin', function() {
testLayout(
{style: {}, children: [
{style: {alignItems: 'flex-end'}, children: [
{style: {margin: 10}},
{style: {height: 100}}
]}
]},
{width: 20, height: 120, top: 0, left: 0, children: [{
width: 20, height: 120, top: 0, left: 0, children: [
{width: 0, height: 0, top: 10, left: 10},
{width: 0, height: 100, top: 20, left: 20}
]}
]}
);
});
2014-04-09 21:02:16 -07:00
it('should layout randomly', function() {
function RNG(seed) {
this.state = seed;
}
RNG.prototype.nextFloat = function() {
// LCG using GCC's constants
this.state = (1103515245 * this.state + 12345) % 0x80000000;
return this.state / (0x80000000 - 1);
}
var rng = new RNG(0);
function randMinMax(node, chance, attribute, min, max) {
if (rng.nextFloat() < chance) {
node.style[attribute] = Math.floor(rng.nextFloat() * (max - min)) + min;
}
}
function randEnum(node, chance, attribute, enumValues) {
if (rng.nextFloat() < chance) {
node.style[attribute] = enumValues[Math.floor(rng.nextFloat() * enumValues.length)];
}
}
2014-04-10 09:29:06 -07:00
function randChildren(node, chance) {
while (rng.nextFloat() < chance) {
2014-04-10 09:29:06 -07:00
if (!node.children) {
node.children = [];
}
node.children.push(generateRandomNode());
}
}
2014-04-09 21:02:16 -07:00
function generateRandomNode() {
var node = {style: {}};
randMinMax(node, 0.1, 'width', 0, 1000);
randMinMax(node, 0.1, 'height', 0, 1000);
randMinMax(node, 0.1, 'margin', 0, 20);
randMinMax(node, 0.1, 'marginLeft', 0, 20);
randMinMax(node, 0.1, 'marginTop', 0, 20);
randMinMax(node, 0.1, 'marginRight', 0, 20);
randMinMax(node, 0.1, 'marginBottom', 0, 20);
randEnum(node, 0.1, 'flexDirection', ['row', 'column']);
randEnum(node, 0.1, 'justifyContent', ['flex-start', 'center', 'flex-end', 'space-between', 'space-around']);
randEnum(node, 0.1, 'alignItems', ['flex-start', 'center', 'flex-end']);
2014-04-14 17:50:55 -07:00
randEnum(node, 0.1, 'alignSelf', ['flex-start', 'center', 'flex-end']);
2014-04-10 09:29:06 -07:00
randChildren(node, 0.2);
2014-04-09 21:02:16 -07:00
return node;
}
2014-04-14 10:29:04 -07:00
for (var i = 0; i < 1000; ++i) {
2014-04-09 21:02:16 -07:00
var node = generateRandomNode();
2014-04-14 17:50:55 -07:00
// The iframe's body has a natural width of 300 that it doesn't really make
// to replicate in the test suite. The easiest workaround is not to test
// alignSelf property on the top element.
delete node.style.alignSelf;
delete node.style.flex;
2014-04-14 17:50:55 -07:00
expect({i: i, node: node, layout: computeLayout(node)})
.toEqual({i: i, node: node, layout: computeDOMLayout(node)});
2014-04-09 21:02:16 -07:00
}
})
2014-03-30 17:12:38 -07:00
});