@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +7 -70
  2. package/esm/index.css +847 -0
  3. package/esm/index.js +13155 -0
  4. package/esm/index.js.map +1 -0
  5. package/esm/package.json +3 -0
  6. package/lib/anchor-utils.js +18 -74
  7. package/lib/anchor-utils.js.map +1 -1
  8. package/lib/anchor.js +20 -28
  9. package/lib/anchor.js.map +1 -1
  10. package/lib/index.js +1 -11
  11. package/lib/index.js.map +1 -1
  12. package/lib/protractor/graphic.js +68 -105
  13. package/lib/protractor/graphic.js.map +1 -1
  14. package/lib/protractor/index.js +35 -65
  15. package/lib/protractor/index.js.map +1 -1
  16. package/lib/rotatable.js +73 -141
  17. package/lib/rotatable.js.map +1 -1
  18. package/lib/ruler/graphic.js +29 -66
  19. package/lib/ruler/graphic.js.map +1 -1
  20. package/lib/ruler/index.js +43 -75
  21. package/lib/ruler/index.js.map +1 -1
  22. package/lib/ruler/unit-type.js +19 -36
  23. package/lib/ruler/unit-type.js.map +1 -1
  24. package/lib/ruler/unit.js +51 -88
  25. package/lib/ruler/unit.js.map +1 -1
  26. package/lib/style-utils.js +2 -9
  27. package/lib/style-utils.js.map +1 -1
  28. package/lib/transform-origin.js +2 -13
  29. package/lib/transform-origin.js.map +1 -1
  30. package/package.json +20 -10
  31. package/src/__tests__/rotatable.test.jsx +84 -41
  32. package/src/anchor.jsx +15 -16
  33. package/src/protractor/__tests__/graphic.test.jsx +57 -6
  34. package/src/protractor/__tests__/index.test.jsx +58 -6
  35. package/src/protractor/graphic.jsx +49 -54
  36. package/src/protractor/index.jsx +24 -22
  37. package/src/rotatable.jsx +23 -28
  38. package/src/ruler/__tests__/graphic.test.jsx +57 -16
  39. package/src/ruler/__tests__/index.test.jsx +70 -12
  40. package/src/ruler/__tests__/unit-type.test.jsx +59 -6
  41. package/src/ruler/__tests__/unit.test.jsx +61 -8
  42. package/src/ruler/graphic.jsx +11 -14
  43. package/src/ruler/index.jsx +25 -28
  44. package/src/ruler/unit-type.jsx +10 -9
  45. package/src/ruler/unit.jsx +25 -29
  46. package/src/__tests__/__snapshots__/rotatable.test.jsx.snap +0 -37
  47. package/src/protractor/__tests__/__snapshots__/graphic.test.jsx.snap +0 -1234
  48. package/src/protractor/__tests__/__snapshots__/index.test.jsx.snap +0 -40
  49. package/src/ruler/__tests__/__snapshots__/graphic.test.jsx.snap +0 -160
  50. package/src/ruler/__tests__/__snapshots__/index.test.jsx.snap +0 -45
  51. package/src/ruler/__tests__/__snapshots__/unit-type.test.jsx.snap +0 -12
  52. package/src/ruler/__tests__/__snapshots__/unit.test.jsx.snap +0 -30
