diff options
author | Wouter Admiraal <wouter.admiraal@sonarsource.com> | 2021-09-07 15:44:58 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-09-13 20:03:33 +0000 |
commit | 52b02379a6f0485c9efd4bc59f05752a9cc0ed20 (patch) | |
tree | ef97e8231b6526c8edcaa71c46b51429b3a111eb /server | |
parent | 86f2c502104fbc03eb5aac19598e9b5c2e7290b8 (diff) | |
download | sonarqube-52b02379a6f0485c9efd4bc59f05752a9cc0ed20.tar.gz sonarqube-52b02379a6f0485c9efd4bc59f05752a9cc0ed20.zip |
Fix multiple Code Smells
Diffstat (limited to 'server')
31 files changed, 616 insertions, 391 deletions
diff --git a/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx b/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx index 75d3172ea80..f71f476a50a 100644 --- a/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx +++ b/server/sonar-web/src/main/js/components/charts/BubbleChart.tsx @@ -102,7 +102,6 @@ export default class BubbleChart<T> extends React.PureComponent<Props<T>, State> this.zoom = zoom() .scaleExtent([1, 10]) .on('zoom', this.zoomed); - // TODO: Check why as any cast is necessary now. select(this.node).call(this.zoom as any); }; @@ -118,11 +117,10 @@ export default class BubbleChart<T> extends React.PureComponent<Props<T>, State> }); }; - resetZoom = (event: React.MouseEvent<Link>) => { - event.stopPropagation(); - event.preventDefault(); + resetZoom = (e: React.MouseEvent<Link>) => { + e.stopPropagation(); + e.preventDefault(); if (this.zoom && this.node) { - // TODO: Check why as any cast is necessary now. select(this.node).call(this.zoom.transform as any, zoomIdentity); } }; @@ -144,11 +142,11 @@ export default class BubbleChart<T> extends React.PureComponent<Props<T>, State> } getTicks(scale: Scale, format: (d: number) => string) { - const zoom = Math.ceil(this.state.transform.k); - const ticks = scale.ticks(TICKS_COUNT * zoom).map(tick => format(tick)); + const zoomAmount = Math.ceil(this.state.transform.k); + const ticks = scale.ticks(TICKS_COUNT * zoomAmount).map(tick => format(tick)); const uniqueTicksCount = uniq(ticks).length; const ticksCount = - uniqueTicksCount < TICKS_COUNT * zoom ? uniqueTicksCount - 1 : TICKS_COUNT * zoom; + uniqueTicksCount < TICKS_COUNT * zoomAmount ? uniqueTicksCount - 1 : TICKS_COUNT * zoomAmount; return scale.ticks(ticksCount); } @@ -360,10 +358,10 @@ interface BubbleProps<T> { } function Bubble<T>(props: BubbleProps<T>) { - const handleClick = (event: React.MouseEvent<SVGCircleElement>) => { + const handleClick = (e: React.MouseEvent<SVGCircleElement>) => { if (props.onClick) { - event.stopPropagation(); - event.preventDefault(); + e.stopPropagation(); + e.preventDefault(); props.onClick(props.data); } }; diff --git a/server/sonar-web/src/main/js/components/charts/Histogram.tsx b/server/sonar-web/src/main/js/components/charts/Histogram.tsx index a4ae509a2ef..08b0f30e948 100644 --- a/server/sonar-web/src/main/js/components/charts/Histogram.tsx +++ b/server/sonar-web/src/main/js/components/charts/Histogram.tsx @@ -47,7 +47,7 @@ export default class Histogram extends React.PureComponent<Props> { const width = Math.round(xScale(d)) + /* minimum bar width */ 1; const x = xScale.range()[0] + (alignTicks ? padding[3] : 0); - const y = Math.round(yScale(index)! + yScale.bandwidth() / 2); + const y = Math.round((yScale(index) || 0) + yScale.bandwidth() / 2); return <rect className="bar-chart-bar" height={BAR_HEIGHT} width={width} x={x} y={y} />; } @@ -62,7 +62,7 @@ export default class Histogram extends React.PureComponent<Props> { } const x = xScale(d) + (alignTicks ? padding[3] : 0); - const y = Math.round(yScale(index)! + yScale.bandwidth() / 2 + BAR_HEIGHT / 2); + const y = Math.round((yScale(index) || 0) + yScale.bandwidth() / 2 + BAR_HEIGHT / 2); return ( <Tooltip overlay={this.props.yTooltips && this.props.yTooltips[index]}> @@ -83,7 +83,7 @@ export default class Histogram extends React.PureComponent<Props> { } const x = xScale.range()[0]; - const y = Math.round(yScale(index)! + yScale.bandwidth() / 2 + BAR_HEIGHT / 2); + const y = Math.round((yScale(index) || 0) + yScale.bandwidth() / 2 + BAR_HEIGHT / 2); const historyTickClass = alignTicks ? 'histogram-tick-start' : 'histogram-tick'; return ( diff --git a/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.tsx b/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.tsx index f4f2742eb75..7caee5b3aec 100644 --- a/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.tsx +++ b/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.tsx @@ -49,7 +49,10 @@ interface State { } type XScale = ScaleTime<number, number>; -// TODO it should be `ScaleLinear<number, number> | ScalePoint<number> | ScalePoint<string>`, but it's super hard to make it work :'( +// It should be `ScaleLinear<number, number> | ScalePoint<number> | ScalePoint<string>`, but in order +// to make it work, we need to write a lot of type guards :-(. This introduces a lot of unnecessary code, +// not to mention overhead at runtime. The simplest is just to cast to any, and rely on D3's internals +// to make it work. type YScale = any; export default class ZoomTimeLine extends React.PureComponent<Props, State> { @@ -114,7 +117,7 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> { this.handleZoomUpdate(xScale, xDim); }; - handleSelectionDrag = (xScale: XScale, width: number, xDim: number[], checkDelta?: boolean) => ( + handleSelectionDrag = (xScale: XScale, width: number, xDim: number[], checkDelta = false) => ( _: MouseEvent, data: DraggableData ) => { @@ -129,7 +132,7 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> { fixedX: number, xDim: number[], handleDirection: string, - checkDelta?: boolean + checkDelta = false ) => (_: MouseEvent, data: DraggableData) => { if (!checkDelta || data.deltaX) { const x = Math.max(xDim[0], Math.min(data.x, xDim[1])); diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx b/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx index 3be36c3677d..967b075314f 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx +++ b/server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx @@ -17,43 +17,135 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { mount } from 'enzyme'; +import { select } from 'd3-selection'; +import { zoom } from 'd3-zoom'; +import { shallow } from 'enzyme'; import * as React from 'react'; -import { AutoSizerProps } from 'react-virtualized'; +import { Link } from 'react-router'; +import { AutoSizer, AutoSizerProps } from 'react-virtualized/dist/commonjs/AutoSizer'; +import { mockComponentMeasureEnhanced } from '../../../helpers/mocks/component'; +import { mockHtmlElement } from '../../../helpers/mocks/dom'; +import { mockEvent } from '../../../helpers/testMocks'; +import { click } from '../../../helpers/testUtils'; import BubbleChart from '../BubbleChart'; jest.mock('react-virtualized/dist/commonjs/AutoSizer', () => ({ AutoSizer: ({ children }: AutoSizerProps) => children({ width: 100, height: NaN }) })); +jest.mock('d3-selection', () => ({ + event: { transform: { x: 10, y: 10, k: 20 } }, + select: jest.fn().mockReturnValue({ call: jest.fn() }) +})); + +jest.mock('d3-zoom', () => ({ + ...jest.requireActual('d3-zoom'), + zoom: jest.fn() +})); + +beforeEach(jest.clearAllMocks); + it('should display bubbles', () => { - const items = [ - { x: 1, y: 10, size: 7 }, - { x: 2, y: 30, size: 5 } - ]; - const chart = mount(<BubbleChart height={100} items={items} padding={[0, 0, 0, 0]} />); - chart.find('Bubble').forEach(bubble => expect(bubble).toMatchSnapshot()); - - chart.setProps({ height: 120 }); + const wrapper = shallowRender(); + wrapper + .find(AutoSizer) + .dive() + .find('Bubble') + .forEach(bubble => { + expect(bubble.dive()).toMatchSnapshot(); + }); }); it('should render bubble links', () => { - const items = [ - { x: 1, y: 10, size: 7, link: 'foo' }, - { x: 2, y: 30, size: 5, link: 'bar' } - ]; - const chart = mount(<BubbleChart height={100} items={items} padding={[0, 0, 0, 0]} />); - chart.find('Bubble').forEach(bubble => expect(bubble).toMatchSnapshot()); + const wrapper = shallowRender({ + items: [ + { x: 1, y: 10, size: 7, link: 'foo' }, + { x: 2, y: 30, size: 5, link: 'bar' } + ] + }); + wrapper + .find(AutoSizer) + .dive() + .find('Bubble') + .forEach(bubble => { + expect(bubble.dive()).toMatchSnapshot(); + }); }); it('should render bubbles with click handlers', () => { - const onClick = jest.fn(); - const items = [ - { x: 1, y: 10, size: 7, data: 'foo' }, - { x: 2, y: 30, size: 5, data: 'bar' } - ]; - const chart = mount( - <BubbleChart height={100} items={items} onBubbleClick={onClick} padding={[0, 0, 0, 0]} /> - ); - chart.find('Bubble').forEach(bubble => expect(bubble).toMatchSnapshot()); + const onBubbleClick = jest.fn(); + const wrapper = shallowRender({ onBubbleClick }); + wrapper + .find(AutoSizer) + .dive() + .find('Bubble') + .forEach(bubble => { + click(bubble.dive().find('circle')); + expect(bubble.dive()).toMatchSnapshot(); + }); + expect(onBubbleClick).toBeCalledTimes(2); + expect(onBubbleClick).toHaveBeenLastCalledWith(mockComponentMeasureEnhanced()); +}); + +it('should correctly handle zooming', () => { + class ZoomBehaviorMock { + on = () => this; + scaleExtent = () => this; + translateExtent = () => this; + } + + const call = jest.fn(); + const zoomBehavior = new ZoomBehaviorMock(); + (select as jest.Mock).mockReturnValueOnce({ call }); + (zoom as jest.Mock).mockReturnValueOnce(zoomBehavior); + + return new Promise<void>((resolve, reject) => { + const wrapper = shallowRender({ padding: [5, 5, 5, 5] }); + wrapper.instance().boundNode( + mockHtmlElement<SVGSVGElement>({ + getBoundingClientRect: () => ({ width: 100, height: 100 } as DOMRect) + }) + ); + + // Call zoom event handler. + wrapper.instance().zoomed(); + expect(wrapper.state().transform).toEqual({ + x: 105, + y: 105, + k: 20 + }); + + // Reset Zoom levels. + const resetZoomClick = wrapper + .find('div.bubble-chart-zoom') + .find(Link) + .props().onClick; + if (!resetZoomClick) { + reject(); + return; + } + + const stopPropagation = jest.fn(); + const preventDefault = jest.fn(); + resetZoomClick(mockEvent({ stopPropagation, preventDefault })); + expect(stopPropagation).toBeCalled(); + expect(preventDefault).toBeCalled(); + expect(call).toHaveBeenCalledWith(zoomBehavior); + + resolve(); + }); }); + +function shallowRender(props: Partial<BubbleChart<T.ComponentMeasureEnhanced>['props']> = {}) { + return shallow<BubbleChart<T.ComponentMeasureEnhanced>>( + <BubbleChart + height={100} + items={[ + { x: 1, y: 10, size: 7, data: mockComponentMeasureEnhanced() }, + { x: 2, y: 30, size: 5, data: mockComponentMeasureEnhanced() } + ]} + padding={[0, 0, 0, 0]} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/Histogram-test.tsx b/server/sonar-web/src/main/js/components/charts/__tests__/Histogram-test.tsx index 15f4754aae0..2f67d166cfd 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/Histogram-test.tsx +++ b/server/sonar-web/src/main/js/components/charts/__tests__/Histogram-test.tsx @@ -17,52 +17,50 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { scaleBand } from 'd3-scale'; import { shallow } from 'enzyme'; import * as React from 'react'; import Histogram from '../Histogram'; -it('renders', () => { - expect(shallow(<Histogram bars={[100, 75, 150]} height={75} width={100} />)).toMatchSnapshot(); +jest.mock('d3-scale', () => { + const d3 = jest.requireActual('d3-scale'); + return { + ...d3, + scaleBand: jest.fn(d3.scaleBand) + }; }); -it('renders with yValues', () => { - expect( - shallow( - <Histogram - bars={[100, 75, 150]} - height={75} - width={100} - yValues={['100.0', '75.0', '150.0']} - /> - ) - ).toMatchSnapshot(); -}); +beforeEach(jest.clearAllMocks); -it('renders with yValues and yTicks', () => { +it('renders correctly', () => { + expect(shallowRender()).toMatchSnapshot('default'); + expect(shallowRender({ alignTicks: true })).toMatchSnapshot('align ticks'); + expect(shallowRender({ yValues: ['100.0', '75.0', '150.0'] })).toMatchSnapshot('with yValues'); expect( - shallow( - <Histogram - bars={[100, 75, 150]} - height={75} - width={100} - yTicks={['a', 'b', 'c']} - yValues={['100.0', '75.0', '150.0']} - /> - ) - ).toMatchSnapshot(); + shallowRender({ yTicks: ['a', 'b', 'c'], yValues: ['100.0', '75.0', '150.0'] }) + ).toMatchSnapshot('with yValues and yTicks'); + expect( + shallowRender({ + yTicks: ['a', 'b', 'c'], + yTooltips: ['a - 100', 'b - 75', 'c - 150'], + yValues: ['100.0', '75.0', '150.0'] + }) + ).toMatchSnapshot('with yValues, yTicks and yTooltips'); }); -it('renders with yValues, yTicks and yTooltips', () => { +it('correctly handles yScale() returning undefined', () => { + const yScale = () => undefined; + yScale.bandwidth = () => 1; + + (scaleBand as jest.Mock).mockReturnValueOnce({ + domain: () => ({ rangeRound: () => yScale }) + }); + expect( - shallow( - <Histogram - bars={[100, 75, 150]} - height={75} - width={100} - yTicks={['a', 'b', 'c']} - yTooltips={['a - 100', 'b - 75', 'c - 150']} - yValues={['100.0', '75.0', '150.0']} - /> - ) + shallowRender({ yValues: ['100.0', '75.0', '150.0'], yTicks: ['a', 'b', 'c'] }) ).toMatchSnapshot(); }); + +function shallowRender(props: Partial<Histogram['props']> = {}) { + return shallow<Histogram>(<Histogram bars={[100, 75, 150]} height={75} width={100} {...props} />); +} diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap index b0f69f95417..8adfc888cd0 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap @@ -1,148 +1,51 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should display bubbles 1`] = ` -<Bubble - key="0" - r={45} - scale={1} - x={33.21428571428571} - y={70.07936507936509} -> - <Tooltip> - <g> - <circle - className="bubble-chart-bubble" - r={45} - style={ - Object { - "fill": undefined, - "stroke": undefined, - } +<Tooltip> + <g> + <circle + className="bubble-chart-bubble" + r={45} + style={ + Object { + "fill": undefined, + "stroke": undefined, } - transform="translate(33.21428571428571, 70.07936507936509) scale(1)" - /> - </g> - </Tooltip> -</Bubble> + } + transform="translate(33.21428571428571, 70.07936507936509) scale(1)" + /> + </g> +</Tooltip> `; exports[`should display bubbles 2`] = ` -<Bubble - key="1" - r={33.57142857142858} - scale={1} - x={66.42857142857142} - y={33.57142857142858} -> - <Tooltip> - <g> - <circle - className="bubble-chart-bubble" - r={33.57142857142858} - style={ - Object { - "fill": undefined, - "stroke": undefined, - } +<Tooltip> + <g> + <circle + className="bubble-chart-bubble" + r={33.57142857142858} + style={ + Object { + "fill": undefined, + "stroke": undefined, } - transform="translate(66.42857142857142, 33.57142857142858) scale(1)" - /> - </g> - </Tooltip> -</Bubble> + } + transform="translate(66.42857142857142, 33.57142857142858) scale(1)" + /> + </g> +</Tooltip> `; exports[`should render bubble links 1`] = ` -<Bubble - key="0" - link="foo" - r={45} - scale={1} - x={33.21428571428571} - y={70.07936507936509} -> - <Tooltip> - <g> - <Link - onlyActiveOnIndex={false} - style={Object {}} - to="foo" - > - <a - onClick={[Function]} - style={Object {}} - > - <circle - className="bubble-chart-bubble" - r={45} - style={ - Object { - "fill": undefined, - "stroke": undefined, - } - } - transform="translate(33.21428571428571, 70.07936507936509) scale(1)" - /> - </a> - </Link> - </g> - </Tooltip> -</Bubble> -`; - -exports[`should render bubble links 2`] = ` -<Bubble - key="1" - link="bar" - r={33.57142857142858} - scale={1} - x={66.42857142857142} - y={33.57142857142858} -> - <Tooltip> - <g> - <Link - onlyActiveOnIndex={false} - style={Object {}} - to="bar" - > - <a - onClick={[Function]} - style={Object {}} - > - <circle - className="bubble-chart-bubble" - r={33.57142857142858} - style={ - Object { - "fill": undefined, - "stroke": undefined, - } - } - transform="translate(66.42857142857142, 33.57142857142858) scale(1)" - /> - </a> - </Link> - </g> - </Tooltip> -</Bubble> -`; - -exports[`should render bubbles with click handlers 1`] = ` -<Bubble - data="foo" - key="0" - onClick={[MockFunction]} - r={45} - scale={1} - x={33.21428571428571} - y={70.07936507936509} -> - <Tooltip> - <g> +<Tooltip> + <g> + <Link + onlyActiveOnIndex={false} + style={Object {}} + to="foo" + > <circle className="bubble-chart-bubble" - onClick={[Function]} r={45} style={ Object { @@ -152,26 +55,21 @@ exports[`should render bubbles with click handlers 1`] = ` } transform="translate(33.21428571428571, 70.07936507936509) scale(1)" /> - </g> - </Tooltip> -</Bubble> + </Link> + </g> +</Tooltip> `; -exports[`should render bubbles with click handlers 2`] = ` -<Bubble - data="bar" - key="1" - onClick={[MockFunction]} - r={33.57142857142858} - scale={1} - x={66.42857142857142} - y={33.57142857142858} -> - <Tooltip> - <g> +exports[`should render bubble links 2`] = ` +<Tooltip> + <g> + <Link + onlyActiveOnIndex={false} + style={Object {}} + to="bar" + > <circle className="bubble-chart-bubble" - onClick={[Function]} r={33.57142857142858} style={ Object { @@ -181,7 +79,45 @@ exports[`should render bubbles with click handlers 2`] = ` } transform="translate(66.42857142857142, 33.57142857142858) scale(1)" /> - </g> - </Tooltip> -</Bubble> + </Link> + </g> +</Tooltip> +`; + +exports[`should render bubbles with click handlers 1`] = ` +<Tooltip> + <g> + <circle + className="bubble-chart-bubble" + onClick={[Function]} + r={45} + style={ + Object { + "fill": undefined, + "stroke": undefined, + } + } + transform="translate(33.21428571428571, 70.07936507936509) scale(1)" + /> + </g> +</Tooltip> +`; + +exports[`should render bubbles with click handlers 2`] = ` +<Tooltip> + <g> + <circle + className="bubble-chart-bubble" + onClick={[Function]} + r={33.57142857142858} + style={ + Object { + "fill": undefined, + "stroke": undefined, + } + } + transform="translate(66.42857142857142, 33.57142857142858) scale(1)" + /> + </g> +</Tooltip> `; diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/Histogram-test.tsx.snap b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/Histogram-test.tsx.snap index f52a4ea6cf3..9f2ac323b09 100644 --- a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/Histogram-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/Histogram-test.tsx.snap @@ -1,6 +1,162 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders 1`] = ` +exports[`correctly handles yScale() returning undefined 1`] = ` +<svg + className="bar-chart" + height={75} + width={100} +> + <g + transform="translate(10, 10)" + > + <g> + <g + key="0" + > + <rect + className="bar-chart-bar" + height={10} + width={54} + x={0} + y={1} + /> + <Tooltip> + <text + className="bar-chart-tick histogram-value" + dx="1em" + dy="0.3em" + x={53.33333333333333} + y={6} + > + 100.0 + </text> + </Tooltip> + <text + className="bar-chart-tick histogram-tick" + dx="-1em" + dy="0.3em" + x={0} + y={6} + > + a + </text> + </g> + <g + key="1" + > + <rect + className="bar-chart-bar" + height={10} + width={41} + x={0} + y={1} + /> + <Tooltip> + <text + className="bar-chart-tick histogram-value" + dx="1em" + dy="0.3em" + x={40} + y={6} + > + 75.0 + </text> + </Tooltip> + <text + className="bar-chart-tick histogram-tick" + dx="-1em" + dy="0.3em" + x={0} + y={6} + > + b + </text> + </g> + <g + key="2" + > + <rect + className="bar-chart-bar" + height={10} + width={81} + x={0} + y={1} + /> + <Tooltip> + <text + className="bar-chart-tick histogram-value" + dx="1em" + dy="0.3em" + x={80} + y={6} + > + 150.0 + </text> + </Tooltip> + <text + className="bar-chart-tick histogram-tick" + dx="-1em" + dy="0.3em" + x={0} + y={6} + > + c + </text> + </g> + </g> + </g> +</svg> +`; + +exports[`renders correctly: align ticks 1`] = ` +<svg + className="bar-chart" + height={75} + width={100} +> + <g + transform="translate(4, 10)" + > + <g> + <g + key="0" + > + <rect + className="bar-chart-bar" + height={10} + width={54} + x={10} + y={10} + /> + </g> + <g + key="1" + > + <rect + className="bar-chart-bar" + height={10} + width={41} + x={10} + y={28} + /> + </g> + <g + key="2" + > + <rect + className="bar-chart-bar" + height={10} + width={81} + x={10} + y={46} + /> + </g> + </g> + </g> +</svg> +`; + +exports[`renders correctly: default 1`] = ` <svg className="bar-chart" height={75} @@ -48,7 +204,7 @@ exports[`renders 1`] = ` </svg> `; -exports[`renders with yValues 1`] = ` +exports[`renders correctly: with yValues 1`] = ` <svg className="bar-chart" height={75} @@ -129,7 +285,7 @@ exports[`renders with yValues 1`] = ` </svg> `; -exports[`renders with yValues and yTicks 1`] = ` +exports[`renders correctly: with yValues and yTicks 1`] = ` <svg className="bar-chart" height={75} @@ -237,7 +393,7 @@ exports[`renders with yValues and yTicks 1`] = ` </svg> `; -exports[`renders with yValues, yTicks and yTooltips 1`] = ` +exports[`renders correctly: with yValues, yTicks and yTooltips 1`] = ` <svg className="bar-chart" height={75} diff --git a/server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx b/server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx index 61bf510c57d..0ab285a3391 100644 --- a/server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx +++ b/server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx @@ -54,7 +54,9 @@ export default class ConfirmModal<T = string> extends React.PureComponent<Props< handleSubmit = () => { const result = this.props.onConfirm(this.props.confirmData); if (result) { - return result.then(this.props.onClose, () => {}); + return result.then(this.props.onClose, () => { + /* noop */ + }); } this.props.onClose(); return undefined; diff --git a/server/sonar-web/src/main/js/components/controls/Dropdown.tsx b/server/sonar-web/src/main/js/components/controls/Dropdown.tsx index 92b1444aeb7..04f2b3c8208 100644 --- a/server/sonar-web/src/main/js/components/controls/Dropdown.tsx +++ b/server/sonar-web/src/main/js/components/controls/Dropdown.tsx @@ -119,9 +119,6 @@ interface OverlayProps { placement?: PopupPlacement; } -// TODO use the same styling for <Select /> -// TODO use the same styling for <DateInput /> - export class DropdownOverlay extends React.Component<OverlayProps> { get placement() { return this.props.placement || PopupPlacement.Bottom; diff --git a/server/sonar-web/src/main/js/components/controls/GlobalMessages.tsx b/server/sonar-web/src/main/js/components/controls/GlobalMessages.tsx index b2d5b1d21dd..5a68bf9d649 100644 --- a/server/sonar-web/src/main/js/components/controls/GlobalMessages.tsx +++ b/server/sonar-web/src/main/js/components/controls/GlobalMessages.tsx @@ -24,7 +24,7 @@ import { colors, sizes, zIndexes } from '../../app/theme'; import { cutLongWords } from '../../helpers/path'; import { ClearButton } from './buttons'; -interface Message { +interface IMessage { id: string; level: 'ERROR' | 'SUCCESS'; message: string; @@ -32,7 +32,7 @@ interface Message { export interface GlobalMessagesProps { closeGlobalMessage: (id: string) => void; - messages: Message[]; + messages: IMessage[]; } export default function GlobalMessages({ closeGlobalMessage, messages }: GlobalMessagesProps) { @@ -60,7 +60,7 @@ const MessagesContainer = styled.div` export class GlobalMessage extends React.PureComponent<{ closeGlobalMessage: (id: string) => void; - message: Message; + message: IMessage; }> { handleClose = () => { this.props.closeGlobalMessage(this.props.message.id); @@ -94,7 +94,7 @@ const appearAnim = keyframes` } `; -const Message = styled.div<Pick<Message, 'level'>>` +const Message = styled.div<Pick<IMessage, 'level'>>` position: relative; padding: 0 30px 0 10px; line-height: ${sizes.controlHeight}; @@ -112,7 +112,7 @@ const Message = styled.div<Pick<Message, 'level'>>` } `; -const CloseButton = styled(ClearButton)<Pick<Message, 'level'>>` +const CloseButton = styled(ClearButton)<Pick<IMessage, 'level'>>` position: absolute; top: calc(${sizes.gridSize} / 4); right: calc(${sizes.gridSize} / 4); diff --git a/server/sonar-web/src/main/js/components/controls/InputValidationField.tsx b/server/sonar-web/src/main/js/components/controls/InputValidationField.tsx index bb9cfcab90f..0e844b113d2 100644 --- a/server/sonar-web/src/main/js/components/controls/InputValidationField.tsx +++ b/server/sonar-web/src/main/js/components/controls/InputValidationField.tsx @@ -31,8 +31,8 @@ interface Props { id?: string; label?: React.ReactNode; name: string; - onBlur: (event: React.FocusEvent<any>) => void; - onChange: (event: React.ChangeEvent<any>) => void; + onBlur: (event: React.FocusEvent<HTMLInputElement>) => void; + onChange: (event: React.ChangeEvent<HTMLInputElement>) => void; placeholder?: string; touched: boolean | undefined; type?: string; diff --git a/server/sonar-web/src/main/js/components/controls/RadioToggle.tsx b/server/sonar-web/src/main/js/components/controls/RadioToggle.tsx index 2ccf856ce30..0af42bbca83 100644 --- a/server/sonar-web/src/main/js/components/controls/RadioToggle.tsx +++ b/server/sonar-web/src/main/js/components/controls/RadioToggle.tsx @@ -46,7 +46,7 @@ export default class RadioToggle extends React.PureComponent<Props> { renderOption = (option: Option) => { const checked = option.value === this.props.value; - const htmlId = this.props.name + '__' + option.value; + const htmlId = `${this.props.name}__${option.value}`; return ( <li key={option.value.toString()}> <input diff --git a/server/sonar-web/src/main/js/components/controls/ScreenPositionFixer.tsx b/server/sonar-web/src/main/js/components/controls/ScreenPositionFixer.tsx index 6aa05387ac5..8c24f24a730 100644 --- a/server/sonar-web/src/main/js/components/controls/ScreenPositionFixer.tsx +++ b/server/sonar-web/src/main/js/components/controls/ScreenPositionFixer.tsx @@ -43,6 +43,8 @@ interface Fixes { topFix?: number; } +const EDGE_MARGIN = rawSizes.grid / 2; + export default class ScreenPositionFixer extends React.Component<Props, Fixes> { throttledPosition: () => void; @@ -82,8 +84,6 @@ export default class ScreenPositionFixer extends React.Component<Props, Fixes> { }; position = () => { - const edgeMargin = 0.5 * rawSizes.grid; - // eslint-disable-next-line react/no-find-dom-node const node = findDOMNode(this); if (node && node instanceof Element) { @@ -91,17 +91,17 @@ export default class ScreenPositionFixer extends React.Component<Props, Fixes> { const { clientHeight, clientWidth } = document.documentElement; let leftFix = 0; - if (left < edgeMargin) { - leftFix = edgeMargin - left; - } else if (left + width > clientWidth - edgeMargin) { - leftFix = clientWidth - edgeMargin - left - width; + if (left < EDGE_MARGIN) { + leftFix = EDGE_MARGIN - left; + } else if (left + width > clientWidth - EDGE_MARGIN) { + leftFix = clientWidth - EDGE_MARGIN - left - width; } let topFix = 0; - if (top < edgeMargin) { - topFix = edgeMargin - top; - } else if (top + height > clientHeight - edgeMargin) { - topFix = clientHeight - edgeMargin - top - height; + if (top < EDGE_MARGIN) { + topFix = EDGE_MARGIN - top; + } else if (top + height > clientHeight - EDGE_MARGIN) { + topFix = clientHeight - EDGE_MARGIN - top - height; } this.setState({ leftFix, topFix }); diff --git a/server/sonar-web/src/main/js/components/controls/SearchBox.tsx b/server/sonar-web/src/main/js/components/controls/SearchBox.tsx index d03081e26bc..476b8f93b7e 100644 --- a/server/sonar-web/src/main/js/components/controls/SearchBox.tsx +++ b/server/sonar-web/src/main/js/components/controls/SearchBox.tsx @@ -135,7 +135,11 @@ export default class SearchBox extends React.PureComponent<Props, State> { <div className={classNames('search-box', this.props.className)} id={this.props.id} - title={tooShort ? translateWithParameters('select2.tooShort', minLength!) : ''}> + title={ + tooShort && minLength !== undefined + ? translateWithParameters('select2.tooShort', minLength) + : '' + }> <input aria-label={translate('search_verb')} autoComplete="off" @@ -165,9 +169,9 @@ export default class SearchBox extends React.PureComponent<Props, State> { /> )} - {tooShort && ( + {tooShort && minLength !== undefined && ( <span className="search-box-note"> - {translateWithParameters('select2.tooShort', minLength!)} + {translateWithParameters('select2.tooShort', minLength)} </span> )} </div> diff --git a/server/sonar-web/src/main/js/components/controls/Select.tsx b/server/sonar-web/src/main/js/components/controls/Select.tsx index 44657a87991..cc345e12677 100644 --- a/server/sonar-web/src/main/js/components/controls/Select.tsx +++ b/server/sonar-web/src/main/js/components/controls/Select.tsx @@ -18,8 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { - defaultFilterOptions as reactSelectDefaultFilterOptions, +import ReactSelectClass, { ReactAsyncSelectProps, ReactCreatableSelectProps, ReactSelectProps @@ -28,10 +27,6 @@ import { lazyLoadComponent } from '../lazyLoadComponent'; import { ClearButton } from './buttons'; import './Select.css'; -declare module 'react-select' { - export function defaultFilterOptions(...args: any[]): any; -} - const ReactSelectLib = import('react-select'); const ReactSelect = lazyLoadComponent(() => ReactSelectLib); const ReactCreatable = lazyLoadComponent(() => @@ -43,30 +38,22 @@ function renderInput() { return <ClearButton className="button-tiny spacer-left text-middle" iconProps={{ size: 12 }} />; } -interface WithInnerRef { - innerRef?: (element: React.Component) => void; +export interface WithInnerRef { + innerRef?: React.Ref<ReactSelectClass<unknown>>; } export default function Select({ innerRef, ...props }: WithInnerRef & ReactSelectProps) { - // TODO try to define good defaults, if any - // ReactSelect doesn't declare `clearRenderer` prop - const ReactSelectAny = ReactSelect as any; // hide the "x" icon when select is empty const clearable = props.clearable ? Boolean(props.value) : false; return ( - <ReactSelectAny {...props} clearable={clearable} clearRenderer={renderInput} ref={innerRef} /> + <ReactSelect {...props} clearable={clearable} clearRenderer={renderInput} ref={innerRef} /> ); } -export const defaultFilterOptions = reactSelectDefaultFilterOptions; - export function Creatable(props: ReactCreatableSelectProps) { - // ReactSelect doesn't declare `clearRenderer` prop - const ReactCreatableAny = ReactCreatable as any; - return <ReactCreatableAny {...props} clearRenderer={renderInput} />; + return <ReactCreatable {...props} clearRenderer={renderInput} />; } -// TODO figure out why `ref` prop is incompatible -export function AsyncSelect(props: ReactAsyncSelectProps & { ref?: any }) { +export function AsyncSelect(props: ReactAsyncSelectProps & WithInnerRef) { return <ReactAsync {...props} />; } diff --git a/server/sonar-web/src/main/js/components/controls/Tooltip.css b/server/sonar-web/src/main/js/components/controls/Tooltip.css index d49494f1f77..3b03e423792 100644 --- a/server/sonar-web/src/main/js/components/controls/Tooltip.css +++ b/server/sonar-web/src/main/js/components/controls/Tooltip.css @@ -56,9 +56,6 @@ border-radius: 4px; overflow: hidden; word-break: break-word; -} - -.tooltip-inner { padding: 12px 17px; color: #fff; background-color: #475760; diff --git a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx index 802541ad1bc..79facbd7945 100644 --- a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx +++ b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx @@ -334,7 +334,7 @@ export class TooltipInner extends React.Component<TooltipProps, State> { } const { classNameSpace = 'tooltip' } = this.props; - const placement = this.getPlacement(); + const currentPlacement = this.getPlacement(); const style = isMeasured(this.state) ? { left: this.state.left + leftFix, @@ -346,7 +346,7 @@ export class TooltipInner extends React.Component<TooltipProps, State> { return ( <div - className={`${classNameSpace} ${placement}`} + className={`${classNameSpace} ${currentPlacement}`} onMouseEnter={this.handleOverlayMouseEnter} onMouseLeave={this.handleOverlayMouseLeave} ref={this.tooltipNodeRef} @@ -356,7 +356,7 @@ export class TooltipInner extends React.Component<TooltipProps, State> { className={`${classNameSpace}-arrow`} style={ isMeasured(this.state) - ? this.adjustArrowPosition(placement, { leftFix, topFix }) + ? this.adjustArrowPosition(currentPlacement, { leftFix, topFix }) : undefined } /> diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/Select-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/Select-test.tsx new file mode 100644 index 00000000000..2b3bd985de7 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/Select-test.tsx @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { ReactAsyncSelectProps, ReactCreatableSelectProps, ReactSelectProps } from 'react-select'; +import Select, { AsyncSelect, Creatable, WithInnerRef } from '../Select'; + +describe('Select', () => { + it('should render correctly', () => { + return new Promise<void>((resolve, reject) => { + expect(shallowRender()).toMatchSnapshot('default'); + expect(shallowRender({ clearable: true, value: undefined })).toMatchSnapshot( + 'disable clear button if no value' + ); + + const clearRenderFn = shallowRender().props().clearRenderer; + if (!clearRenderFn) { + reject(); + return; + } + expect(clearRenderFn()).toMatchSnapshot('clear button'); + + resolve(); + }); + }); + + function shallowRender(props: Partial<WithInnerRef & ReactSelectProps> = {}) { + return shallow<WithInnerRef & ReactSelectProps>(<Select value="foo" {...props} />); + } +}); + +describe('Creatable', () => { + it('should render correctly', () => { + return new Promise<void>((resolve, reject) => { + expect(shallowRender()).toMatchSnapshot('default'); + + const clearRenderFn = shallowRender().props().clearRenderer; + if (!clearRenderFn) { + reject(); + return; + } + expect(clearRenderFn()).toMatchSnapshot('clear button'); + + resolve(); + }); + }); + + function shallowRender(props: Partial<ReactCreatableSelectProps> = {}) { + return shallow<ReactCreatableSelectProps>(<Creatable {...props} />); + } +}); + +describe('AsyncSelect', () => { + it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('default'); + }); + + function shallowRender(props: Partial<WithInnerRef & ReactAsyncSelectProps> = {}) { + return shallow<WithInnerRef & ReactAsyncSelectProps>( + <AsyncSelect loadOptions={jest.fn()} {...props} /> + ); + } +}); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Select-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Select-test.tsx.snap new file mode 100644 index 00000000000..c96f416934c --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Select-test.tsx.snap @@ -0,0 +1,50 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AsyncSelect should render correctly: default 1`] = ` +<LazyComponentWrapper + loadOptions={[MockFunction]} +/> +`; + +exports[`Creatable should render correctly: clear button 1`] = ` +<ClearButton + className="button-tiny spacer-left text-middle" + iconProps={ + Object { + "size": 12, + } + } +/> +`; + +exports[`Creatable should render correctly: default 1`] = ` +<LazyComponentWrapper + clearRenderer={[Function]} +/> +`; + +exports[`Select should render correctly: clear button 1`] = ` +<ClearButton + className="button-tiny spacer-left text-middle" + iconProps={ + Object { + "size": 12, + } + } +/> +`; + +exports[`Select should render correctly: default 1`] = ` +<LazyComponentWrapper + clearRenderer={[Function]} + clearable={false} + value="foo" +/> +`; + +exports[`Select should render correctly: disable clear button if no value 1`] = ` +<LazyComponentWrapper + clearRenderer={[Function]} + clearable={false} +/> +`; diff --git a/server/sonar-web/src/main/js/components/controls/buttons.tsx b/server/sonar-web/src/main/js/components/controls/buttons.tsx index ec5b58dbb74..ff4bd8acdc0 100644 --- a/server/sonar-web/src/main/js/components/controls/buttons.tsx +++ b/server/sonar-web/src/main/js/components/controls/buttons.tsx @@ -41,7 +41,7 @@ interface ButtonProps extends AllowedButtonAttributes { onClick?: () => void; preventDefault?: boolean; stopPropagation?: boolean; - type?: 'button' | 'submit' | 'reset' | undefined; + type?: 'button' | 'submit' | 'reset'; } export class Button extends React.PureComponent<ButtonProps> { diff --git a/server/sonar-web/src/main/js/components/icons/OnboardingAddMembersIcon.tsx b/server/sonar-web/src/main/js/components/icons/OnboardingAddMembersIcon.tsx deleted file mode 100644 index 919affa1373..00000000000 --- a/server/sonar-web/src/main/js/components/icons/OnboardingAddMembersIcon.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2021 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { colors } from '../../app/theme'; -import Icon, { IconProps } from './Icon'; - -export default function OnboardingAddMembersIcon({ fill, size = 64, ...iconProps }: IconProps) { - return ( - <Icon height={(size / 64) * 80} viewBox="0 0 64 80" width={size} {...iconProps}> - <g> - <path - d="M49 34c0 9.389-7.611 17-17 17s-17-7.611-17-17 7.611-17 17-17 17 7.611 17 17z" - style={{ fill: 'none', stroke: fill || colors.darkBlue, strokeWidth: 2 }} - /> - <path - d="M36 32c0 2.2-1.8 4-4 4s-4-1.8-4-4v-1c0-2.2 1.8-4 4-4s4 1.8 4 4v1zm4 39a8 8 0 1 1-16 0 8 8 0 0 1 16 0z" - style={{ fill: 'none', stroke: fill || colors.darkBlue, strokeWidth: 2 }} - /> - <path - d="M33 70h2v2h-2v2h-2v-2h-2v-2h2v-2h2v2zm-5-14l-.072-.001c-1.521-.054-2.834-1.337-2.925-2.855L25 50h2c0 1.745-.532 3.91.952 3.999L28 54h8v.002l.072-.005c.506-.042.922-.489.928-1.003V50h2c0 1.024.011 2.048-.001 3.072-.054 1.518-1.337 2.834-2.855 2.925l-.072.002L36 56v8h-2v-7.982c-1.333.007-2.667.007-4 0V64h-2v-8zm-7 0H1V10 0h62v56H43v-2h18V10H3v44h18v2zm38-4H43v-2h14V14H7v36h14v2H5V12h54v40zm-19-9l1 .017c-.03 1.79-2.454 2.506-3.918 2.717-4.074.584-8.503.911-12.176-.477-.949-.358-1.887-1.119-1.906-2.24l.191-.017H23v-3.566l5.38-3.228.913-.913 1.414 1.414-1.087 1.087L25 40.566v2.438c.067 1.304 10.98 2.117 13.844.157.076-.052.152-.172.156-.178v-2.417l-4.62-2.772-1.087-1.087 1.414-1.414.913.913L41 39.434V43h-1zm14-4h-2v-2h2v2zm-42 0h-2v-2h2v2zm42-4h-2v-2h2v2zm-42 0h-2v-2h2v2zm42-4h-2v-2h2v2zm-42 0h-2v-2h2v2zm20.198-10.999c3.529.062 6.837 1.669 9.386 4.169l-1.289 1.539c-4.178-4.152-11.167-5.254-16.359-.228l-.231.228-1.41-1.418c2.633-2.617 6.031-4.313 9.903-4.29zM3 2v6h58V2H3zm56 4H17V4h42v2zM11 6H9V4h2v2zM7 6H5V4h2v2zm8 0h-2V4h2v2z" - style={{ fill: fill || colors.darkBlue, fillRule: 'nonzero' }} - /> - </g> - </Icon> - ); -} diff --git a/server/sonar-web/src/main/js/components/icons/OnboardingTeamIcon.tsx b/server/sonar-web/src/main/js/components/icons/OnboardingTeamIcon.tsx deleted file mode 100644 index 4bb4b12fd2d..00000000000 --- a/server/sonar-web/src/main/js/components/icons/OnboardingTeamIcon.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2021 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { colors } from '../../app/theme'; -import Icon, { IconProps } from './Icon'; - -export default function OnboardingTeamIcon({ fill, size = 64, ...iconProps }: IconProps) { - return ( - <Icon size={size} viewBox="0 0 64 64" {...iconProps}> - <g fill="none" fillRule="evenodd" stroke={fill || colors.darkBlue} strokeWidth="2"> - <path d="M32 9v5M11.5195 43.0898l7.48-4.091m33.481-18.0994l-7.48 4.1m-33.481-4.1l7.48 4.1M45 38.999l7.48 4.101M32 50v5m15-23c0 8.284-6.715 15-15 15s-15-6.716-15-15c0-8.285 6.715-15 15-15s15 6.715 15 15z" /> - <path d="M40 38c0 1.656-3.58 2-8 2s-8-.344-8-2m16 0v-3l-5-3-1-1m-10 7v-3l5-3 1-1m6-4c0 2.2-1.8 4-4 4s-4-1.8-4-4v-1c0-2.2 1.8-4 4-4s4 1.8 4 4v1zm-.0098-21.71c7.18 1.069 13.439 4.96 17.609 10.51m-17.609 42.91c7.18-1.07 13.439-4.96 17.609-10.51M6.6299 41.25c-1.06-2.88-1.63-6-1.63-9.25s.57-6.37 1.63-9.25m3.7705-6.9502c4.17-5.55 10.43-9.44 17.609-10.51m-17.609 42.9104c4.17 5.55 10.43 9.439 17.609 10.51M57.3701 22.75c1.06 2.88 1.63 6 1.63 9.25s-.57 6.37-1.63 9.25" /> - <path d="M36 5c0 2.209-1.79 4-4 4-2.209 0-4-1.791-4-4 0-2.21 1.791-4 4-4 2.21 0 4 1.79 4 4zm-5 0h2M12 19c0 2.209-1.79 4-4 4-2.209 0-4-1.791-4-4 0-2.21 1.791-4 4-4 2.21 0 4 1.79 4 4zm-5 0h2m51 0c0 2.209-1.79 4-4 4-2.209 0-4-1.791-4-4 0-2.21 1.791-4 4-4 2.21 0 4 1.79 4 4zm-5 0h2M12 45c0 2.209-1.79 4-4 4-2.209 0-4-1.791-4-4 0-2.21 1.791-4 4-4 2.21 0 4 1.79 4 4zm-5 0h2m51 0c0 2.209-1.79 4-4 4-2.209 0-4-1.791-4-4 0-2.21 1.791-4 4-4 2.21 0 4 1.79 4 4zm-5 0h2M36 59c0 2.209-1.79 4-4 4-2.209 0-4-1.791-4-4 0-2.21 1.791-4 4-4 2.21 0 4 1.79 4 4zm-5 0h2" /> - </g> - </Icon> - ); -} diff --git a/server/sonar-web/src/main/js/components/icons/SeverityIcon.tsx b/server/sonar-web/src/main/js/components/icons/SeverityIcon.tsx index 6fc6042d705..044ed7edca0 100644 --- a/server/sonar-web/src/main/js/components/icons/SeverityIcon.tsx +++ b/server/sonar-web/src/main/js/components/icons/SeverityIcon.tsx @@ -38,8 +38,8 @@ export default function SeverityIcon({ severity, ...iconProps }: Props) { return null; } - const Icon = severityIcons[severity.toLowerCase()]; - return Icon ? <Icon {...iconProps} /> : null; + const DesiredIcon = severityIcons[severity.toLowerCase()]; + return DesiredIcon ? <DesiredIcon {...iconProps} /> : null; } function BlockerSeverityIcon(iconProps: IconProps) { diff --git a/server/sonar-web/src/main/js/components/icons/StatusIcon.tsx b/server/sonar-web/src/main/js/components/icons/StatusIcon.tsx index 06b6e01ebe5..713a92cdcec 100644 --- a/server/sonar-web/src/main/js/components/icons/StatusIcon.tsx +++ b/server/sonar-web/src/main/js/components/icons/StatusIcon.tsx @@ -37,8 +37,8 @@ const statusIcons: T.Dict<(props: IconProps) => React.ReactElement> = { }; export default function StatusIcon({ status, ...iconProps }: Props) { - const Icon = statusIcons[status.toLowerCase()]; - return Icon ? <Icon {...iconProps} /> : null; + const DesiredStatusIcon = statusIcons[status.toLowerCase()]; + return DesiredStatusIcon ? <DesiredStatusIcon {...iconProps} /> : null; } function OpenStatusIcon(iconProps: IconProps) { diff --git a/server/sonar-web/src/main/js/components/icons/TestStatusIcon.tsx b/server/sonar-web/src/main/js/components/icons/TestStatusIcon.tsx index f7bd5ff5843..6555744c948 100644 --- a/server/sonar-web/src/main/js/components/icons/TestStatusIcon.tsx +++ b/server/sonar-web/src/main/js/components/icons/TestStatusIcon.tsx @@ -33,8 +33,8 @@ const statusIcons: T.Dict<(props: IconProps) => React.ReactElement> = { }; export default function TestStatusIcon({ status, ...iconProps }: Props) { - const Icon = statusIcons[status.toLowerCase()]; - return Icon ? <Icon {...iconProps} /> : null; + const DesiredStatusIcon = statusIcons[status.toLowerCase()]; + return DesiredStatusIcon ? <DesiredStatusIcon {...iconProps} /> : null; } function OkTestStatusIcon(iconProps: IconProps) { diff --git a/server/sonar-web/src/main/js/components/ui/Alert.tsx b/server/sonar-web/src/main/js/components/ui/Alert.tsx index 2a63bad32ad..0a86b415a9c 100644 --- a/server/sonar-web/src/main/js/components/ui/Alert.tsx +++ b/server/sonar-web/src/main/js/components/ui/Alert.tsx @@ -44,12 +44,15 @@ interface AlertVariantInformation { backGroundColor: string; } +const DOUBLE = 2; +const QUADRUPLE = 4; + const StyledAlertIcon = styled.div<{ isBanner: boolean; variantInfo: AlertVariantInformation }>` flex: 0 0 auto; display: flex; justify-content: center; align-items: center; - width: calc(${({ isBanner }) => (isBanner ? 2 : 4)} * ${sizes.gridSize}); + width: calc(${({ isBanner }) => (isBanner ? DOUBLE : QUADRUPLE)} * ${sizes.gridSize}); border-right: ${({ isBanner }) => (!isBanner ? '1px solid' : 'none')}; border-color: ${({ variantInfo }) => variantInfo.borderColor}; `; diff --git a/server/sonar-web/src/main/js/components/ui/ContextNavBar.tsx b/server/sonar-web/src/main/js/components/ui/ContextNavBar.tsx index 07a75d15ae5..e81fbf8bbce 100644 --- a/server/sonar-web/src/main/js/components/ui/ContextNavBar.tsx +++ b/server/sonar-web/src/main/js/components/ui/ContextNavBar.tsx @@ -20,12 +20,12 @@ import * as classNames from 'classnames'; import * as React from 'react'; import './ContextNavBar.css'; -import NavBar from './NavBar'; +import NavBar, { NavBarProps } from './NavBar'; -interface Props { +interface Props extends NavBarProps { className?: string; + id?: string; height: number; - [attr: string]: any; } export default function ContextNavBar({ className, ...other }: Props) { diff --git a/server/sonar-web/src/main/js/components/ui/NavBar.css b/server/sonar-web/src/main/js/components/ui/NavBar.css index c9e5757121d..40b364d2d75 100644 --- a/server/sonar-web/src/main/js/components/ui/NavBar.css +++ b/server/sonar-web/src/main/js/components/ui/NavBar.css @@ -23,9 +23,6 @@ box-sizing: border-box; } -.navbar { -} - .navbar-inner { position: fixed; left: 0; diff --git a/server/sonar-web/src/main/js/components/ui/NavBarTabs.tsx b/server/sonar-web/src/main/js/components/ui/NavBarTabs.tsx index 0cc09e5abc6..d10bb7b4446 100644 --- a/server/sonar-web/src/main/js/components/ui/NavBarTabs.tsx +++ b/server/sonar-web/src/main/js/components/ui/NavBarTabs.tsx @@ -21,10 +21,9 @@ import * as classNames from 'classnames'; import * as React from 'react'; import './NavBarTabs.css'; -interface Props { - children?: any; +interface Props extends React.HTMLAttributes<HTMLUListElement> { + children?: React.ReactNode; className?: string; - [attr: string]: any; } export default function NavBarTabs({ children, className, ...other }: Props) { diff --git a/server/sonar-web/src/main/js/components/icons/OnboardingProjectIcon.tsx b/server/sonar-web/src/main/js/helpers/mocks/dom.ts index 36aaaec1dbc..71e666f3f5f 100644 --- a/server/sonar-web/src/main/js/components/icons/OnboardingProjectIcon.tsx +++ b/server/sonar-web/src/main/js/helpers/mocks/dom.ts @@ -17,18 +17,20 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import * as React from 'react'; -import { colors } from '../../app/theme'; -import Icon, { IconProps } from './Icon'; -export default function OnboardingProjectIcon({ fill, size = 64, ...iconProps }: IconProps) { - return ( - <Icon size={size} viewBox="0 0 64 64" {...iconProps}> - <g fill="none" fillRule="evenodd" stroke={fill || colors.darkBlue} strokeWidth="2"> - <path d="M2 59h60V13H2zm0-46h60V5H2zm3-4h2m2 0h2m2 0h2m2 0h42" /> - <path d="M59 34h-6l-2-4h-6l-2 5h-6l-2 2h-6l-2-4h-6l-2 5h-6l-2 4H5m1 14v-9m4 9v-6m4 6V43m4 13V45m4 11V42m4 14V39m4 17V41m4 15V46m4 10V40m4 16V44m4 12V37m4 19V38m4 18V43m4 13V39m-3-18h-2m-2 0h-2m-2 0h-2M9 29h14M9 33h7m17-12h8m-14 4h8m-8-4h4m-21 4h12v-4H10z" /> - <path d="M58 31V17H6v22" /> - </g> - </Icon> - ); +export function mockHtmlElement<T extends Element>(overrides: Partial<T> = {}): T { + return { + getBoundingClientRect: () => ({ + bottom: 0, + height: 100, + width: 50, + left: 0, + right: 0, + top: 10, + x: 12, + y: 23, + toJSON: () => '' + }), + ...overrides + } as T; } diff --git a/server/sonar-web/src/main/js/helpers/pages.ts b/server/sonar-web/src/main/js/helpers/pages.ts index 40c686e43fa..f56c4faaa11 100644 --- a/server/sonar-web/src/main/js/helpers/pages.ts +++ b/server/sonar-web/src/main/js/helpers/pages.ts @@ -49,7 +49,7 @@ export function removeNoFooterPageClass() { toggleBodyClass(CLASS_NO_FOOTER_PAGE, false); } -function toggleBodyClass(className: string, force?: boolean) { +function toggleBodyClass(className: string, force: boolean) { document.body.classList.toggle(className, force); if (document.documentElement) { document.documentElement.classList.toggle(className, force); |