Skip to content

Commit

Permalink
Test & style make op optional, TODO: track
Browse files Browse the repository at this point in the history
Signed-off-by: Everett Ross <[email protected]>
  • Loading branch information
everett980 committed Nov 21, 2019
1 parent 01a392d commit c33d4ab
Show file tree
Hide file tree
Showing 38 changed files with 1,186 additions and 640 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,23 @@ describe('calcPositioning', () => {
);
expect(svcMarginTop).toBe(radius - lineHeight - OP_PADDING_TOP / 2);
});

it('treats multiple operations as a single word', () => {
const maxSvcLines = 2;
const operationCount = 10;
svcMeasurements = genWidths(new Array(maxSvcLines).fill(0.5));
opMeasurements = genWidths(new Array(operationCount).fill(3));
const { opWidth, radius, svcWidth, svcMarginTop } = calcPositioning(
genStr(maxSvcLines),
new Array(operationCount).fill(genStr(1))
);
expect(measureSvc).toHaveBeenCalledTimes(maxSvcLines);
expect(measureOp).toHaveBeenCalledTimes(1);
expect(svcWidth).toBe(1 * lineHeight);
expect(opWidth).toBe(3 * lineHeight);
expect(radius).toMatchInlineSnapshot(`34.51869478592516`);
expect(svcMarginTop).toBeCloseTo(radius - radius * Math.sin(Math.acos(svcWidth / 2 / radius)), 8);
});
});