@@ -1,13 +1,64 @@
1
- import toJson from 'enzyme-to-json';
2
- import { shallow } from 'enzyme';
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
- describe('snapshot', () => {
8
- it('renders', () => {
9
- const wrapper = shallow(<Graphic classes={{ path: 'path', line: 'line', circle: 'circle' }} />);
10
- expect(toJson(wrapper)).toMatchSnapshot();
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 toJson from 'enzyme-to-json';
2
- import { shallow } from 'enzyme';
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
- describe('snapshot', () => {
8
- it('renders', () => {
9
- const wrapper = shallow(<Protractor classes={{}} />);
10
- expect(toJson(wrapper)).toMatchSnapshot();
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 { withStyles } from '@material-ui/core/styles';
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 Line = withStyles((theme) => ({
8
- line: {
9
- strokeWidth: '0.2',
10
- stroke: strokeColor(theme),
11
- },
12
- }))(({ angle, classes, major, minor }) => (
13
- <line
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 = withStyles((theme) => ({
25
- line: {
26
- strokeWidth: '0.2',
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 Text = withStyles((theme) => ({
34
- text: {
35
- fontSize: '2.5px',
36
- textAnchor: 'middle',
37
- fill: strokeColor(theme),
38
- ...noSelect(),
39
- },
40
- }))(({ angle, classes }) => (
41
- <text transform={`rotate(${angle - 90}, 50.5, 50)`} className={classes.text} x="50" y="12.5">
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
- </text>
44
- ));
41
+ </StyledText>
42
+ );
45
43
 
46
- export class Graphic extends React.PureComponent {
47
- static propTypes = {
48
- classes: PropTypes.object.isRequired,
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
- <path className={classes.path} d="M 1,50 A 1,1 0 0 1 100,50 L 100,60 L 1,60 Z" fill="none" />
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
- <circle r="4" cx="50.5" cy="50" className={classes.circle} />
66
- <line className={classes.line} x1="48.5" x2="52.5" y1="50" y2="50" />
67
- <line className={classes.line} transform={'rotate(90 50.5 50)'} x1="48.5" x2="52.5" y1="50" y2="50" />
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 withStyles((theme) => ({
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;
@@ -1,14 +1,28 @@
1
1
  import React from 'react';
2
- import { withStyles } from '@material-ui/core/styles';
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
- import classNames from 'classnames';
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 { classes, width, className, startPosition } = this.props;
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
- <div className={classes.protractor} style={{ width: `${width}px` }}>
56
+ <StyledProtractor style={{ width: `${width}px` }}>
43
57
  <Graphic />
44
58
 
45
- <Anchor className={classNames('leftAnchor', classes.leftAnchor)} />
46
- <Anchor className={classNames('rightAnchor', classes.rightAnchor)} />
47
- </div>
59
+ <StyledLeftAnchor className="leftAnchor" />
60
+ <StyledRightAnchor className="rightAnchor" />
61
+ </StyledProtractor>
48
62
  </Rotatable>
49
63
  );
50
64
  }
51
65
  }
52
66
 
53
- export default withStyles(() => ({
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 { withStyles } from '@material-ui/core/styles';
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
- import classNames from 'classnames';
9
-
10
- const Anchor = withStyles({
11
- anchor: {
12
- position: 'absolute',
13
- zIndex: 100,
14
- width: '200px',
15
- height: '80px',
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
- <svg
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
- </svg>
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, classes, showAnchor, className } = this.props;
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
- <div
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
- </div>
285
+ </RotatableContainer>
285
286
  );
286
287
  }
287
288
  }
288
289
 
289
- export default withStyles({
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 toJson from 'enzyme-to-json';
2
- import { shallow } from 'enzyme';
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
- describe('snapshot', () => {
8
- it('renders', () => {
9
- const wrapper = shallow(
10
- <Graphic
11
- width={300}
12
- height={100}
13
- units={12}
14
- unit={{
15
- type: 'in',
16
- }}
17
- classes={{ bg: 'bg' }}
18
- />,
19
- );
20
- expect(toJson(wrapper)).toMatchSnapshot();
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 toJson from 'enzyme-to-json';
2
- import { shallow } from 'enzyme';
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
- describe('snapshot', () => {
9
- it('renders', () => {
10
- const wrapper = shallow(<Ruler classes={{}} />);
11
- expect(toJson(wrapper)).toMatchSnapshot();
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
- const wrapper = shallow(<Ruler measure={'imperial'} classes={{}} label={'in'} />);
18
- const r = wrapper.find(RulerGraphic);
19
- expect(r.prop('unit')).toEqual({ ticks: 16, type: 'in' });
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
- const wrapper = shallow(<Ruler measure={'metric'} classes={{}} label={'cm'} />);
24
- const r = wrapper.find(RulerGraphic);
25
- expect(r.prop('unit')).toEqual({ ticks: 10, type: 'cm' });
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
  });