/** * 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. */ window.onload = function() { checkDefaultValues(); printTest( new RustEmitter(), 'rust', 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(' ') : [], }; 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')); }}, });