describe('neglible service rectangle', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ type TRect = { height: number; width: number };

let svcSpan: HTMLSpanElement | undefined;

// TODO remove
let log: (...args: any) => void = () => {};

/*
* Mesaurements for the words comprising a service are measured by a span that is mounted out of a view.
* Using a canvas would remove the requirement that the measuring container is part of the DOM, however canvas
Expand Down Expand Up @@ -153,12 +150,12 @@ function calcWidth(lengths: number[], lines: number, longestThusFar: number = 0)
*/
const calcRects = _memoize(
function calcRects(str: string | string[], span: HTMLSpanElement): TRect[] {
log(str, Array.isArray(str));
const lengths = (Array.isArray(str) ? [`${str.length} Operations}`] : (str.match(WORD_RX) || [str])).map(s => {
span.innerHTML = s; // eslint-disable-line no-param-reassign
return span.getClientRects()[0].width;
});
log(lengths);
const lengths = (Array.isArray(str) ? [`${str.length} Operations}`] : str.match(WORD_RX) || [str]).map(
s => {
span.innerHTML = s; // eslint-disable-line no-param-reassign
return span.getClientRects()[0].width;
}
);

const rects: TRect[] = [];
for (let lines = 1; lines <= lengths.length; lines++) {
Expand All @@ -167,7 +164,6 @@ const calcRects = _memoize(
if (!rects.length || width < rects[rects.length - 1].width) rects.push({ height, width });
if (height > width) break;
}
log(rects);
return rects;
},
(str: string, span: HTMLSpanElement) => `${str}\t${span.style.fontWeight}`
Expand Down Expand Up @@ -246,10 +242,12 @@ function smallestRadius(svcRects: TRect[], opRects?: TRect[]): TSmallestRadiusRV
return rv;
}

const calcPositioning: (service: string, operation?: string | string[] | null) => TSmallestRadiusRV = _memoize(
const calcPositioning: (
service: string,
operation?: string | string[] | null
) => TSmallestRadiusRV = _memoize(
function calcPositioningImpl(service: string, operation?: string | string[] | null) {
const svcRects = calcRects(service, _initSvcSpan());
log = Array.isArray(operation) ? console.log : () => {};
const opRects = operation ? calcRects(operation, _initOpSpan()) : undefined;

return smallestRadius(svcRects, opRects);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@ limitations under the License.

/*
* Because .plexus-Digraph--MeasurableHtmlNode is `position: absolute`, adding a z-index to DdgNodeContent
* would have no effect on layering.
* would have no effect on layering between different nodes.
*/
.plexus-Digraph--MeasurableHtmlNode {
/* transition-delay must equal .DdgNodeContent--actionsWrapper transition delay plus duration */
transition-delay: 250ms;
transition-property: z-index;
z-index: 0;
}

.plexus-Digraph--MeasurableHtmlNode:hover {
transition-delay: 0s;
z-index: 1;
/* TODO ease-out 150ms */
/* if ease-out also works for display none the hovered.state can be used only as a guard for calculating the
* parent/children vis */
}

.DdgNodeContent--core {
Expand Down Expand Up @@ -63,7 +68,19 @@ limitations under the License.
bottom: 100%;
box-shadow: 0 0px 4px 1px rgba(0, 0, 0, 0.1);
left: 1em;
opacity: 0;
pointer-events: none;
position: absolute;
/* transition delay plus duration must equal .plexus-Digraph--MeasurableHtmlNode transition-delay */
transition-delay: 150ms;
transition-duration: 0.1s;
transition-property: opacity;
}

.DdgNodeContent:hover .DdgNodeContent--actionsWrapper {
opacity: 1;
pointer-events: all;
transition-delay: 0s;
}

.DdgNodeContent--actionsItem {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jest.mock('./calc-positioning', () => () => ({
/* eslint-disable import/first */
import React from 'react';
import { shallow } from 'enzyme';
import { Checkbox } from 'antd';

import DdgNodeContent from '.';
import { MAX_LENGTH, MAX_LINKED_TRACES, MIN_LENGTH, PARAM_NAME_LENGTH, RADIUS } from './constants';
Expand Down Expand Up @@ -50,14 +51,10 @@ describe('<DdgNodeContent>', () => {
let wrapper;

beforeEach(() => {
props.getGenerationVisibility.mockImplementation(direction =>
direction === EDirection.Upstream ? ECheckedStatus.Full : ECheckedStatus.Partial
);
props.getVisiblePathElems.mockReset();
props.setViewModifier.mockReset();
props.updateGenerationVisibility.mockReset();
wrapper = shallow(<DdgNodeContent {...props} />);
wrapper.setState({ hovered: true });
});

it('does not explode', () => {
Expand All @@ -70,15 +67,15 @@ describe('<DdgNodeContent>', () => {
expect(wrapper).toMatchSnapshot();
});

it('renders correctly when isFocalNode = true and focalNodeUrl = null', () => {
it('renders the number of operations if there are multiple', () => {
expect(wrapper).toMatchSnapshot();
wrapper.setProps({ focalNodeUrl: null, isFocalNode: true });
wrapper.setProps({ operation: ['op0', 'op1', 'op2', 'op3'] });
expect(wrapper).toMatchSnapshot();
});

it('renders correctly when not hovered', () => {
it('renders correctly when isFocalNode = true and focalNodeUrl = null', () => {
expect(wrapper).toMatchSnapshot();
wrapper.setState({ hovered: false });
wrapper.setProps({ focalNodeUrl: null, isFocalNode: true });
expect(wrapper).toMatchSnapshot();
});

Expand All @@ -93,62 +90,73 @@ describe('<DdgNodeContent>', () => {
});

describe('hover behavior', () => {
beforeAll(() => {
jest.useFakeTimers();
const testIndices = [4, 8, 15, 16, 23, 42];
const testElems = testIndices.map(visibilityIdx => ({ visibilityIdx }));

beforeEach(() => {
props.getVisiblePathElems.mockReturnValue(testElems);
});

it('calls setViewModifier on mouse enter', () => {
wrapper.simulate('mouseenter', { type: 'mouseenter' });

expect(props.setViewModifier).toHaveBeenCalledTimes(1);
expect(props.setViewModifier).toHaveBeenCalledWith(vertexKey, EViewModifier.Hovered, true);
expect(props.setViewModifier).toHaveBeenCalledWith(testIndices, EViewModifier.Hovered, true);
});

it('calls setViewModifier on mouse leave', () => {
it('calls setViewModifier with all modified indices on mouse leave', () => {
wrapper.simulate('mouseenter', { type: 'mouseenter' });
wrapper.simulate('mouseleave', { type: 'mouseleave' });

expect(props.setViewModifier).toHaveBeenCalledTimes(1);
expect(props.setViewModifier).toHaveBeenCalledWith(vertexKey, EViewModifier.Hovered, false);
});
expect(props.setViewModifier).toHaveBeenCalledTimes(2);
expect(props.setViewModifier).toHaveBeenCalledWith(testIndices, EViewModifier.Hovered, false);

it('calls setViewModifier on unmount iff state.hovered is true', () => {
wrapper.unmount();

expect(props.setViewModifier).toHaveBeenCalledTimes(1);
expect(props.setViewModifier).toHaveBeenCalledWith(vertexKey, EViewModifier.Hovered, false);

// state.hovered is initially false
const unhoveredWrapper = shallow(<DdgNodeContent {...props} />);
unhoveredWrapper.unmount();

expect(props.setViewModifier).toHaveBeenCalledTimes(1);
expect(props.setViewModifier).toHaveBeenCalledWith(vertexKey, EViewModifier.Hovered, false);
});

it('sets state.hovered to true on mouse enter', () => {
wrapper.setState({ hovered: false });
wrapper.simulate('mouseenter', { type: 'mouseenter' });
const moreIndices = [108];
const moreElems = moreIndices.map(visibilityIdx => ({ visibilityIdx }));
props.getVisiblePathElems.mockReturnValue(moreElems);
wrapper.simulate('mouseenter', { type: 'mouseenter' });
wrapper.simulate('mouseleave', { type: 'mouseleave' });

expect(wrapper.state('hovered')).toBe(true);
expect(props.setViewModifier).toHaveBeenCalledTimes(5);
expect(props.setViewModifier).toHaveBeenCalledWith(
testIndices.concat(moreIndices),
EViewModifier.Hovered,
false
);
});

it('sets state.hovered to false on mouse leave, after delay', () => {
it('calls setViewModifier on unmount iff any indices were hovered and not unhovered', () => {
wrapper.unmount();
expect(props.setViewModifier).toHaveBeenCalledTimes(0);

wrapper = shallow(<DdgNodeContent {...props} />);
wrapper.simulate('mouseenter', { type: 'mouseenter' });
wrapper.simulate('mouseleave', { type: 'mouseleave' });
expect(wrapper.state('hovered')).toBe(true);
expect(props.setViewModifier).toHaveBeenCalledTimes(2);
wrapper.unmount();
expect(props.setViewModifier).toHaveBeenCalledTimes(2);

jest.runAllTimers();
expect(wrapper.state('hovered')).toBe(false);
});
wrapper = shallow(<DdgNodeContent {...props} />);
wrapper.simulate('mouseenter', { type: 'mouseenter' });
wrapper.unmount();

it('cancels delayed set state if mouse re-enters before timeout runs', () => {
wrapper.simulate('mouseleave', { type: 'mouseleave' });
expect(wrapper.instance().hoverClearDelay).toEqual(expect.any(Number));
expect(props.setViewModifier).toHaveBeenCalledTimes(4);
expect(props.setViewModifier).toHaveBeenCalledWith(testIndices, EViewModifier.Hovered, false);
});

it('calculates state.childrenVisibility and state.parentVisibility on mouse enter', () => {
const childrenVisibility = ECheckedStatus.Partial;
const parentVisibility = ECheckedStatus.Full;
props.getGenerationVisibility.mockImplementation((_key, direction) =>
direction === EDirection.Upstream ? parentVisibility : childrenVisibility
);
wrapper.simulate('mouseenter', { type: 'mouseenter' });
expect(wrapper.instance().hoverClearDelay).toBeUndefined();

jest.runAllTimers();
expect(wrapper.state('hovered')).toBe(true);
expect(wrapper.state()).toEqual({
childrenVisibility,
parentVisibility,
});
});
});

Expand Down Expand Up @@ -186,10 +194,42 @@ describe('<DdgNodeContent>', () => {
});

describe('updateChildren', () => {
it('renders children visibility status indicator iff state.childrenVisibility is provided', () => {
const initialItemCount = wrapper.find('.DdgNodeContent--actionsItem').length;

wrapper.setState({ childrenVisibility: ECheckedStatus.Empty });
expect(wrapper.find('.DdgNodeContent--actionsItem').length).toBe(initialItemCount + 1);
expect(wrapper.find(Checkbox).props()).toEqual(
expect.objectContaining({
checked: false,
indeterminate: false,
})
);

wrapper.setState({ childrenVisibility: ECheckedStatus.Partial });
expect(wrapper.find('.DdgNodeContent--actionsItem').length).toBe(initialItemCount + 1);
expect(wrapper.find(Checkbox).props()).toEqual(
expect.objectContaining({
checked: false,
indeterminate: true,
})
);

wrapper.setState({ childrenVisibility: ECheckedStatus.Full });
expect(wrapper.find('.DdgNodeContent--actionsItem').length).toBe(initialItemCount + 1);
expect(wrapper.find(Checkbox).props()).toEqual(
expect.objectContaining({
checked: true,
indeterminate: false,
})
);
});

it('calls this.props.updateGenerationVisibility with this.props.vertexKey', () => {
wrapper.setState({ childrenVisibility: ECheckedStatus.Empty });
wrapper
.find('.DdgNodeContent--actionsItem')
.at(5)
.last()
.simulate('click');

expect(props.updateGenerationVisibility).toHaveBeenCalledWith(props.vertexKey, EDirection.Downstream);
Expand All @@ -198,10 +238,42 @@ describe('<DdgNodeContent>', () => {
});

describe('updateParents', () => {
it('renders parent visibility status indicator iff state.parentVisibility is provided', () => {
const initialItemCount = wrapper.find('.DdgNodeContent--actionsItem').length;

wrapper.setState({ parentVisibility: ECheckedStatus.Empty });
expect(wrapper.find('.DdgNodeContent--actionsItem').length).toBe(initialItemCount + 1);
expect(wrapper.find(Checkbox).props()).toEqual(
expect.objectContaining({
checked: false,
indeterminate: false,
})
);

wrapper.setState({ parentVisibility: ECheckedStatus.Partial });
expect(wrapper.find('.DdgNodeContent--actionsItem').length).toBe(initialItemCount + 1);
expect(wrapper.find(Checkbox).props()).toEqual(
expect.objectContaining({
checked: false,
indeterminate: true,
})
);

wrapper.setState({ parentVisibility: ECheckedStatus.Full });
expect(wrapper.find('.DdgNodeContent--actionsItem').length).toBe(initialItemCount + 1);
expect(wrapper.find(Checkbox).props()).toEqual(
expect.objectContaining({
checked: true,
indeterminate: false,
})
);
});

it('calls this.props.updateGenerationVisibility with this.props.vertexKey', () => {
wrapper.setState({ parentVisibility: ECheckedStatus.Empty });
wrapper
.find('.DdgNodeContent--actionsItem')
.at(4)
.last()
.simulate('click');

expect(props.updateGenerationVisibility).toHaveBeenCalledWith(props.vertexKey, EDirection.Upstream);
Expand Down
Loading

0 comments on commit c33d4ab

Please sign in to comment.