From 9a0c1deae84edde28562ad3b0e558a99d52ae8ad Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Tue, 11 Jul 2023 09:31:14 -0600 Subject: [PATCH 1/4] fix(color-contrast): correctly handle flex and position --- lib/commons/dom/create-grid.js | 78 +++++++++++++-------------- lib/commons/dom/visually-sort.js | 5 ++ test/commons/dom/get-element-stack.js | 21 ++++++++ 3 files changed, 63 insertions(+), 41 deletions(-) diff --git a/lib/commons/dom/create-grid.js b/lib/commons/dom/create-grid.js index 465acdc0f2..c36eb7e069 100644 --- a/lib/commons/dom/create-grid.js +++ b/lib/commons/dom/create-grid.js @@ -10,7 +10,7 @@ import assert from '../../core/utils/assert'; const ROOT_ORDER = 0; const DEFAULT_ORDER = 0.1; const FLOAT_ORDER = 0.2; -const POSITION_STATIC_ORDER = 0.3; +export const POSITION_ORDER = 0.3; let nodeIndex = 0; /** @@ -39,7 +39,7 @@ export default function createGrid( } nodeIndex = 0; - vNode._stackingOrder = [createContext(ROOT_ORDER, null)]; + vNode._stackingOrder = [createContext(ROOT_ORDER, null, nodeIndex++)]; rootGrid ??= new Grid(); addNodeToGrid(rootGrid, vNode); @@ -240,7 +240,7 @@ function isStackingContext(vNode, parentVNode) { * @param {VirtualNode} vNode * @return {Boolean} */ -function isFlexOrGridContainer(vNode) { +export function isFlexOrGridContainer(vNode) { if (!vNode) { return false; } @@ -254,65 +254,61 @@ function isFlexOrGridContainer(vNode) { * zIndex values for each stacking context parent. * @param {VirtualNode} vNode * @param {VirtualNode} parentVNode - * @param {Number} nodeIndex + * @param {Number} index * @return {Number[]} */ -function createStackingOrder(vNode, parentVNode, nodeIndex) { +function createStackingOrder(vNode, parentVNode, index) { const stackingOrder = parentVNode._stackingOrder.slice(); - // if a positioned element has z-index: auto or 0 (step #8), or if - // a non-positioned floating element (step #5), treat it as its - // own stacking context - // @see https://www.w3.org/Style/css2-updates/css2/zindex.html - if (!isStackingContext(vNode, parentVNode)) { - if (vNode.getComputedStylePropertyValue('position') !== 'static') { - // Put positioned elements above floated elements - stackingOrder.push(createContext(POSITION_STATIC_ORDER, vNode)); - } else if (vNode.getComputedStylePropertyValue('float') !== 'none') { - // Put floated elements above z-index: 0 - // (step #5 floating get sorted below step #8 positioned) - stackingOrder.push(createContext(FLOAT_ORDER, vNode)); - } - return stackingOrder; - } - // if an element creates a stacking context, find the first // true stack (not a "fake" stack created from positioned or // floated elements without a z-index) and create a new stack at // that point (step #5 and step #8) // @see https://www.w3.org/Style/css2-updates/css2/zindex.html - const index = stackingOrder.findIndex(({ value }) => - [ROOT_ORDER, FLOAT_ORDER, POSITION_STATIC_ORDER].includes(value) - ); - if (index !== -1) { - stackingOrder.splice(index, stackingOrder.length - index); + if (isStackingContext(vNode, parentVNode)) { + const index = stackingOrder.findIndex(({ value }) => + [ROOT_ORDER, FLOAT_ORDER, POSITION_ORDER].includes(value) + ); + if (index !== -1) { + stackingOrder.splice(index, stackingOrder.length - index); + } } const zIndex = getRealZIndex(vNode, parentVNode); if (!['auto', '0'].includes(zIndex)) { - stackingOrder.push(createContext(parseInt(zIndex), vNode)); + stackingOrder.push(createContext(parseInt(zIndex), vNode, index)); + return stackingOrder; + } + + // if a positioned element has z-index: auto or 0 (step #8), or if + // a non-positioned floating element (step #5), treat it as its + // own stacking context + // @see https://www.w3.org/Style/css2-updates/css2/zindex.html + if (vNode.getComputedStylePropertyValue('position') !== 'static') { + // Put positioned elements above floated elements + stackingOrder.push(createContext(POSITION_ORDER, vNode, index)); + return stackingOrder; + } + + if (vNode.getComputedStylePropertyValue('float') !== 'none') { + // Put floated elements above z-index: 0 + // (step #5 floating get sorted below step #8 positioned) + stackingOrder.push(createContext(FLOAT_ORDER, vNode, index)); + return stackingOrder; + } + + if (!isStackingContext(vNode, parentVNode)) { return stackingOrder; } - // since many things can create a new stacking context without position or - // z-index, we need to know the order in the dom to sort them by. Use the - // nodeIndex property to create a number less than the "fake" stacks from - // positioned or floated elements but still larger than 0 - // 10 pad gives us the ability to sort up to 1B nodes (padStart does not - // exist in ie11) - let float = nodeIndex.toString(); - while (float.length < 10) { - float = '0' + float; - } - stackingOrder.push( - createContext(parseFloat(`${DEFAULT_ORDER}${float}`), vNode) - ); + stackingOrder.push(createContext(DEFAULT_ORDER, vNode, index)); return stackingOrder; } -function createContext(value, vNode) { +function createContext(value, vNode, index) { return { value, + nodeIndex: index, vNode }; } diff --git a/lib/commons/dom/visually-sort.js b/lib/commons/dom/visually-sort.js index 1ceaf8245e..cc38cbc472 100644 --- a/lib/commons/dom/visually-sort.js +++ b/lib/commons/dom/visually-sort.js @@ -27,6 +27,11 @@ export default function visuallySort(a, b) { if (b._stackingOrder[i].value < a._stackingOrder[i].value) { return -1; } + + // stacks share the same stacking context value so compare document order + if (b._stackingOrder[i].nodeIndex !== a._stackingOrder[i].nodeIndex) { + return b._stackingOrder[i].nodeIndex - a._stackingOrder[i].nodeIndex; + } } // nodes are the same stacking order diff --git a/test/commons/dom/get-element-stack.js b/test/commons/dom/get-element-stack.js index df466da016..2122f147b0 100644 --- a/test/commons/dom/get-element-stack.js +++ b/test/commons/dom/get-element-stack.js @@ -465,6 +465,27 @@ describe('dom.getElementStack', () => { assert.deepEqual(stack, []); }); + it('should correctly position children of different stacking contexts', () => { + fixture.innerHTML = + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + 'Hello World' + + '
' + + '
' + + '
' + + '
'; + + axe.testUtils.flatTreeSetup(fixture); + const target = fixture.querySelector('#target'); + const stack = mapToIDs(getElementStack(target)); + assert.deepEqual(stack, ['target', '6', '5', '4', '3', '2', '1']); + }); + it('should throw error if element midpoint-x exceeds the grid', () => { fixture.innerHTML = '
Hello World
'; axe.testUtils.flatTreeSetup(fixture); From 171656a33c05c72cbe319e136e9d9f445ea6b108 Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Tue, 11 Jul 2023 09:32:32 -0600 Subject: [PATCH 2/4] typos --- lib/commons/dom/create-grid.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/commons/dom/create-grid.js b/lib/commons/dom/create-grid.js index c36eb7e069..2376f21ec3 100644 --- a/lib/commons/dom/create-grid.js +++ b/lib/commons/dom/create-grid.js @@ -10,7 +10,7 @@ import assert from '../../core/utils/assert'; const ROOT_ORDER = 0; const DEFAULT_ORDER = 0.1; const FLOAT_ORDER = 0.2; -export const POSITION_ORDER = 0.3; +const POSITION_ORDER = 0.3; let nodeIndex = 0; /** @@ -240,7 +240,7 @@ function isStackingContext(vNode, parentVNode) { * @param {VirtualNode} vNode * @return {Boolean} */ -export function isFlexOrGridContainer(vNode) { +function isFlexOrGridContainer(vNode) { if (!vNode) { return false; } From bd494d4433cea5ba36a5382062110c9252952a9f Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Tue, 11 Jul 2023 15:49:09 -0600 Subject: [PATCH 3/4] indentation --- test/commons/dom/get-element-stack.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/test/commons/dom/get-element-stack.js b/test/commons/dom/get-element-stack.js index 2122f147b0..3b045f7874 100644 --- a/test/commons/dom/get-element-stack.js +++ b/test/commons/dom/get-element-stack.js @@ -466,19 +466,20 @@ describe('dom.getElementStack', () => { }); it('should correctly position children of different stacking contexts', () => { - fixture.innerHTML = - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - 'Hello World' + - '
' + - '
' + - '
' + - '
'; + fixture.innerHTML = ` +
+
+
+
+
+
+
+ Hello World +
+
+
+
+ `; axe.testUtils.flatTreeSetup(fixture); const target = fixture.querySelector('#target'); From 0807e8f4e65792ef9df66584af31bc2934bdda7e Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Wed, 12 Jul 2023 12:18:01 -0600 Subject: [PATCH 4/4] tests --- lib/commons/dom/create-grid.js | 90 +++++++++++++------- lib/commons/dom/visually-sort.js | 12 +-- test/commons/dom/create-grid.js | 131 +++++++++++++++++++++--------- test/commons/dom/visually-sort.js | 113 +++++++++++++++++++++++--- 4 files changed, 260 insertions(+), 86 deletions(-) diff --git a/lib/commons/dom/create-grid.js b/lib/commons/dom/create-grid.js index 2376f21ec3..14eac3f9d4 100644 --- a/lib/commons/dom/create-grid.js +++ b/lib/commons/dom/create-grid.js @@ -7,10 +7,10 @@ import constants from '../../core/constants'; import cache from '../../core/base/cache'; import assert from '../../core/utils/assert'; -const ROOT_ORDER = 0; -const DEFAULT_ORDER = 0.1; -const FLOAT_ORDER = 0.2; -const POSITION_ORDER = 0.3; +const ROOT_LEVEL = 0; +const DEFAULT_LEVEL = 0.1; +const FLOAT_LEVEL = 0.2; +const POSITION_LEVEL = 0.3; let nodeIndex = 0; /** @@ -39,7 +39,9 @@ export default function createGrid( } nodeIndex = 0; - vNode._stackingOrder = [createContext(ROOT_ORDER, null, nodeIndex++)]; + vNode._stackingOrder = [ + createStackingContext(ROOT_LEVEL, nodeIndex++, null) + ]; rootGrid ??= new Grid(); addNodeToGrid(rootGrid, vNode); @@ -251,13 +253,13 @@ function isFlexOrGridContainer(vNode) { /** * Determine the stacking order of an element. The stacking order is an array of - * zIndex values for each stacking context parent. + * stacking contexts in ancestor order. * @param {VirtualNode} vNode * @param {VirtualNode} parentVNode - * @param {Number} index + * @param {Number} treeOrder * @return {Number[]} */ -function createStackingOrder(vNode, parentVNode, index) { +function createStackingOrder(vNode, parentVNode, treeOrder) { const stackingOrder = parentVNode._stackingOrder.slice(); // if an element creates a stacking context, find the first @@ -266,53 +268,79 @@ function createStackingOrder(vNode, parentVNode, index) { // that point (step #5 and step #8) // @see https://www.w3.org/Style/css2-updates/css2/zindex.html if (isStackingContext(vNode, parentVNode)) { - const index = stackingOrder.findIndex(({ value }) => - [ROOT_ORDER, FLOAT_ORDER, POSITION_ORDER].includes(value) + const index = stackingOrder.findIndex(({ stackLevel }) => + [ROOT_LEVEL, FLOAT_LEVEL, POSITION_LEVEL].includes(stackLevel) ); if (index !== -1) { stackingOrder.splice(index, stackingOrder.length - index); } } + const stackLevel = getStackLevel(vNode, parentVNode); + if (stackLevel !== null) { + stackingOrder.push(createStackingContext(stackLevel, treeOrder, vNode)); + } + return stackingOrder; +} + +/** + * Create a stacking context, keeping track of the stack level, tree order, and virtual + * node container. + * @see https://www.w3.org/Style/css2-updates/css2/zindex.html + * @see https://www.w3.org/Style/css2-updates/css2/visuren.html#layers + * @param {Number} stackLevel - The stack level of the stacking context + * @param {Number} treeOrder - The elements depth-first traversal order + * @param {VirtualNode} vNode - The virtual node that is the container for the stacking context + */ +function createStackingContext(stackLevel, treeOrder, vNode) { + return { + stackLevel, + treeOrder, + vNode + }; +} + +/** + * Calculate the level of the stacking context. + * @param {VirtualNode} vNode - The virtual node container of the stacking context + * @param {VirtualNode} parentVNode - The parent virtual node of the vNode + * @return {Number|null} + */ +function getStackLevel(vNode, parentVNode) { const zIndex = getRealZIndex(vNode, parentVNode); if (!['auto', '0'].includes(zIndex)) { - stackingOrder.push(createContext(parseInt(zIndex), vNode, index)); - return stackingOrder; + return parseInt(zIndex); } // if a positioned element has z-index: auto or 0 (step #8), or if // a non-positioned floating element (step #5), treat it as its // own stacking context // @see https://www.w3.org/Style/css2-updates/css2/zindex.html + + // Put positioned elements above floated elements if (vNode.getComputedStylePropertyValue('position') !== 'static') { - // Put positioned elements above floated elements - stackingOrder.push(createContext(POSITION_ORDER, vNode, index)); - return stackingOrder; + return POSITION_LEVEL; } + // Put floated elements above z-index: 0 + // (step #5 floating get sorted below step #8 positioned) if (vNode.getComputedStylePropertyValue('float') !== 'none') { - // Put floated elements above z-index: 0 - // (step #5 floating get sorted below step #8 positioned) - stackingOrder.push(createContext(FLOAT_ORDER, vNode, index)); - return stackingOrder; + return FLOAT_LEVEL; } - if (!isStackingContext(vNode, parentVNode)) { - return stackingOrder; + if (isStackingContext(vNode, parentVNode)) { + return DEFAULT_LEVEL; } - stackingOrder.push(createContext(DEFAULT_ORDER, vNode, index)); - return stackingOrder; -} - -function createContext(value, vNode, index) { - return { - value, - nodeIndex: index, - vNode - }; + return null; } +/** + * Calculate the z-index value of a node taking into account when doesn't apply. + * @param {VirtualNode} vNode - The virtual node to get z-index of + * @param {VirtualNode} parentVNode - The parent virtual node of the vNode + * @return {Number|'auto'} + */ function getRealZIndex(vNode, parentVNode) { const position = vNode.getComputedStylePropertyValue('position'); if (position === 'static' && !isFlexOrGridContainer(parentVNode)) { diff --git a/lib/commons/dom/visually-sort.js b/lib/commons/dom/visually-sort.js index cc38cbc472..ee5a0c905f 100644 --- a/lib/commons/dom/visually-sort.js +++ b/lib/commons/dom/visually-sort.js @@ -1,8 +1,10 @@ import createGrid from './create-grid'; + /** * Visually sort nodes based on their stack order * References: * https://www.w3.org/Style/css2-updates/css2/zindex.html + * https://www.w3.org/Style/css2-updates/css2/visuren.html#layers * @param {VirtualNode} * @param {VirtualNode} */ @@ -19,18 +21,18 @@ export default function visuallySort(a, b) { } // 7. the child stacking contexts with positive stack levels (least positive first). - if (b._stackingOrder[i].value > a._stackingOrder[i].value) { + if (b._stackingOrder[i].stackLevel > a._stackingOrder[i].stackLevel) { return 1; } // 2. the child stacking contexts with negative stack levels (most negative first). - if (b._stackingOrder[i].value < a._stackingOrder[i].value) { + if (b._stackingOrder[i].stackLevel < a._stackingOrder[i].stackLevel) { return -1; } - // stacks share the same stacking context value so compare document order - if (b._stackingOrder[i].nodeIndex !== a._stackingOrder[i].nodeIndex) { - return b._stackingOrder[i].nodeIndex - a._stackingOrder[i].nodeIndex; + // stacks share the same stack level so compare document order + if (b._stackingOrder[i].treeOrder !== a._stackingOrder[i].treeOrder) { + return b._stackingOrder[i].treeOrder - a._stackingOrder[i].treeOrder; } } diff --git a/test/commons/dom/create-grid.js b/test/commons/dom/create-grid.js index 5b344d3a55..8068acd276 100644 --- a/test/commons/dom/create-grid.js +++ b/test/commons/dom/create-grid.js @@ -1,13 +1,14 @@ // Additional tests for createGrid are part of createRectStack tests, // which is what createGrid was originally part of -describe('create-grid', function () { - var fixture; - var createGrid = axe.commons.dom.createGrid; - var fixtureSetup = axe.testUtils.fixtureSetup; - var gridSize = axe.constants.gridSize; +describe('create-grid', () => { + let fixture; + const createGrid = axe.commons.dom.createGrid; + const fixtureSetup = axe.testUtils.fixtureSetup; + const queryFixture = axe.testUtils.queryFixture; + const gridSize = axe.constants.gridSize; function findPositions(grid, vNode) { - var positions = []; + const positions = []; grid.loopGridPosition(grid.boundaries, (cell, position) => { if (cell.includes(vNode)) { positions.push(position); @@ -16,12 +17,12 @@ describe('create-grid', function () { return positions; } - it('returns the grid size', function () { + it('returns the grid size', () => { axe.setup(); assert.equal(createGrid(), axe.constants.gridSize); }); - it('sets ._grid to nodes in the grid', function () { + it('sets ._grid to nodes in the grid', () => { fixture = fixtureSetup('Hello world'); assert.isUndefined(fixture._grid); assert.isUndefined(fixture.children[0]._grid); @@ -31,21 +32,21 @@ describe('create-grid', function () { assert.equal(fixture._grid, fixture.children[0]._grid); }); - it('adds elements to the correct cell in the grid', function () { + it('adds elements to the correct cell in the grid', () => { fixture = fixtureSetup('Hello world'); createGrid(); - var positions = findPositions(fixture._grid, fixture.children[0]); + const positions = findPositions(fixture._grid, fixture.children[0]); assert.deepEqual(positions, [{ col: 0, row: 0 }]); }); - it('adds large elements to multiple cell', function () { + it('adds large elements to multiple cell', () => { fixture = fixtureSetup( '' + 'Hello world' ); createGrid(); - var positions = findPositions(fixture._grid, fixture.children[0]); + const positions = findPositions(fixture._grid, fixture.children[0]); assert.deepEqual(positions, [ { col: 0, row: 0 }, { col: 1, row: 0 }, @@ -54,38 +55,94 @@ describe('create-grid', function () { ]); }); - describe('hidden elements', function () { - beforeEach(function () { + describe('stackingOrder', () => { + it('adds stacking context information', () => { + fixture = fixtureSetup('Hello world'); + createGrid(); + const vNode = fixture.children[0]; + assert.lengthOf(vNode._stackingOrder, 1); + // purposefully do not test stackLevel and treeOrder values as they are + // implementation details that can easily change + assert.hasAllKeys(vNode._stackingOrder[0], [ + 'stackLevel', + 'treeOrder', + 'vNode' + ]); + assert.typeOf(vNode._stackingOrder[0].stackLevel, 'number'); + assert.typeOf(vNode._stackingOrder[0].treeOrder, 'number'); + assert.isNull(vNode._stackingOrder[0].vNode); // root stack + }); + + // when to use z-index values can be tested though + it('sets the stack level equal to the z-index of positioned elements', () => { + const vNode = queryFixture( + '
Hello world
' + ); + createGrid(); + assert.equal(vNode._stackingOrder[0].stackLevel, 100); + }); + + it("ignores z-index on elements that aren't positioned", () => { + const vNode = queryFixture( + '
Hello world
' + ); + createGrid(); + assert.notEqual(vNode._stackingOrder[0].stackLevel, 100); + }); + + it('uses z-index on children of flex elements', () => { + const vNode = queryFixture( + '
Hello world
' + ); + createGrid(); + assert.equal(vNode._stackingOrder[0].stackLevel, 100); + }); + + it('creates multiple stacking context when they are nested', () => { + const vNode = queryFixture(` +
+
+
Hello world
+
+
+ `); + createGrid(); + assert.lengthOf(vNode._stackingOrder, 2); + }); + }); + + describe('hidden elements', () => { + beforeEach(() => { // Ensure the fixture itself is part of the grid, even if its content isn't document .querySelector('#fixture') .setAttribute('style', 'min-height: 10px'); }); - it('does not add hidden elements', function () { + it('does not add hidden elements', () => { fixture = fixtureSetup('
hidden
'); createGrid(); - var position = findPositions(fixture._grid, fixture.children[0]); + const position = findPositions(fixture._grid, fixture.children[0]); assert.isEmpty(position); assert.isUndefined(fixture.children[0]._grid); }); - it('does not add off screen elements', function () { + it('does not add off screen elements', () => { fixture = fixtureSetup( '
off screen
' ); createGrid(); - var position = findPositions(fixture._grid, fixture.children[0]); + const position = findPositions(fixture._grid, fixture.children[0]); assert.isEmpty(position); assert.isUndefined(fixture.children[0]._grid); }); - it('does add partially on screen elements', function () { + it('does add partially on screen elements', () => { fixture = fixtureSetup( '
off screen
' ); createGrid(); - var position = findPositions(fixture._grid, fixture.children[0]); + const position = findPositions(fixture._grid, fixture.children[0]); assert.deepEqual(position, [ { col: 0, row: -1 }, { col: 0, row: 0 } @@ -102,7 +159,7 @@ describe('create-grid', function () { document.body.removeAttribute('style'); }); - it('adds elements vertically scrolled out of view', function () { + it('adds elements vertically scrolled out of view', () => { const gridScroll = 2; fixture = fixtureSetup(`
@@ -121,12 +178,12 @@ describe('create-grid', function () { createGrid(); childElms.forEach((child, index) => { assert.isDefined(child._grid, `Expect child ${index} to be defined`); - var position = findPositions(child._grid, child); + const position = findPositions(child._grid, child); assert.deepEqual(position, [{ col: 0, row: index - gridScroll }]); }); }); - it('adds elements horizontally scrolled out of view', function () { + it('adds elements horizontally scrolled out of view', () => { const gridScroll = 2; fixture = fixtureSetup(`
@@ -147,60 +204,60 @@ describe('create-grid', function () { createGrid(); childElms.forEach((child, index) => { assert.isDefined(child._grid, `Expect child ${index} to be defined`); - var position = findPositions(child._grid, child); + const position = findPositions(child._grid, child); assert.deepEqual(position, [{ col: index - gridScroll, row: 0 }]); }); }); }); - describe('subGrids', function () { - it('sets the .subGrid property', function () { + describe('subGrids', () => { + it('sets the .subGrid property', () => { fixture = fixtureSetup( '
' + 'x' + '
' ); - var vOverflow = fixture.children[0]; + const vOverflow = fixture.children[0]; assert.isUndefined(vOverflow._subGrid); createGrid(); assert.isDefined(vOverflow._subGrid); assert.notEqual(vOverflow._grid, vOverflow._subGrid); }); - it('sets the ._grid of children as the subGrid', function () { + it('sets the ._grid of children as the subGrid', () => { fixture = fixtureSetup( '
' + 'x' + '
' ); createGrid(); - var vOverflow = fixture.children[0]; - var vSpan = vOverflow.children[0]; + const vOverflow = fixture.children[0]; + const vSpan = vOverflow.children[0]; assert.equal(vOverflow._subGrid, vSpan._grid); }); - it('does not add scrollable children to the root grid', function () { + it('does not add scrollable children to the root grid', () => { fixture = fixtureSetup( '
' + 'x' + '
' ); createGrid(); - var vSpan = fixture.children[0].children[0]; - var position = findPositions(fixture._grid, vSpan); + const vSpan = fixture.children[0].children[0]; + const position = findPositions(fixture._grid, vSpan); assert.isEmpty(position); }); - it('adds scrollable children to the subGrid', function () { + it('adds scrollable children to the subGrid', () => { fixture = fixtureSetup( '
' + 'x' + '
' ); createGrid(); - var vOverflow = fixture.children[0]; - var vSpan = vOverflow.children[0]; - var position = findPositions(vOverflow._subGrid, vSpan); + const vOverflow = fixture.children[0]; + const vSpan = vOverflow.children[0]; + const position = findPositions(vOverflow._subGrid, vSpan); assert.deepEqual(position, [ { col: 0, row: 0 }, { col: 0, row: 1 } diff --git a/test/commons/dom/visually-sort.js b/test/commons/dom/visually-sort.js index 1b943d30f4..897f614215 100644 --- a/test/commons/dom/visually-sort.js +++ b/test/commons/dom/visually-sort.js @@ -1,21 +1,108 @@ // This method is mostly tested through color-contrast integrations -describe('visually-sort', function () { +describe('visually-sort', () => { 'use strict'; - var fixtureSetup = axe.testUtils.fixtureSetup; - var visuallySort = axe.commons.dom.visuallySort; + const fixture = document.querySelector('#fixture'); + const visuallySort = axe.commons.dom.visuallySort; + const querySelectorAll = axe.utils.querySelectorAll; + let root; - it('returns 1 if B overlaps A', function () { - var rootNode = fixtureSetup('bar'); - var vNodeA = rootNode.children[0]; - var vNodeB = vNodeA.children[0]; - assert.equal(visuallySort(vNodeA, vNodeB), 1); + beforeEach(() => { + fixture.innerHTML = ` +
+
+
+
+
+
+
+
+
Text
+
Text
+ Text +
Text
+
+
+
+
+ `; + const shadowRoot = fixture + .querySelector('#shadow-host') + .attachShadow({ mode: 'open' }); + shadowRoot.innerHTML = '
Text
'; + root = axe.setup(fixture); }); - it('returns -1 if A overlaps B', function () { - var rootNode = fixtureSetup('bar'); - var vNodeB = rootNode.children[0]; - var vNodeA = vNodeB.children[0]; - assert.equal(visuallySort(vNodeA, vNodeB), -1); + /* + Array.sort() return meanings: + + compareFn(a, b) | return value sort order + ----------------|----------------------- + > 0 | sort a after b, e.g. [b, a] + < 0 | sort a before b, e.g. [a, b] + === 0 | keep original order of a and b + */ + + it('sorts a higher stack before a lower stack', () => { + const vNodeA = querySelectorAll(root, '#1')[0]; + const vNodeB = querySelectorAll(root, '#4')[0]; + + assert.isBelow(visuallySort(vNodeA, vNodeB), 0); + }); + + it('sorts a lower stack after a higher stack', () => { + const vNodeA = querySelectorAll(root, '#4')[0]; + const vNodeB = querySelectorAll(root, '#1')[0]; + + assert.isAbove(visuallySort(vNodeA, vNodeB), 0); + }); + + it('sorts a child stack before a parent stack', () => { + const vNodeA = querySelectorAll(root, '#6')[0]; + const vNodeB = querySelectorAll(root, '#4')[0]; + + assert.isBelow(visuallySort(vNodeA, vNodeB), 0); + }); + + it('sorts a parent stack after a child stack', () => { + const vNodeA = querySelectorAll(root, '#4')[0]; + const vNodeB = querySelectorAll(root, '#6')[0]; + + assert.isAbove(visuallySort(vNodeA, vNodeB), 0); + }); + + it('sorts a child of a higher stack before a child of a lower stack', () => { + const vNodeA = querySelectorAll(root, '#3')[0]; + const vNodeB = querySelectorAll(root, '#7')[0]; + + assert.isBelow(visuallySort(vNodeA, vNodeB), 0); + }); + + it('sorts a child of a lower stack after a child of a higher stack', () => { + const vNodeA = querySelectorAll(root, '#7')[0]; + const vNodeB = querySelectorAll(root, '#3')[0]; + + assert.isAbove(visuallySort(vNodeA, vNodeB), 0); + }); + + it('sorts elements by tree order when in the same stack', () => { + const vNodeA = querySelectorAll(root, '#8')[0]; + const vNodeB = querySelectorAll(root, '#10')[0]; + + assert.isAbove(visuallySort(vNodeA, vNodeB), 0); + }); + + it('sorts floated elements before other elements of the same stack', () => { + const vNodeA = querySelectorAll(root, '#7')[0]; + const vNodeB = querySelectorAll(root, '#8')[0]; + + assert.isBelow(visuallySort(vNodeA, vNodeB), 0); + }); + + it('sorts shadow DOM elements by tree order when in the same stack', () => { + const vNodeA = querySelectorAll(root, '#8')[0]; + const vNodeB = querySelectorAll(root, '#shadow-host')[0].children[0]; + + assert.isAbove(visuallySort(vNodeA, vNodeB), 0); }); });