Browse Source

Fix multiple Code Smells

tags/9.1.0.47736
Wouter Admiraal 2 years ago
parent
commit
52b02379a6
31 changed files with 616 additions and 391 deletions
  1. 9
    11
      server/sonar-web/src/main/js/components/charts/BubbleChart.tsx
  2. 3
    3
      server/sonar-web/src/main/js/components/charts/Histogram.tsx
  3. 6
    3
      server/sonar-web/src/main/js/components/charts/ZoomTimeLine.tsx
  4. 117
    25
      server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx
  5. 34
    36
      server/sonar-web/src/main/js/components/charts/__tests__/Histogram-test.tsx
  6. 87
    151
      server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap
  7. 160
    4
      server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/Histogram-test.tsx.snap
  8. 3
    1
      server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx
  9. 0
    3
      server/sonar-web/src/main/js/components/controls/Dropdown.tsx
  10. 5
    5
      server/sonar-web/src/main/js/components/controls/GlobalMessages.tsx
  11. 2
    2
      server/sonar-web/src/main/js/components/controls/InputValidationField.tsx
  12. 1
    1
      server/sonar-web/src/main/js/components/controls/RadioToggle.tsx
  13. 10
    10
      server/sonar-web/src/main/js/components/controls/ScreenPositionFixer.tsx
  14. 7
    3
      server/sonar-web/src/main/js/components/controls/SearchBox.tsx
  15. 6
    19
      server/sonar-web/src/main/js/components/controls/Select.tsx
  16. 0
    3
      server/sonar-web/src/main/js/components/controls/Tooltip.css
  17. 3
    3
      server/sonar-web/src/main/js/components/controls/Tooltip.tsx
  18. 81
    0
      server/sonar-web/src/main/js/components/controls/__tests__/Select-test.tsx
  19. 50
    0
      server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Select-test.tsx.snap
  20. 1
    1
      server/sonar-web/src/main/js/components/controls/buttons.tsx
  21. 0
    43
      server/sonar-web/src/main/js/components/icons/OnboardingAddMembersIcon.tsx
  22. 0
    34
      server/sonar-web/src/main/js/components/icons/OnboardingTeamIcon.tsx
  23. 2
    2
      server/sonar-web/src/main/js/components/icons/SeverityIcon.tsx
  24. 2
    2
      server/sonar-web/src/main/js/components/icons/StatusIcon.tsx
  25. 2
    2
      server/sonar-web/src/main/js/components/icons/TestStatusIcon.tsx
  26. 4
    1
      server/sonar-web/src/main/js/components/ui/Alert.tsx
  27. 3
    3
      server/sonar-web/src/main/js/components/ui/ContextNavBar.tsx
  28. 0
    3
      server/sonar-web/src/main/js/components/ui/NavBar.css
  29. 2
    3
      server/sonar-web/src/main/js/components/ui/NavBarTabs.tsx
  30. 15
    13
      server/sonar-web/src/main/js/helpers/mocks/dom.ts
  31. 1
    1
      server/sonar-web/src/main/js/helpers/pages.ts

+ 9
- 11
server/sonar-web/src/main/js/components/charts/BubbleChart.tsx View File

@@ -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);
}
};

+ 3
- 3
server/sonar-web/src/main/js/components/charts/Histogram.tsx View File

@@ -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 (

+ 6
- 3
server/sonar-web/src/main/js/components/charts/ZoomTimeLine.tsx View File

@@ -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]));

+ 117
- 25
server/sonar-web/src/main/js/components/charts/__tests__/BubbleChart-test.tsx View File

@@ -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}
/>
);
}

+ 34
- 36
server/sonar-web/src/main/js/components/charts/__tests__/Histogram-test.tsx View File

@@ -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} />);
}

+ 87
- 151
server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/BubbleChart-test.tsx.snap View File

@@ -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>
`;

+ 160
- 4
server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/Histogram-test.tsx.snap View File

@@ -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}

+ 3
- 1
server/sonar-web/src/main/js/components/controls/ConfirmModal.tsx View File

@@ -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;

+ 0
- 3
server/sonar-web/src/main/js/components/controls/Dropdown.tsx View File

@@ -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;

+ 5
- 5
server/sonar-web/src/main/js/components/controls/GlobalMessages.tsx View File

@@ -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);

+ 2
- 2
server/sonar-web/src/main/js/components/controls/InputValidationField.tsx View File

@@ -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;

+ 1
- 1
server/sonar-web/src/main/js/components/controls/RadioToggle.tsx View File

@@ -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

+ 10
- 10
server/sonar-web/src/main/js/components/controls/ScreenPositionFixer.tsx View File

@@ -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 });

+ 7
- 3
server/sonar-web/src/main/js/components/controls/SearchBox.tsx View File

@@ -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>

+ 6
- 19
server/sonar-web/src/main/js/components/controls/Select.tsx View File

@@ -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} />;
}

+ 0
- 3
server/sonar-web/src/main/js/components/controls/Tooltip.css View File

@@ -56,9 +56,6 @@
border-radius: 4px;
overflow: hidden;
word-break: break-word;
}

.tooltip-inner {
padding: 12px 17px;
color: #fff;
background-color: #475760;

+ 3
- 3
server/sonar-web/src/main/js/components/controls/Tooltip.tsx View File

@@ -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
}
/>

+ 81
- 0
server/sonar-web/src/main/js/components/controls/__tests__/Select-test.tsx View File

@@ -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} />
);
}
});

+ 50
- 0
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Select-test.tsx.snap View File

@@ -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}
/>
`;

+ 1
- 1
server/sonar-web/src/main/js/components/controls/buttons.tsx View File

@@ -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> {

+ 0
- 43
server/sonar-web/src/main/js/components/icons/OnboardingAddMembersIcon.tsx View File

@@ -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>
);
}

+ 0
- 34
server/sonar-web/src/main/js/components/icons/OnboardingTeamIcon.tsx View File

@@ -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>
);
}

+ 2
- 2
server/sonar-web/src/main/js/components/icons/SeverityIcon.tsx View File

@@ -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) {

+ 2
- 2
server/sonar-web/src/main/js/components/icons/StatusIcon.tsx View File

@@ -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) {

+ 2
- 2
server/sonar-web/src/main/js/components/icons/TestStatusIcon.tsx View File

@@ -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) {

+ 4
- 1
server/sonar-web/src/main/js/components/ui/Alert.tsx View File

@@ -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};
`;

+ 3
- 3
server/sonar-web/src/main/js/components/ui/ContextNavBar.tsx View File

@@ -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) {

+ 0
- 3
server/sonar-web/src/main/js/components/ui/NavBar.css View File

@@ -23,9 +23,6 @@
box-sizing: border-box;
}

.navbar {
}

.navbar-inner {
position: fixed;
left: 0;

+ 2
- 3
server/sonar-web/src/main/js/components/ui/NavBarTabs.tsx View File

@@ -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) {

server/sonar-web/src/main/js/components/icons/OnboardingProjectIcon.tsx → server/sonar-web/src/main/js/helpers/mocks/dom.ts View File

@@ -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;
}

+ 1
- 1
server/sonar-web/src/main/js/helpers/pages.ts View File

@@ -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);

Loading…
Cancel
Save