UNPKG

chrome-devtools-frontend

Version:
512 lines (453 loc) • 18.2 kB
// Copyright 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import type {PathCommands, Position, Quad} from './common.js'; import { distance, getColinearPointAtDistance, getGapQuadBetweenQuads, getGapQuads, getLinesAndItemsQuads, growQuadToEdgesOf, intersectSegments, segmentContains, uniteQuads, } from './highlight_flex_common.js'; function createPathCommands(...points: number[]): PathCommands { if (points.length !== 8) { throw new Error('Expected 8 coordinates to describe the element'); } const path: PathCommands = ['M']; for (let i = 0; i < points.length; i += 2) { path.push(points[i]); path.push(points[i + 1]); path.push('L'); } path[path.length - 1] = 'Z'; return path; } function createItem(...points: number[]): {itemBorder: PathCommands, baseline: number} { return { itemBorder: createPathCommands(...points), baseline: 0, }; } function createQuad(...points: number[]): Quad { if (points.length !== 8) { throw new Error('Expected 8 coordinates to describe the element'); } return { p1: {x: points[0], y: points[1]}, p2: {x: points[2], y: points[3]}, p3: {x: points[4], y: points[5]}, p4: {x: points[6], y: points[7]}, }; } describe('getLinesAndItemsQuads', () => { it('creates the right number of line and item quads', () => { const lineQuads = getLinesAndItemsQuads( createPathCommands(0, 0, 100, 0, 100, 100, 0, 100), [ [ createItem(10, 10, 30, 10, 30, 30, 10, 30), createItem(40, 10, 60, 10, 60, 30, 40, 30), createItem(70, 10, 90, 10, 90, 30, 70, 30), ], [ createItem(10, 40, 70, 40, 70, 70, 10, 70), createItem(80, 40, 90, 40, 90, 70, 80, 70), ], [ createItem(10, 80, 40, 80, 40, 90, 10, 90), createItem(50, 80, 90, 80, 90, 90, 50, 90), ], ], true, false); assert.lengthOf(lineQuads, 3, '3 line quads got created'); assert.lengthOf(lineQuads[0].items, 3, '3 flex items on the first line got created'); assert.lengthOf(lineQuads[1].items, 2, '2 flex items on the second line got created'); assert.lengthOf(lineQuads[2].items, 2, '2 flex items on the third line got created'); }); it('creates a line quad as big as the container when there is only one line', () => { const lineQuads = getLinesAndItemsQuads( createPathCommands(0, 0, 10, 0, 10, 10, 0, 10), [[createItem(2, 2, 8, 2, 8, 8, 2, 8)]], true, false); assert.deepEqual(lineQuads[0].quad.p1, {x: 0, y: 0}); assert.deepEqual(lineQuads[0].quad.p2, {x: 10, y: 0}); assert.deepEqual(lineQuads[0].quad.p3, {x: 10, y: 10}); assert.deepEqual(lineQuads[0].quad.p4, {x: 0, y: 10}); }); it('creates quads for flex lines that extend to the edges of the container in the main direction', () => { const lineQuadsRowDirection = getLinesAndItemsQuads( createPathCommands(0, 0, 100, 0, 100, 80, 0, 80), [ [ createItem(10, 10, 30, 10, 30, 30, 10, 30), createItem(40, 10, 60, 10, 60, 30, 40, 30), createItem(70, 10, 90, 10, 90, 30, 70, 30), ], [ createItem(10, 40, 70, 40, 70, 70, 10, 70), createItem(80, 40, 90, 40, 90, 70, 80, 70), ], ], true, false); assert.deepEqual(lineQuadsRowDirection[0].quad, createQuad(0, 10, 100, 10, 100, 30, 0, 30)); assert.deepEqual(lineQuadsRowDirection[1].quad, createQuad(0, 40, 100, 40, 100, 70, 0, 70)); const lineQuadsColumnDirection = getLinesAndItemsQuads( createPathCommands(0, 0, 50, 0, 50, 70, 0, 70), [ [ createItem(10, 10, 20, 10, 20, 30, 10, 30), createItem(10, 40, 20, 40, 20, 50, 10, 50), ], [ createItem(30, 20, 40, 20, 40, 40, 30, 40), createItem(30, 50, 40, 50, 40, 60, 30, 60), ], ], false, false); assert.deepEqual(lineQuadsColumnDirection[0].quad, createQuad(10, 0, 20, 0, 20, 70, 10, 70)); assert.deepEqual(lineQuadsColumnDirection[1].quad, createQuad(30, 0, 40, 0, 40, 70, 30, 70)); }); it('creates normal and extended quads for items', () => { const lineQuads = getLinesAndItemsQuads( createPathCommands(0, 0, 70, 0, 70, 40, 0, 40), [ [ createItem(10, 10, 30, 10, 30, 30, 10, 30), createItem(40, 10, 60, 10, 60, 30, 40, 30), ], ], true, false); assert.deepEqual( lineQuads[0].items[0], createQuad(10, 10, 30, 10, 30, 30, 10, 30), 'The first flex item quad matches the item'); assert.deepEqual( lineQuads[0].items[1], createQuad(40, 10, 60, 10, 60, 30, 40, 30), 'The second flex item quad matches the item'); assert.deepEqual( lineQuads[0].extendedItems[0], createQuad(10, 0, 30, 0, 30, 40, 10, 40), 'The first flex item extended quad extends to the cross edge of the flex line'); assert.deepEqual( lineQuads[0].extendedItems[1], createQuad(40, 0, 60, 0, 60, 40, 40, 40), 'The second flex item extended quad extends to the cross edge of the flex line'); }); it('creates correct quads with transformed layout', () => { const lineQuads = getLinesAndItemsQuads( createPathCommands(10, 70, 70, 10, 110, 50, 50, 110), [ [ createItem(30, 70, 50, 50, 60, 60, 40, 80), createItem(60, 40, 70, 30, 80, 40, 70, 50), ], [ createItem(40, 80, 50, 70, 60, 80, 50, 90), createItem(50, 70, 80, 40, 90, 50, 60, 80), ], ], true, false); assert.deepEqual(lineQuads[0].quad, createQuad(20, 80, 80, 20, 90, 30, 30, 90)); assert.deepEqual(lineQuads[0].extendedItems[0], createQuad(30, 70, 50, 50, 60, 60, 40, 80)); assert.deepEqual(lineQuads[0].extendedItems[1], createQuad(60, 40, 70, 30, 80, 40, 70, 50)); assert.deepEqual(lineQuads[1].quad, createQuad(30, 90, 90, 30, 100, 40, 40, 100)); assert.deepEqual(lineQuads[1].extendedItems[0], createQuad(40, 80, 50, 70, 60, 80, 50, 90)); assert.deepEqual(lineQuads[1].extendedItems[1], createQuad(50, 70, 80, 40, 90, 50, 60, 80)); }); }); describe('getGapQuads', () => { it('does not return any cross gap if there is only 1 line', () => { const {crossGaps} = getGapQuads( { crossGap: 10, mainGap: 10, isHorizontalFlow: true, isReverse: false, }, [{ quad: createQuad(0, 0, 100, 0, 100, 100, 0, 100), items: [], extendedItems: [], }]); assert.lengthOf(crossGaps, 0, 'There cannot be cross gap if there is only one line'); }); it('does not return any main or cross gap if there actually isn\'t any gaps', () => { const {crossGaps, mainGaps} = getGapQuads( { crossGap: 0, mainGap: 0, isHorizontalFlow: true, isReverse: false, }, [ { quad: createQuad(0, 10, 100, 10, 100, 30, 0, 30), items: [ createQuad(10, 10, 30, 10, 30, 30, 10, 30), createQuad(40, 10, 60, 10, 60, 30, 30, 40), createQuad(70, 10, 90, 10, 90, 30, 70, 30), ], extendedItems: [ createQuad(10, 10, 30, 10, 30, 30, 10, 30), createQuad(40, 10, 60, 10, 60, 30, 30, 40), createQuad(70, 10, 90, 10, 90, 30, 70, 30), ], }, { quad: createQuad(0, 40, 100, 40, 100, 70, 70, 0), items: [ createQuad(10, 40, 70, 40, 70, 70, 10, 70), createQuad(80, 40, 90, 40, 90, 70, 80, 70), ], extendedItems: [ createQuad(10, 40, 70, 40, 70, 70, 10, 70), createQuad(80, 40, 90, 40, 90, 70, 80, 70), ], }, ]); assert.lengthOf(crossGaps, 0, 'No cross gap quads created when there is no cross gap'); assert.lengthOf(mainGaps[0], 0, 'No main gap quads created when there is no main gap on the first line'); assert.lengthOf(mainGaps[1], 0, 'No main gap quads created when there is no main gap on the second line'); }); it('returns 1 less gap than the number of lines and the number of items', () => { const {crossGaps, mainGaps} = getGapQuads( { crossGap: 10, mainGap: 10, isHorizontalFlow: true, isReverse: false, }, [ { quad: createQuad(0, 10, 100, 10, 100, 30, 0, 30), items: [ createQuad(10, 10, 30, 10, 30, 30, 10, 30), createQuad(40, 10, 60, 10, 60, 30, 30, 40), createQuad(70, 10, 90, 10, 90, 30, 70, 30), ], extendedItems: [ createQuad(10, 10, 30, 10, 30, 30, 10, 30), createQuad(40, 10, 60, 10, 60, 30, 30, 40), createQuad(70, 10, 90, 10, 90, 30, 70, 30), ], }, { quad: createQuad(0, 40, 100, 40, 100, 70, 70, 0), items: [ createQuad(10, 40, 70, 40, 70, 70, 10, 70), createQuad(80, 40, 90, 40, 90, 70, 80, 70), ], extendedItems: [ createQuad(10, 40, 70, 40, 70, 70, 10, 70), createQuad(80, 40, 90, 40, 90, 70, 80, 70), ], }, { quad: createQuad(0, 80, 100, 80, 100, 90, 90, 0), items: [ createQuad(10, 80, 40, 80, 40, 90, 10, 90), createQuad(50, 80, 90, 80, 90, 90, 50, 90), ], extendedItems: [ createQuad(10, 80, 40, 80, 40, 90, 10, 90), createQuad(50, 80, 90, 80, 90, 90, 50, 90), ], }, ]); assert.lengthOf(crossGaps, 2, 'There are 2 cross gaps for 3 lines'); assert.lengthOf(mainGaps[0], 2, 'There are 2 main gaps on the first line, which has 3 items'); assert.lengthOf(mainGaps[1], 1, 'There is 1 main gap on the second line, which has 2 items'); assert.lengthOf(mainGaps[2], 1, 'There is 1 main gap on the third line, which has 2 items'); }); }); describe('getGapQuadBetweenQuads', () => { it('creates a quad between 2 quads stacked either vertically or horizontally, also when reversed', () => { const quadV = getGapQuadBetweenQuads( createQuad(0, 0, 60, 0, 60, 10, 0, 10), createQuad(0, 20, 60, 20, 60, 30, 0, 30), 10, true, false, ); assert.deepEqual(quadV, createQuad(0, 10, 60, 10, 60, 20, 0, 20)); const quadVReversed = getGapQuadBetweenQuads( createQuad(0, 20, 60, 20, 60, 30, 0, 30), createQuad(0, 0, 60, 0, 60, 10, 0, 10), 10, true, true, ); assert.deepEqual(quadVReversed, quadV); const quadH = getGapQuadBetweenQuads( createQuad(0, 0, 10, 0, 10, 50, 50, 0), createQuad(20, 0, 30, 0, 30, 50, 50, 20), 10, false, false, ); assert.deepEqual(quadH, createQuad(10, 0, 20, 0, 20, 50, 10, 50)); const quadHReversed = getGapQuadBetweenQuads( createQuad(20, 0, 30, 0, 30, 50, 50, 20), createQuad(0, 0, 10, 0, 10, 50, 50, 0), 10, false, true, ); assert.deepEqual(quadHReversed, quadH); }); it('works when the gap is smaller than the distance between the quads', () => { const quad = getGapQuadBetweenQuads( createQuad(0, 0, 30, 0, 30, 20, 0, 20), createQuad(0, 50, 30, 50, 30, 70, 0, 70), 10, true, false, ); assert.deepEqual(quad, createQuad(0, 30, 30, 30, 30, 40, 0, 40)); }); it('works when the quads are transformed', () => { const quad = getGapQuadBetweenQuads( createQuad(0, 20, 20, 0, 40, 20, 20, 40), createQuad(50, 70, 70, 50, 80, 60, 60, 80), 10, true, false, ); // The rounding of coordinates ends up 1px off, but this won't really matter visually, so the test just accounts for // it here. assert.deepEqual(quad, createQuad(31, 51, 51, 31, 59, 39, 39, 59)); }); }); describe('uniteQuads', () => { it('creates a quad that is big enough to contain the 2 passed quads', () => { const quad = uniteQuads( createQuad(0, 20, 10, 20, 10, 30, 0, 30), createQuad(20, 10, 40, 10, 40, 40, 20, 40), true, false, ); assert.deepEqual(quad, createQuad(0, 10, 40, 10, 40, 40, 0, 40)); }); it('can be called multiple times with the previously united quad to construct a flex line out of flex items', () => { let quad = uniteQuads( createQuad(0, 20, 10, 20, 10, 30, 0, 30), createQuad(20, 10, 40, 10, 40, 40, 20, 40), true, false, ); quad = uniteQuads( quad, createQuad(60, 30, 90, 30, 90, 50, 60, 50), true, false, ); quad = uniteQuads( quad, createQuad(130, 0, 180, 0, 180, 30, 130, 30), true, false, ); assert.deepEqual(quad, createQuad(0, 0, 180, 0, 180, 50, 0, 50)); }); it('also works when the quads are transformed', () => { const quad = uniteQuads( createQuad(0, 20, 20, 0, 40, 20, 20, 40), createQuad(50, 70, 70, 50, 80, 60, 60, 80), false, false, ); assert.deepEqual(quad, createQuad(0, 20, 20, 0, 80, 60, 60, 80)); }); }); describe('growQuadToEdgesOf', () => { it('works horizontally', () => { const quad = growQuadToEdgesOf( createQuad(10, 10, 20, 10, 20, 20, 10, 20), createQuad(0, 0, 60, 0, 60, 30, 0, 30), true, ); assert.deepEqual(quad, createQuad(0, 10, 60, 10, 60, 20, 0, 20)); }); it('works vertically', () => { const quad = growQuadToEdgesOf( createQuad(10, 10, 20, 10, 20, 20, 10, 20), createQuad(0, 0, 60, 0, 60, 30, 0, 30), false, ); assert.deepEqual(quad, createQuad(10, 0, 20, 0, 20, 30, 10, 30)); }); it('works with transformed quads', () => { const quad = growQuadToEdgesOf( createQuad(30, 60, 60, 30, 80, 50, 50, 80), createQuad(10, 60, 60, 10, 100, 50, 50, 100), true, ); assert.deepEqual(quad, createQuad(20, 70, 70, 20, 90, 40, 40, 90)); }); }); describe('getColinearPointAtDistance', () => { function assertPoint(p1: Position, p2: Position, distance: number, expected: Position): void { const point = getColinearPointAtDistance(p1, p2, distance); assert.deepEqual({x: Math.round(point.x), y: Math.round(point.y)}, expected); } it('returns the right coordinates when the line is horizontal', () => { assertPoint({x: 0, y: 0}, {x: 10, y: 0}, 5, {x: 5, y: 0}); }); it('returns the right coordinates when the line is vertical', () => { assertPoint({x: 0, y: 0}, {x: 0, y: 10}, 5, {x: 0, y: 5}); }); it('returns the right coordinates when the line is at an angle', () => { assertPoint({x: 0, y: 0}, {x: 10, y: 10}, 5, {x: 4, y: 4}); }); it('also works when distance is longer than the p1-p2 segment', () => { assertPoint({x: 10, y: 20}, {x: 10, y: 40}, 50, {x: 10, y: 70}); }); }); describe('distance', () => { function assertDistance(p1: Position, p2: Position, expected: number): void { const d = distance(p1, p2); assert.deepEqual(Math.round(d), expected); } it('works', () => { assertDistance({x: 0, y: 0}, {x: 10, y: 0}, 10); assertDistance({x: 0, y: 0}, {x: 100, y: 0}, 100); assertDistance({x: 10, y: 0}, {x: 0, y: 0}, 10); assertDistance({x: 10, y: 10}, {x: 10, y: 30}, 20); assertDistance({x: 10, y: 10}, {x: 10, y: 5}, 5); assertDistance({x: 10, y: 10}, {x: 20, y: 20}, 14); }); }); describe('segmentContains', () => { it('works with straight segments', () => { assert.isFalse(segmentContains([{x: 0, y: 0}, {x: 0, y: 10}], {x: 10, y: 10})); assert.isFalse(segmentContains([{x: 0, y: 10}, {x: 0, y: 0}], {x: 10, y: 10})); assert.isFalse(segmentContains([{x: 10, y: 10}, {x: 100, y: 10}], {x: 10, y: 20})); assert.isFalse(segmentContains([{x: 10, y: 10}, {x: 10, y: 100}], {x: 10, y: 0})); assert.isTrue(segmentContains([{x: 0, y: 0}, {x: 0, y: 100}], {x: 0, y: 10})); assert.isTrue(segmentContains([{x: 0, y: 100}, {x: 0, y: 0}], {x: 0, y: 10})); assert.isTrue(segmentContains([{x: 10, y: 10}, {x: 20, y: 10}], {x: 15, y: 10})); assert.isTrue(segmentContains([{x: 20, y: 10}, {x: 10, y: 10}], {x: 15, y: 10})); }); it('works with other segments', () => { assert.isFalse(segmentContains([{x: 0, y: 0}, {x: 10, y: 10}], {x: 10, y: 100})); assert.isFalse(segmentContains([{x: 20, y: 20}, {x: 30, y: 0}], {x: 10, y: 100})); assert.isTrue(segmentContains([{x: 0, y: 0}, {x: 100, y: 100}], {x: 50, y: 50})); assert.isTrue(segmentContains([{x: 0, y: 100}, {x: 100, y: 0}], {x: 50, y: 50})); }); }); describe('intersectSegments', () => { function assertIntersection(s1: Position[], s2: Position[], expected: Position): void { const point = intersectSegments(s1, s2); assert.deepEqual({x: Math.round(point.x), y: Math.round(point.y)}, expected); } it('works when x or y is 0', () => { assertIntersection([{x: 0, y: 0}, {x: 0, y: 10}], [{x: 0, y: 5}, {x: 5, y: 5}], {x: 0, y: 5}); assertIntersection([{x: 0, y: 0}, {x: 100, y: 0}], [{x: 50, y: 0}, {x: 50, y: 5}], {x: 50, y: 0}); assertIntersection([{x: -5, y: 0}, {x: 5, y: 0}], [{x: 0, y: -5}, {x: 0, y: 5}], {x: 0, y: 0}); }); it('works in simple cases', () => { assertIntersection([{x: 5, y: 15}, {x: 15, y: 5}], [{x: 5, y: 5}, {x: 15, y: 15}], {x: 10, y: 10}); assertIntersection([{x: 5, y: 10}, {x: 15, y: 10}], [{x: 10, y: 5}, {x: 10, y: 15}], {x: 10, y: 10}); }); it('works when segments only intersect outside their boundaries', () => { assertIntersection([{x: 5, y: 5}, {x: 5, y: 15}], [{x: 15, y: 10}, {x: 25, y: 10}], {x: 5, y: 10}); }); });