Files
yoga/gentest/gentest.js
Nick Gerleman 7e96b65790 Import "Fix percent absolute position and size calcuate different with web" behind experimental feature (#1028) (#1201)
Summary:
Fixes https://github.com/facebook/yoga/issues/850

https://github.com/facebook/yoga/issues/850 describes a conformance issue where positioning of an absolute child using percentages is not calculated against the correct box size.

This takes the fix for that in https://github.com/facebook/yoga/pull/1028, regenerates tests, and fixes tests so that the experimental feature can be enabled. Goal is to run this as an experiment internally to see if we can enable by default.

Changelog:
[Internal]

Pull Request resolved: https://github.com/facebook/yoga/pull/1201

Reviewed By: yungsters

Differential Revision: D42282358

Pulled By: NickGerleman

fbshipit-source-id: 57c0dd9b0f1c47cb9335ff6e13d44b4646e5fa58
2023-01-08 13:41:27 -08:00

574 lines
18 KiB
JavaScript
Executable File

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const DEFAULT_EXPERIMENTS = [
'AbsolutePercentageAgainstPaddingEdge',
'FixAbsoluteTrailingColumnMargin',
];
window.onload = function() {
checkDefaultValues();
printTest(
new CPPEmitter(),
'cpp',
document.body.children[0],
document.body.children[1],
document.body.children[2]);
printTest(
new JavaEmitter(),
'java',
document.body.children[0],
document.body.children[1],
document.body.children[2]);
printTest(
new CSEmitter(),
'cs',
document.body.children[0],
document.body.children[1],
document.body.children[2]);
printTest(
new JavascriptEmitter(),
'js',
document.body.children[0],
document.body.children[1],
document.body.children[2]);
}
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
function printTest(e, ext, LTRContainer, RTLContainer, genericContainer) {
e.push([
ext === 'js' ? '/**' : '/*',
' * Copyright (c) Meta Platforms, Inc. and affiliates.',
' *',
' * This source code is licensed under the MIT license found in the',
' * LICENSE file in the root directory of this source tree.',
' */',
ext === 'cpp' ? '\n// clang-format off' : '',
'// @' + 'generated by gentest/gentest.rb from gentest/fixtures/' + document.title + '.html',
'',
]);
e.emitPrologue();
var LTRLayoutTree = calculateTree(LTRContainer);
var RTLLayoutTree = calculateTree(RTLContainer);
var genericLayoutTree = calculateTree(genericContainer);
for (var i = 0; i < genericLayoutTree.length; i++) {
e.emitTestPrologue(genericLayoutTree[i].name, genericLayoutTree[i].experiments);
if (genericLayoutTree[i].name == 'wrap_column') {
// Modify width and left values due to both safari and chrome not abiding by the
// specification. The undefined dimension of a parent should be defined by the total size
// of their children in that dimension.
// See diagram under flex-wrap header https://www.w3.org/TR/css-flexbox-1/
assert(LTRLayoutTree[0].width == 30, 'wrap_column LTR root.width should be 30');
LTRLayoutTree[0].width = 60;
assert(RTLLayoutTree[0].width == 30, 'wrap_column RTL root.width should be 30');
RTLLayoutTree[0].width = 60;
var children = RTLLayoutTree[0].children;
assert(children[0].left == 0, 'wrap_column RTL root_child0.left should be 0');
children[0].left = 30;
assert(children[1].left == 0, 'wrap_column RTL root_child0.left should be 0');
children[1].left = 30;
assert(children[2].left == 0, 'wrap_column RTL root_child2.left should be 0');
children[2].left = 30;
assert(children[3].left == -30, 'wrap_column RTL root_child3.left should be -30');
children[3].left = 0;
}
setupTestTree(
e,
undefined,
LTRLayoutTree[i],
genericLayoutTree[i],
'root',
null);
e.YGNodeCalculateLayout('root', e.YGDirectionLTR, genericLayoutTree[i].experiments);
e.push('');
assertTestTree(e, LTRLayoutTree[i], 'root', null);
e.push('');
e.YGNodeCalculateLayout('root', e.YGDirectionRTL, genericLayoutTree[i].experiments);
e.push('');
assertTestTree(e, RTLLayoutTree[i], 'root', null);
e.emitTestEpilogue(genericLayoutTree[i].experiments);
}
e.emitEpilogue();
e.print();
}
function assertTestTree(e, node, nodeName, parentName) {
e.AssertEQ(node.left, e.YGNodeLayoutGetLeft(nodeName));
e.AssertEQ(node.top, e.YGNodeLayoutGetTop(nodeName));
e.AssertEQ(node.width, e.YGNodeLayoutGetWidth(nodeName));
e.AssertEQ(node.height, e.YGNodeLayoutGetHeight(nodeName));
for (var i = 0; i < node.children.length; i++) {
e.push('');
var childName = nodeName + '_child' + i;
assertTestTree(e, node.children[i], childName, nodeName);
}
}
function checkDefaultValues() {
// Sanity check of the Yoga default values by test-template.html
[
{style:'flex-direction', value:'column'},
{style:'justify-content', value:'flex-start'},
{style:'align-content', value:'flex-start'},
{style:'align-items', value:'stretch'},
{style:'position', value:'relative'},
{style:'flex-wrap', value:'nowrap'},
{style:'overflow', value:'visible'},
{style:'flex-grow', value:'0'},
{style:'flex-shrink', value:'0'},
{style:'left', value:'undefined'},
{style:'top', value:'undefined'},
{style:'right', value:'undefined'},
{style:'bottom', value:'undefined'},
{style:'display', value:'flex'},
].forEach(function(item) {
assert(isDefaultStyleValue(item.style, item.value),
item.style + ' should be ' + item.value);
});
}
function setupTestTree(e, parent, node, genericNode, nodeName, parentName, index) {
e.emitTestTreePrologue(nodeName);
for (var style in node.style) {
// Skip position info for root as it messes up tests
if (node.declaredStyle[style] === "" &&
(style == 'position' ||
style == 'left' ||
style == 'top' ||
style == 'right' ||
style == 'bottom' ||
style == 'width' ||
style == 'height')) {
continue;
}
if (!isDefaultStyleValue(style, node.style[style])) {
switch (style) {
case 'gap':
e.YGNodeStyleSetGap(nodeName, e.YGGutterAll, pointValue(e, node.style[style]));
break;
case 'column-gap':
e.YGNodeStyleSetGap(nodeName, e.YGGutterColumn, pointValue(e, node.style[style]));
break;
case 'row-gap':
e.YGNodeStyleSetGap(nodeName, e.YGGutterRow, pointValue(e, node.style[style]));
break;
case 'direction':
e.YGNodeStyleSetDirection(nodeName, directionValue(e, node.style[style]));
break;
case 'flex-direction':
e.YGNodeStyleSetFlexDirection(nodeName, flexDirectionValue(e, node.style[style]));
break;
case 'justify-content':
e.YGNodeStyleSetJustifyContent(nodeName, justifyValue(e, node.style[style]));
break;
case 'align-content':
e.YGNodeStyleSetAlignContent(nodeName, alignValue(e, node.style[style]));
break;
case 'align-items':
e.YGNodeStyleSetAlignItems(nodeName, alignValue(e, node.style[style]));
break;
case 'align-self':
if (!parent || node.style[style] !== parent.style['align-items']) {
e.YGNodeStyleSetAlignSelf(nodeName, alignValue(e, node.style[style]));
}
break;
case 'position':
e.YGNodeStyleSetPositionType(nodeName, positionValue(e, node.style[style]));
break;
case 'flex-wrap':
e.YGNodeStyleSetFlexWrap(nodeName, wrapValue(e, node.style[style]));
break;
case 'overflow':
e.YGNodeStyleSetOverflow(nodeName, overflowValue(e, node.style[style]));
break;
case 'flex-grow':
e.YGNodeStyleSetFlexGrow(nodeName, node.style[style]);
break;
case 'flex-shrink':
e.YGNodeStyleSetFlexShrink(nodeName, node.style[style]);
break;
case 'flex-basis':
e.YGNodeStyleSetFlexBasis(nodeName, pointValue(e, node.style[style]));
break;
case 'left':
if (genericNode.rawStyle.indexOf('start:') >= 0) {
e.YGNodeStyleSetPosition(nodeName, e.YGEdgeStart, pointValue(e, node.style[style]));
} else {
e.YGNodeStyleSetPosition(nodeName, e.YGEdgeLeft, pointValue(e, node.style[style]));
}
break;
case 'top':
e.YGNodeStyleSetPosition(nodeName, e.YGEdgeTop, pointValue(e, node.style[style]));
break;
case 'right':
if (genericNode.rawStyle.indexOf('end:') >= 0) {
e.YGNodeStyleSetPosition(nodeName, e.YGEdgeEnd, pointValue(e, node.style[style]));
} else {
e.YGNodeStyleSetPosition(nodeName, e.YGEdgeRight, pointValue(e, node.style[style]));
}
break;
case 'bottom':
e.YGNodeStyleSetPosition(nodeName, e.YGEdgeBottom, pointValue(e, node.style[style]));
break;
case 'margin-left':
if (genericNode.rawStyle.indexOf('margin-start:') >= 0) {
e.YGNodeStyleSetMargin(nodeName, e.YGEdgeStart, pointValue(e, node.style[style]));
} else {
e.YGNodeStyleSetMargin(nodeName, e.YGEdgeLeft, pointValue(e, node.style[style]));
}
break;
case 'margin-top':
e.YGNodeStyleSetMargin(nodeName, e.YGEdgeTop, pointValue(e, node.style[style]));
break;
case 'margin-right':
if (genericNode.rawStyle.indexOf('margin-end:') >= 0) {
e.YGNodeStyleSetMargin(nodeName, e.YGEdgeEnd, pointValue(e, node.style[style]));
} else {
e.YGNodeStyleSetMargin(nodeName, e.YGEdgeRight, pointValue(e, node.style[style]));
}
break;
case 'margin-bottom':
e.YGNodeStyleSetMargin(nodeName, e.YGEdgeBottom, pointValue(e, node.style[style]));
break;
case 'padding-left':
if (genericNode.rawStyle.indexOf('padding-start:') >= 0) {
e.YGNodeStyleSetPadding(nodeName, e.YGEdgeStart, pointValue(e, node.style[style]));
} else {
e.YGNodeStyleSetPadding(nodeName, e.YGEdgeLeft, pointValue(e, node.style[style]));
}
break;
case 'padding-top':
e.YGNodeStyleSetPadding(nodeName, e.YGEdgeTop, pointValue(e, node.style[style]));
break;
case 'padding-right':
if (genericNode.rawStyle.indexOf('padding-end:') >= 0) {
e.YGNodeStyleSetPadding(nodeName, e.YGEdgeEnd, pointValue(e, node.style[style]));
} else {
e.YGNodeStyleSetPadding(nodeName, e.YGEdgeRight, pointValue(e, node.style[style]));
}
break;
case 'padding-bottom':
e.YGNodeStyleSetPadding(nodeName, e.YGEdgeBottom, pointValue(e, node.style[style]));
break;
case 'border-left-width':
if (genericNode.rawStyle.indexOf('border-start-width:') >= 0) {
e.YGNodeStyleSetBorder(nodeName, e.YGEdgeStart, pointValue(e, node.style[style]));
} else {
e.YGNodeStyleSetBorder(nodeName, e.YGEdgeLeft, pointValue(e, node.style[style]));
}
break;
case 'border-top-width':
e.YGNodeStyleSetBorder(nodeName, e.YGEdgeTop, pointValue(e, node.style[style]));
break;
case 'border-right-width':
if (genericNode.rawStyle.indexOf('border-end-width:') >= 0) {
e.YGNodeStyleSetBorder(nodeName, e.YGEdgeEnd, pointValue(e, node.style[style]));
} else {
e.YGNodeStyleSetBorder(nodeName, e.YGEdgeRight, pointValue(e, node.style[style]));
}
break;
case 'border-bottom-width':
e.YGNodeStyleSetBorder(nodeName, e.YGEdgeBottom, pointValue(e, node.style[style]));
break;
case 'width':
e.YGNodeStyleSetWidth(nodeName, pointValue(e, node.style[style]));
break;
case 'min-width':
e.YGNodeStyleSetMinWidth(nodeName, pointValue(e, node.style[style]));
break;
case 'max-width':
e.YGNodeStyleSetMaxWidth(nodeName, pointValue(e, node.style[style]));
break;
case 'height':
e.YGNodeStyleSetHeight(nodeName, pointValue(e, node.style[style]));
break;
case 'min-height':
e.YGNodeStyleSetMinHeight(nodeName, pointValue(e, node.style[style]));
break;
case 'max-height':
e.YGNodeStyleSetMaxHeight(nodeName, pointValue(e, node.style[style]));
break;
case 'display':
e.YGNodeStyleSetDisplay(nodeName, displayValue(e, node.style[style]))
break;
}
}
}
if (parentName) {
e.YGNodeInsertChild(parentName, nodeName, index);
}
for (var i = 0; i < node.children.length; i++) {
e.push('');
var childName = nodeName + '_child' + i;
setupTestTree(
e,
node,
node.children[i],
genericNode.children[i],
childName,
nodeName,
i);
}
}
function overflowValue(e, value) {
switch (value) {
case 'visible': return e.YGOverflowVisible;
case 'hidden': return e.YGOverflowHidden;
}
}
function wrapValue(e, value) {
switch (value) {
case 'wrap': return e.YGWrapWrap;
case 'wrap-reverse': return e.YGWrapWrapReverse;
case 'nowrap': return e.YGWrapNoWrap;
}
}
function flexDirectionValue(e, value) {
switch (value) {
case 'row': return e.YGFlexDirectionRow;
case 'row-reverse': return e.YGFlexDirectionRowReverse;
case 'column': return e.YGFlexDirectionColumn;
case 'column-reverse': return e.YGFlexDirectionColumnReverse;
}
}
function justifyValue(e, value) {
switch (value) {
case 'center': return e.YGJustifyCenter;
case 'space-around': return e.YGJustifySpaceAround;
case 'space-between': return e.YGJustifySpaceBetween;
case 'space-evenly': return e.YGJustifySpaceEvenly;
case 'flex-start': return e.YGJustifyFlexStart;
case 'flex-end': return e.YGJustifyFlexEnd;
}
}
function positionValue(e, value) {
switch (value) {
case 'absolute': return e.YGPositionTypeAbsolute;
default: return e.YGPositionTypeRelative
}
}
function directionValue(e, value) {
switch (value) {
case 'ltr': return e.YGDirectionLTR;
case 'rtl': return e.YGDirectionRTL;
case 'inherit': return e.YGDirectionInherit;
}
}
function alignValue(e, value) {
switch (value) {
case 'auto': return e.YGAlignAuto;
case 'center': return e.YGAlignCenter;
case 'stretch': return e.YGAlignStretch;
case 'flex-start': return e.YGAlignFlexStart;
case 'flex-end': return e.YGAlignFlexEnd;
case 'space-between': return e.YGAlignSpaceBetween;
case 'space-around': return e.YGAlignSpaceAround;
case 'baseline': return e.YGAlignBaseline;
}
}
function pointValue(e, value) {
switch (value) {
case 'auto': return e.YGAuto;
case 'undefined': return e.YGUndefined;
default: return value;
}
}
function displayValue(e, value){
switch(value){
case 'flex': return e.YGDisplayFlex;
case 'none': return e.YGDisplayNone;
}
}
var DEFAULT_STYLES = new Map();
function isDefaultStyleValue(style, value) {
let defaultStyle = DEFAULT_STYLES.get(style);
if (defaultStyle == null) {
switch (style) {
case 'position':
defaultStyle = new Set(['relative']);;
break;
case 'left':
case 'top':
case 'right':
case 'bottom':
case 'start':
case 'end':
defaultStyle = new Set(['undefined']);
break;
case 'min-height':
case 'min-width':
defaultStyle = new Set(['0', '0px', 'auto']);
break;
default:
var node = document.getElementById('default');
defaultStyle = new Set([getComputedStyle(node, null)[style]]);
break;
}
DEFAULT_STYLES.set(style, defaultStyle);
}
return DEFAULT_STYLES.get(style).has(value);
}
function getRoundedSize(node) {
var boundingRect = node.getBoundingClientRect();
return {
width: Math.round(boundingRect.right) - Math.round(boundingRect.left),
height: Math.round(boundingRect.bottom) - Math.round(boundingRect.top)
};
}
function calculateTree(root, roundToPixelGrid) {
var rootLayout = [];
for (var i = 0; i < root.children.length; i++) {
var child = root.children[i];
var layout = {
name: child.id !== '' ? child.id : 'INSERT_NAME_HERE',
left: child.offsetLeft + child.parentNode.clientLeft,
top: child.offsetTop + child.parentNode.clientTop,
width: child.offsetWidth,
height: child.offsetHeight,
children: calculateTree(child, roundToPixelGrid),
style: getYogaStyle(child),
declaredStyle: child.style,
rawStyle: child.getAttribute('style'),
experiments: child.getAttribute('experiments')
? child.getAttribute('experiments').split(' ')
: DEFAULT_EXPERIMENTS,
};
var size = getRoundedSize(child);
layout.width = size.width;
layout.height = size.height;
rootLayout.push(layout);
}
return rootLayout;
}
function getYogaStyle(node) {
// TODO: Relying on computed style means we cannot test shorthand props like
// "padding", "margin", "gap".
return [
'direction',
'flex-direction',
'justify-content',
'align-content',
'align-items',
'align-self',
'position',
'flex-wrap',
'overflow',
'flex-grow',
'flex-shrink',
'flex-basis',
'left',
'top',
'right',
'bottom',
'margin-left',
'margin-top',
'margin-right',
'margin-bottom',
'padding-left',
'padding-top',
'padding-right',
'padding-bottom',
'border-left-width',
'border-top-width',
'border-right-width',
'border-bottom-width',
'width',
'min-width',
'max-width',
'height',
'min-height',
'max-height',
'column-gap',
'row-gap',
'display',
].reduce(function(map, key) {
map[key] = node.style[key] || getComputedStyle(node, null).getPropertyValue(key);
return map;
}, {});
}
var Emitter = function(lang, indent) {
this.lang = lang;
this.indent = indent;
this.indents = [];
this.lines = [];
};
Emitter.prototype = Object.create(Object.prototype, {
constructor:{value:Emitter},
pushIndent:{value:function() {
this.indents.push(this.indent);
}},
popIndent:{value:function() {
this.indents.pop();
}},
push:{value:function(line) {
if (line instanceof Array) {
line.forEach(function(element) {
this.push(element);
}, this);
return;
} else if (line.length > 0) {
line = this.indents.join('') + line;
}
this.lines.push(line);
}},
print:{value:function() {
console.log(this.lines.join('\n'));
}},
});