@pie-lib/tools 0.29.2-next.0 → 0.29.2-next.164
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -70
- package/esm/index.css +847 -0
- package/esm/index.js +13155 -0
- package/esm/index.js.map +1 -0
- package/esm/package.json +3 -0
- package/lib/anchor-utils.js +18 -74
- package/lib/anchor-utils.js.map +1 -1
- package/lib/anchor.js +20 -28
- package/lib/anchor.js.map +1 -1
- package/lib/index.js +1 -11
- package/lib/index.js.map +1 -1
- package/lib/protractor/graphic.js +68 -105
- package/lib/protractor/graphic.js.map +1 -1
- package/lib/protractor/index.js +35 -65
- package/lib/protractor/index.js.map +1 -1
- package/lib/rotatable.js +73 -141
- package/lib/rotatable.js.map +1 -1
- package/lib/ruler/graphic.js +29 -66
- package/lib/ruler/graphic.js.map +1 -1
- package/lib/ruler/index.js +43 -75
- package/lib/ruler/index.js.map +1 -1
- package/lib/ruler/unit-type.js +19 -36
- package/lib/ruler/unit-type.js.map +1 -1
- package/lib/ruler/unit.js +51 -88
- package/lib/ruler/unit.js.map +1 -1
- package/lib/style-utils.js +2 -9
- package/lib/style-utils.js.map +1 -1
- package/lib/transform-origin.js +2 -13
- package/lib/transform-origin.js.map +1 -1
- package/package.json +20 -10
- package/src/__tests__/rotatable.test.jsx +84 -41
- package/src/anchor.jsx +15 -16
- package/src/protractor/__tests__/graphic.test.jsx +57 -6
- package/src/protractor/__tests__/index.test.jsx +58 -6
- package/src/protractor/graphic.jsx +49 -54
- package/src/protractor/index.jsx +24 -22
- package/src/rotatable.jsx +23 -28
- package/src/ruler/__tests__/graphic.test.jsx +57 -16
- package/src/ruler/__tests__/index.test.jsx +70 -12
- package/src/ruler/__tests__/unit-type.test.jsx +59 -6
- package/src/ruler/__tests__/unit.test.jsx +61 -8
- package/src/ruler/graphic.jsx +11 -14
- package/src/ruler/index.jsx +25 -28
- package/src/ruler/unit-type.jsx +10 -9
- package/src/ruler/unit.jsx +25 -29
- package/src/__tests__/__snapshots__/rotatable.test.jsx.snap +0 -37
- package/src/protractor/__tests__/__snapshots__/graphic.test.jsx.snap +0 -1234
- package/src/protractor/__tests__/__snapshots__/index.test.jsx.snap +0 -40
- package/src/ruler/__tests__/__snapshots__/graphic.test.jsx.snap +0 -160
- package/src/ruler/__tests__/__snapshots__/index.test.jsx.snap +0 -45
- package/src/ruler/__tests__/__snapshots__/unit-type.test.jsx.snap +0 -12
- package/src/ruler/__tests__/__snapshots__/unit.test.jsx.snap +0 -30
|
@@ -1,13 +1,64 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
|
3
3
|
import { Graphic } from '../graphic';
|
|
4
4
|
import React from 'react';
|
|
5
5
|
|
|
6
6
|
describe('graphic', () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const theme = createTheme();
|
|
8
|
+
|
|
9
|
+
const renderComponent = (props = {}) => {
|
|
10
|
+
return render(
|
|
11
|
+
<ThemeProvider theme={theme}>
|
|
12
|
+
<Graphic {...props} />
|
|
13
|
+
</ThemeProvider>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('rendering', () => {
|
|
18
|
+
it('renders svg with correct viewBox', () => {
|
|
19
|
+
const { container } = renderComponent();
|
|
20
|
+
const svg = container.querySelector('svg');
|
|
21
|
+
expect(svg).toBeInTheDocument();
|
|
22
|
+
expect(svg).toHaveAttribute('viewBox', '0 0 102 61');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders path element', () => {
|
|
26
|
+
const { container } = renderComponent();
|
|
27
|
+
const path = container.querySelector('path');
|
|
28
|
+
expect(path).toBeInTheDocument();
|
|
29
|
+
expect(path).toHaveAttribute('d', 'M 1,50 A 1,1 0 0 1 100,50 L 100,60 L 1,60 Z');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('renders circle element', () => {
|
|
33
|
+
const { container } = renderComponent();
|
|
34
|
+
const circle = container.querySelector('circle');
|
|
35
|
+
expect(circle).toBeInTheDocument();
|
|
36
|
+
expect(circle).toHaveAttribute('r', '4');
|
|
37
|
+
expect(circle).toHaveAttribute('cx', '50.5');
|
|
38
|
+
expect(circle).toHaveAttribute('cy', '50');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('renders multiple line elements', () => {
|
|
42
|
+
const { container } = renderComponent();
|
|
43
|
+
const lines = container.querySelectorAll('line');
|
|
44
|
+
// Should render 181 angle lines + 2 crosshair lines = 183+ lines
|
|
45
|
+
expect(lines.length).toBeGreaterThan(180);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('renders text elements for angles', () => {
|
|
49
|
+
const { container } = renderComponent();
|
|
50
|
+
const texts = container.querySelectorAll('text');
|
|
51
|
+
// Should render text for angles 0, 10, 20, ..., 180 (19 texts)
|
|
52
|
+
expect(texts.length).toBeGreaterThan(15);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renders correct angle labels', () => {
|
|
56
|
+
const { container } = renderComponent();
|
|
57
|
+
const texts = container.querySelectorAll('text');
|
|
58
|
+
const textContents = Array.from(texts).map(t => t.textContent);
|
|
59
|
+
expect(textContents).toContain('0');
|
|
60
|
+
expect(textContents).toContain('90');
|
|
61
|
+
expect(textContents).toContain('180');
|
|
11
62
|
});
|
|
12
63
|
});
|
|
13
64
|
});
|
|
@@ -1,13 +1,65 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
|
3
3
|
import { Protractor } from '../index';
|
|
4
4
|
import React from 'react';
|
|
5
5
|
|
|
6
|
+
// Mock the Rotatable component to avoid complex DOM interactions
|
|
7
|
+
jest.mock('../../rotatable', () => {
|
|
8
|
+
return function MockRotatable({ children }) {
|
|
9
|
+
return <div data-testid="rotatable">{children}</div>;
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Mock the Graphic component
|
|
14
|
+
jest.mock('../graphic', () => {
|
|
15
|
+
return function MockGraphic() {
|
|
16
|
+
return <div data-testid="protractor-graphic" />;
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
6
20
|
describe('protractor', () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
21
|
+
const theme = createTheme();
|
|
22
|
+
|
|
23
|
+
const renderComponent = (props = {}) => {
|
|
24
|
+
const defaultProps = {
|
|
25
|
+
width: 450,
|
|
26
|
+
...props,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return render(
|
|
30
|
+
<ThemeProvider theme={theme}>
|
|
31
|
+
<Protractor {...defaultProps} />
|
|
32
|
+
</ThemeProvider>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe('rendering', () => {
|
|
37
|
+
it('renders protractor component', () => {
|
|
38
|
+
renderComponent();
|
|
39
|
+
expect(screen.getByTestId('rotatable')).toBeInTheDocument();
|
|
40
|
+
expect(screen.getByTestId('protractor-graphic')).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('renders with default width', () => {
|
|
44
|
+
const { container } = renderComponent();
|
|
45
|
+
const protractorDiv = container.querySelector('div[style*="width"]');
|
|
46
|
+
expect(protractorDiv).toHaveStyle({ width: '450px' });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders with custom width', () => {
|
|
50
|
+
const { container } = renderComponent({ width: 600 });
|
|
51
|
+
const protractorDiv = container.querySelector('div[style*="width"]');
|
|
52
|
+
expect(protractorDiv).toHaveStyle({ width: '600px' });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renders with custom startPosition', () => {
|
|
56
|
+
renderComponent({ startPosition: { left: 100, top: 200 } });
|
|
57
|
+
expect(screen.getByTestId('protractor-graphic')).toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('renders with className', () => {
|
|
61
|
+
const { container } = renderComponent({ className: 'custom-class' });
|
|
62
|
+
expect(screen.getByTestId('rotatable')).toBeInTheDocument();
|
|
11
63
|
});
|
|
12
64
|
});
|
|
13
65
|
});
|
|
@@ -1,58 +1,67 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { styled } from '@mui/material/styles';
|
|
3
3
|
import range from 'lodash/range';
|
|
4
|
-
import PropTypes from 'prop-types';
|
|
5
4
|
import { strokeColor, noSelect } from '../style-utils';
|
|
6
5
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<
|
|
6
|
+
const StyledLine = styled('line')(({ theme }) => ({
|
|
7
|
+
strokeWidth: '0.2',
|
|
8
|
+
stroke: strokeColor(theme),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
const Line = ({ angle, major, minor }) => (
|
|
12
|
+
<StyledLine
|
|
14
13
|
transform={`rotate(${angle}, 50.5,50)`}
|
|
15
|
-
className={classes.line}
|
|
16
14
|
style={{}}
|
|
17
15
|
x1="1"
|
|
18
16
|
x2={major ? 10 : minor ? 6 : 3}
|
|
19
17
|
y1="50"
|
|
20
18
|
y2="50"
|
|
21
19
|
/>
|
|
22
|
-
)
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const StyledSpikeLine = styled('line')(({ theme }) => ({
|
|
23
|
+
strokeWidth: '0.2',
|
|
24
|
+
stroke: strokeColor(theme),
|
|
25
|
+
}));
|
|
23
26
|
|
|
24
|
-
const Spike =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
stroke: strokeColor(theme),
|
|
28
|
-
},
|
|
29
|
-
}))(({ angle, classes }) => (
|
|
30
|
-
<line transform={`rotate(${angle}, 50.5,50)`} className={classes.line} style={{}} x1="15" x2={'46'} y1="50" y2="50" />
|
|
31
|
-
));
|
|
27
|
+
const Spike = ({ angle }) => (
|
|
28
|
+
<StyledSpikeLine transform={`rotate(${angle}, 50.5,50)`} style={{}} x1="15" x2={'46'} y1="50" y2="50" />
|
|
29
|
+
);
|
|
32
30
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<
|
|
31
|
+
const StyledText = styled('text')(({ theme }) => ({
|
|
32
|
+
fontSize: '2.5px',
|
|
33
|
+
textAnchor: 'middle',
|
|
34
|
+
fill: strokeColor(theme),
|
|
35
|
+
...noSelect(),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const Text = ({ angle }) => (
|
|
39
|
+
<StyledText transform={`rotate(${angle - 90}, 50.5, 50)`} x="50" y="12.5">
|
|
42
40
|
{angle}
|
|
43
|
-
</
|
|
44
|
-
)
|
|
41
|
+
</StyledText>
|
|
42
|
+
);
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
const StyledPath = styled('path')(({ theme }) => ({
|
|
45
|
+
strokeWidth: '0.2',
|
|
46
|
+
stroke: strokeColor(theme),
|
|
47
|
+
}));
|
|
50
48
|
|
|
49
|
+
const StyledGraphicLine = styled('line')(({ theme }) => ({
|
|
50
|
+
strokeWidth: '0.2',
|
|
51
|
+
stroke: strokeColor(theme),
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
const StyledCircle = styled('circle')(({ theme }) => ({
|
|
55
|
+
strokeWidth: '0.2',
|
|
56
|
+
stroke: strokeColor(theme),
|
|
57
|
+
fill: 'none',
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
export class Graphic extends React.PureComponent {
|
|
51
61
|
render() {
|
|
52
|
-
const { classes } = this.props;
|
|
53
62
|
return (
|
|
54
63
|
<svg viewBox="0 0 102 61">
|
|
55
|
-
<
|
|
64
|
+
<StyledPath d="M 1,50 A 1,1 0 0 1 100,50 L 100,60 L 1,60 Z" fill="none" />
|
|
56
65
|
{range(0, 181).map((r) => (
|
|
57
66
|
<Line minor={r % 5 === 0} major={r % 10 === 0} angle={r} key={r} />
|
|
58
67
|
))}
|
|
@@ -62,26 +71,12 @@ export class Graphic extends React.PureComponent {
|
|
|
62
71
|
<Text angle={r} />
|
|
63
72
|
</React.Fragment>
|
|
64
73
|
))}
|
|
65
|
-
<
|
|
66
|
-
<
|
|
67
|
-
<
|
|
74
|
+
<StyledCircle r="4" cx="50.5" cy="50" />
|
|
75
|
+
<StyledGraphicLine x1="48.5" x2="52.5" y1="50" y2="50" />
|
|
76
|
+
<StyledGraphicLine transform={'rotate(90 50.5 50)'} x1="48.5" x2="52.5" y1="50" y2="50" />
|
|
68
77
|
</svg>
|
|
69
78
|
);
|
|
70
79
|
}
|
|
71
80
|
}
|
|
72
81
|
|
|
73
|
-
export default
|
|
74
|
-
path: {
|
|
75
|
-
strokeWidth: '0.2',
|
|
76
|
-
stroke: strokeColor(theme),
|
|
77
|
-
},
|
|
78
|
-
line: {
|
|
79
|
-
strokeWidth: '0.2',
|
|
80
|
-
stroke: strokeColor(theme),
|
|
81
|
-
},
|
|
82
|
-
circle: {
|
|
83
|
-
strokeWidth: '0.2',
|
|
84
|
-
stroke: strokeColor(theme),
|
|
85
|
-
fill: 'none',
|
|
86
|
-
},
|
|
87
|
-
}))(Graphic);
|
|
82
|
+
export default Graphic;
|
package/src/protractor/index.jsx
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { styled } from '@mui/material/styles';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
import Graphic from './graphic';
|
|
5
5
|
import Anchor from '../anchor';
|
|
6
6
|
import Rotatable from '../rotatable';
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
const StyledProtractor = styled('div')(() => ({
|
|
9
|
+
position: 'relative',
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
const StyledLeftAnchor = styled(Anchor)(() => ({
|
|
13
|
+
position: 'absolute',
|
|
14
|
+
left: 0,
|
|
15
|
+
bottom: 0,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const StyledRightAnchor = styled(Anchor)(() => ({
|
|
19
|
+
position: 'absolute',
|
|
20
|
+
right: 0,
|
|
21
|
+
bottom: 0,
|
|
22
|
+
}));
|
|
8
23
|
|
|
9
24
|
export class Protractor extends React.Component {
|
|
10
25
|
static propTypes = {
|
|
11
|
-
classes: PropTypes.object.isRequired,
|
|
12
26
|
width: PropTypes.number.isRequired,
|
|
13
27
|
className: PropTypes.string,
|
|
14
28
|
startPosition: PropTypes.shape({
|
|
@@ -23,10 +37,10 @@ export class Protractor extends React.Component {
|
|
|
23
37
|
};
|
|
24
38
|
|
|
25
39
|
render() {
|
|
26
|
-
const {
|
|
40
|
+
const { width, startPosition } = this.props;
|
|
41
|
+
|
|
27
42
|
return (
|
|
28
43
|
<Rotatable
|
|
29
|
-
className={className}
|
|
30
44
|
startPosition={startPosition}
|
|
31
45
|
handle={[
|
|
32
46
|
{
|
|
@@ -39,27 +53,15 @@ export class Protractor extends React.Component {
|
|
|
39
53
|
},
|
|
40
54
|
]}
|
|
41
55
|
>
|
|
42
|
-
<
|
|
56
|
+
<StyledProtractor style={{ width: `${width}px` }}>
|
|
43
57
|
<Graphic />
|
|
44
58
|
|
|
45
|
-
<
|
|
46
|
-
<
|
|
47
|
-
</
|
|
59
|
+
<StyledLeftAnchor className="leftAnchor" />
|
|
60
|
+
<StyledRightAnchor className="rightAnchor" />
|
|
61
|
+
</StyledProtractor>
|
|
48
62
|
</Rotatable>
|
|
49
63
|
);
|
|
50
64
|
}
|
|
51
65
|
}
|
|
52
66
|
|
|
53
|
-
export default
|
|
54
|
-
protractor: { position: 'relative' },
|
|
55
|
-
leftAnchor: {
|
|
56
|
-
position: 'absolute',
|
|
57
|
-
left: 0,
|
|
58
|
-
bottom: 0,
|
|
59
|
-
},
|
|
60
|
-
rightAnchor: {
|
|
61
|
-
position: 'absolute',
|
|
62
|
-
right: 0,
|
|
63
|
-
bottom: 0,
|
|
64
|
-
},
|
|
65
|
-
}))(Protractor);
|
|
67
|
+
export default Protractor;
|
package/src/rotatable.jsx
CHANGED
|
@@ -1,44 +1,47 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import {
|
|
3
|
+
import { styled } from '@mui/material/styles';
|
|
4
4
|
import { getAnchor as calcAnchor, distanceBetween, arctangent } from './anchor-utils';
|
|
5
5
|
import { Portal } from 'react-portal';
|
|
6
6
|
import Point from '@mapbox/point-geometry';
|
|
7
7
|
import { parse as parseOrigin } from './transform-origin';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
})(({ classes, left, top, color, fill }) => {
|
|
8
|
+
|
|
9
|
+
const AnchorSvg = styled('svg')({
|
|
10
|
+
position: 'absolute',
|
|
11
|
+
zIndex: 100,
|
|
12
|
+
width: '200px',
|
|
13
|
+
height: '80px',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const Anchor = ({ left, top, color, fill }) => {
|
|
18
17
|
color = color || 'green';
|
|
19
18
|
fill = fill || 'white';
|
|
20
19
|
return (
|
|
21
20
|
<Portal>
|
|
22
|
-
<
|
|
23
|
-
className={classes.anchor}
|
|
21
|
+
<AnchorSvg
|
|
24
22
|
style={{
|
|
25
23
|
left: left - 10,
|
|
26
24
|
top: top - 10,
|
|
27
25
|
}}
|
|
28
26
|
>
|
|
29
27
|
<circle cx={10} cy={10} r={8} strokeWidth={1} stroke={color} fill={fill} />
|
|
30
|
-
</
|
|
28
|
+
</AnchorSvg>
|
|
31
29
|
</Portal>
|
|
32
30
|
);
|
|
33
|
-
}
|
|
31
|
+
};
|
|
34
32
|
|
|
35
33
|
/**
|
|
36
34
|
* Tip o' the hat to:
|
|
37
35
|
* https://bl.ocks.org/joyrexus/7207044
|
|
38
36
|
*/
|
|
37
|
+
const RotatableContainer = styled('div')({
|
|
38
|
+
position: 'relative',
|
|
39
|
+
display: 'inline-block',
|
|
40
|
+
cursor: 'move',
|
|
41
|
+
});
|
|
42
|
+
|
|
39
43
|
export class Rotatable extends React.Component {
|
|
40
44
|
static propTypes = {
|
|
41
|
-
classes: PropTypes.object.isRequired,
|
|
42
45
|
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
|
|
43
46
|
showAnchor: PropTypes.bool,
|
|
44
47
|
handle: PropTypes.arrayOf(
|
|
@@ -47,7 +50,6 @@ export class Rotatable extends React.Component {
|
|
|
47
50
|
origin: PropTypes.string,
|
|
48
51
|
}),
|
|
49
52
|
),
|
|
50
|
-
className: PropTypes.string,
|
|
51
53
|
startPosition: PropTypes.shape({
|
|
52
54
|
left: PropTypes.number,
|
|
53
55
|
top: PropTypes.number,
|
|
@@ -259,7 +261,7 @@ export class Rotatable extends React.Component {
|
|
|
259
261
|
};
|
|
260
262
|
|
|
261
263
|
render() {
|
|
262
|
-
const { children,
|
|
264
|
+
const { children, showAnchor } = this.props;
|
|
263
265
|
const { rotation, anchor, origin, translate, position } = this.state;
|
|
264
266
|
|
|
265
267
|
const t = translate ? `translate(${translate.x}px, ${translate.y}px)` : '';
|
|
@@ -272,8 +274,7 @@ export class Rotatable extends React.Component {
|
|
|
272
274
|
};
|
|
273
275
|
|
|
274
276
|
return (
|
|
275
|
-
<
|
|
276
|
-
className={classNames(classes.rotatable, className)}
|
|
277
|
+
<RotatableContainer
|
|
277
278
|
style={style}
|
|
278
279
|
ref={(r) => (this.rotatable = r)}
|
|
279
280
|
onMouseDown={this.mouseDown}
|
|
@@ -281,15 +282,9 @@ export class Rotatable extends React.Component {
|
|
|
281
282
|
>
|
|
282
283
|
{anchor && showAnchor && <Anchor {...anchor} />}
|
|
283
284
|
{children}
|
|
284
|
-
</
|
|
285
|
+
</RotatableContainer>
|
|
285
286
|
);
|
|
286
287
|
}
|
|
287
288
|
}
|
|
288
289
|
|
|
289
|
-
export default
|
|
290
|
-
rotatable: {
|
|
291
|
-
position: 'relative',
|
|
292
|
-
display: 'inline-block',
|
|
293
|
-
cursor: 'move',
|
|
294
|
-
},
|
|
295
|
-
})(Rotatable);
|
|
290
|
+
export default Rotatable;
|
|
@@ -1,23 +1,64 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
|
3
3
|
import { Graphic } from '../graphic';
|
|
4
4
|
import React from 'react';
|
|
5
5
|
|
|
6
6
|
describe('graphic', () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
7
|
+
const theme = createTheme();
|
|
8
|
+
|
|
9
|
+
const renderComponent = (props = {}) => {
|
|
10
|
+
const defaultProps = {
|
|
11
|
+
width: 300,
|
|
12
|
+
height: 100,
|
|
13
|
+
units: 12,
|
|
14
|
+
unit: {
|
|
15
|
+
type: 'in',
|
|
16
|
+
ticks: 16,
|
|
17
|
+
},
|
|
18
|
+
...props,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return render(
|
|
22
|
+
<ThemeProvider theme={theme}>
|
|
23
|
+
<Graphic {...defaultProps} />
|
|
24
|
+
</ThemeProvider>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
describe('rendering', () => {
|
|
29
|
+
it('renders svg with correct viewBox', () => {
|
|
30
|
+
const { container } = renderComponent({ width: 300, height: 100 });
|
|
31
|
+
const svg = container.querySelector('svg');
|
|
32
|
+
expect(svg).toBeInTheDocument();
|
|
33
|
+
expect(svg).toHaveAttribute('viewBox', '0 0 300 100');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('renders background rect with correct dimensions', () => {
|
|
37
|
+
const { container } = renderComponent({ width: 300, height: 100 });
|
|
38
|
+
const rect = container.querySelector('rect');
|
|
39
|
+
expect(rect).toBeInTheDocument();
|
|
40
|
+
expect(rect).toHaveAttribute('width', '300');
|
|
41
|
+
expect(rect).toHaveAttribute('height', '100');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('renders correct number of unit markers', () => {
|
|
45
|
+
const { container } = renderComponent({ units: 12 });
|
|
46
|
+
const svg = container.querySelector('svg');
|
|
47
|
+
// Each unit renders as a group element with a specific structure
|
|
48
|
+
const groups = svg.querySelectorAll('g');
|
|
49
|
+
expect(groups.length).toBeGreaterThan(0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('renders with different unit types', () => {
|
|
53
|
+
const { container } = renderComponent({ unit: { type: 'cm', ticks: 10 } });
|
|
54
|
+
const svg = container.querySelector('svg');
|
|
55
|
+
expect(svg).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('renders with custom dimensions', () => {
|
|
59
|
+
const { container } = renderComponent({ width: 480, height: 60, units: 10 });
|
|
60
|
+
const svg = container.querySelector('svg');
|
|
61
|
+
expect(svg).toHaveAttribute('viewBox', '0 0 480 60');
|
|
21
62
|
});
|
|
22
63
|
});
|
|
23
64
|
});
|
|
@@ -1,28 +1,86 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
|
3
3
|
import { Ruler } from '../index';
|
|
4
4
|
import RulerGraphic from '../graphic';
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
+
// Mock the Rotatable component to avoid complex DOM interactions
|
|
8
|
+
jest.mock('../../rotatable', () => {
|
|
9
|
+
return function MockRotatable({ children }) {
|
|
10
|
+
return <div data-testid="rotatable">{children}</div>;
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Mock the RulerGraphic component to verify props
|
|
15
|
+
jest.mock('../graphic', () => {
|
|
16
|
+
return function MockRulerGraphic(props) {
|
|
17
|
+
return <div data-testid="ruler-graphic" data-unit={JSON.stringify(props.unit)} />;
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
7
21
|
describe('ruler', () => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
22
|
+
const theme = createTheme();
|
|
23
|
+
|
|
24
|
+
const renderComponent = (props = {}) => {
|
|
25
|
+
const defaultProps = {
|
|
26
|
+
units: 12,
|
|
27
|
+
measure: 'imperial',
|
|
28
|
+
...props,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return render(
|
|
32
|
+
<ThemeProvider theme={theme}>
|
|
33
|
+
<Ruler {...defaultProps} />
|
|
34
|
+
</ThemeProvider>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
describe('rendering', () => {
|
|
39
|
+
it('renders ruler component', () => {
|
|
40
|
+
renderComponent();
|
|
41
|
+
expect(screen.getByTestId('rotatable')).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByTestId('ruler-graphic')).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('renders with default props', () => {
|
|
46
|
+
renderComponent();
|
|
47
|
+
const graphic = screen.getByTestId('ruler-graphic');
|
|
48
|
+
expect(graphic).toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('renders with custom startPosition', () => {
|
|
52
|
+
renderComponent({ startPosition: { left: 100, top: 200 } });
|
|
53
|
+
expect(screen.getByTestId('ruler-graphic')).toBeInTheDocument();
|
|
12
54
|
});
|
|
13
55
|
});
|
|
14
56
|
|
|
15
57
|
describe('logic', () => {
|
|
16
58
|
it('sets unit for imperial', () => {
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
59
|
+
renderComponent({ measure: 'imperial', label: 'in' });
|
|
60
|
+
const graphic = screen.getByTestId('ruler-graphic');
|
|
61
|
+
const unit = JSON.parse(graphic.getAttribute('data-unit'));
|
|
62
|
+
expect(unit).toEqual({ ticks: 16, type: 'in' });
|
|
20
63
|
});
|
|
21
64
|
|
|
22
65
|
it('sets unit for metric', () => {
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
66
|
+
renderComponent({ measure: 'metric', label: 'cm' });
|
|
67
|
+
const graphic = screen.getByTestId('ruler-graphic');
|
|
68
|
+
const unit = JSON.parse(graphic.getAttribute('data-unit'));
|
|
69
|
+
expect(unit).toEqual({ ticks: 10, type: 'cm' });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('sets custom tick count for imperial when divisible by 4', () => {
|
|
73
|
+
renderComponent({ measure: 'imperial', label: 'in', tickCount: 8 });
|
|
74
|
+
const graphic = screen.getByTestId('ruler-graphic');
|
|
75
|
+
const unit = JSON.parse(graphic.getAttribute('data-unit'));
|
|
76
|
+
expect(unit).toEqual({ ticks: 8, type: 'in' });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('defaults to 16 ticks for imperial when tickCount not divisible by 4', () => {
|
|
80
|
+
renderComponent({ measure: 'imperial', label: 'in', tickCount: 7 });
|
|
81
|
+
const graphic = screen.getByTestId('ruler-graphic');
|
|
82
|
+
const unit = JSON.parse(graphic.getAttribute('data-unit'));
|
|
83
|
+
expect(unit).toEqual({ ticks: 16, type: 'in' });
|
|
26
84
|
});
|
|
27
85
|
});
|
|
28
86
|
});
|