Skip to content

Commit

Permalink
Add tests for knob
Browse files Browse the repository at this point in the history
  • Loading branch information
ikcede committed Sep 6, 2024
1 parent 1cfec11 commit becc382
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 13 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ Check out [example/App.tsx](https://github.com/ikcede/ts-bezier-easing-editor/bl
- Configure for extended Y coordinates
- Improve test coverage
- Fix position calculation with CSS zoom applied
- Support auto resize
- Support linear() and step() views

## Local Development

Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@storybook/test": "^8.2.1",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"eslint-plugin-storybook": "^0.8.0",
"jest-environment-jsdom": "^29.7.0",
"prettier": "3.3.3",
Expand Down
40 changes: 27 additions & 13 deletions src/components/Knob.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,41 @@ import { CubicBezier, Scale, ScaledComponent } from '../util';
export type KnobDownFunction = () => void;

interface KnobProps extends ScaledComponent {
/** The bezier that this knob supports */
/** CubicBezier controlling the underlying curve data */
bezier?: CubicBezier;

/** Which beizer point the knob is based on */
/** Which control point to represent (1 or 2) */
control?: 1 | 2;

/** Default color of the knob */
/** Color of the main knob */
knobColor?: string;

/** Default radius of the knob */
/** Radius of the main knob */
knobRadius?: number;

/** Color of the circle within the knob that appears on down */
knobHighlightColor?: string;

/** Default color of the line */
/** Color of the tail line */
tailColor?: string;

/** Default stroke with of the line */
/** Width of the tail line in pixels */
tailWidth?: number;

/** Whether or not to add an extra knob at the base */
showTailKnob?: boolean;

/** Color of the extra knob */
/** Color of the tail knob */
tailKnobColor?: string;

/** Radius of the extra knob */
/** Radius of the tail knob */
tailKnobRadius?: number;

/**
* If the knob is pressed down or not
*
* This is passed from editor to control how the knob is
* being dragged
* This is passed from [BezierEditor] to control how
* the knob is being dragged
*/
down?: boolean;

Expand All @@ -47,7 +47,7 @@ interface KnobProps extends ScaledComponent {
*/
onDown?: KnobDownFunction;

/** If the knob should apply hover effects */
/** Whether the knob should apply hover effects */
useHover?: boolean;

/** Color of the knob when hovered or down */
Expand All @@ -66,8 +66,22 @@ interface KnobProps extends ScaledComponent {
/**
* A knob used to visualize and control one the two bezier points
*
* The tail point starts at either (0, 0) for control = 1 or
* (1, 1) for control = 2
* It represents one of the two control points of a cubic Bezier
* curve and allows for user interaction. The component includes
* the main knob, a tail line, and optionally a tail knob.
*
* The tail point always starts at either (0, 0) for control = 1
* or (1, 1) for control = 2
*
* @component
* @example
* ```jsx
* <Knob
* bezier={new CubicBezier(0.4, 0, 0.6, 1)}
* control={1}
* onDown={() => console.log('Knob pressed')}
* />
* ```
*/
const Knob: React.FC<KnobProps> = ({
bezier = new CubicBezier(0.25, 0.25, 0.75, 0.75),
Expand Down
145 changes: 145 additions & 0 deletions src/components/__tests__/Knob.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React from 'react';
import { render } from '@testing-library/react';
import Knob from '../Knob';
import { CubicBezier, Scale } from '../../util';
import userEvent from '@testing-library/user-event';

describe('Knob', () => {
const user = userEvent.setup();

let xScale: Scale;
let yScale: Scale;

beforeEach(() => {
xScale = new Scale(0, 1, 0, 200);
yScale = new Scale(0, 1, 200, 0);
});

it('renders a line and two circles by default', () => {
const { container } = render(
<svg>
<Knob xScale={xScale} yScale={yScale} />
</svg>
);
expect(container.querySelectorAll('line').length).toBe(1);
expect(container.querySelectorAll('circle').length).toBe(2);
});

it('applies default props correctly', () => {
const { container } = render(
<svg>
<Knob xScale={xScale} yScale={yScale} />
</svg>
);
const line = container.querySelector('line');
const knob = container.querySelectorAll('circle')[1];
expect(line!.getAttribute('stroke')).toEqual('rgb(230, 75, 61)');
expect(line!.getAttribute('stroke-width')).toEqual('3');
expect(knob!.getAttribute('fill')).toEqual('rgb(230, 75, 61)');
expect(knob!.getAttribute('r')).toEqual('8');
});

it('applies custom colors and sizes', () => {
const { container } = render(
<svg>
<Knob
knobColor="red"
knobRadius={10}
tailColor="blue"
tailWidth={5}
xScale={xScale}
yScale={yScale}
/>
</svg>
);
const line = container.querySelector('line');
const knob = container.querySelectorAll('circle')[1];
expect(line?.getAttribute('stroke')).toEqual('blue');
expect(line?.getAttribute('stroke-width')).toEqual('5');
expect(knob?.getAttribute('fill')).toEqual('red');
expect(knob?.getAttribute('r')).toEqual('10');
});

it('hides tail knob when showTailKnob is false', () => {
const { container } = render(
<svg>
<Knob xScale={xScale} yScale={yScale} showTailKnob={false} />
</svg>
);
expect(container.querySelectorAll('circle').length).toBe(1);
});

it('changes appearance on hover when useHover is true', async () => {
const { container } = render(
<svg>
<Knob xScale={xScale} yScale={yScale} useHover />
</svg>
);
const knob = container.querySelectorAll('circle')[1];
await user.hover(knob);
expect(knob!.getAttribute('r')).toEqual('10');
await user.unhover(knob);
expect(knob!.getAttribute('r')).toEqual('8');
});

it('does not change appearance on hover when useHover is false', async () => {
const { container } = render(
<svg>
<Knob xScale={xScale} yScale={yScale} useHover={false} />
</svg>
);
const knob = container.querySelectorAll('circle')[1];
await user.hover(knob);
expect(knob?.getAttribute('r')).toEqual('8');
});

it('calls onDown when knob is clicked', async () => {
const onDown = jest.fn();
const { container } = render(
<svg>
<Knob xScale={xScale} yScale={yScale} onDown={onDown} />
</svg>
);
expect(onDown).toHaveBeenCalledTimes(0);
const knob = container.querySelectorAll('circle')[1];
await user.click(knob);
expect(onDown).toHaveBeenCalled();
});

it('calls onDown when knob is touched', async () => {
const onDown = jest.fn();
const { container } = render(
<svg>
<Knob xScale={xScale} yScale={yScale} onDown={onDown} />
</svg>
);
expect(onDown).toHaveBeenCalledTimes(0);
const knob = container.querySelectorAll('circle')[1];
await user.pointer({ target: knob, keys: '[TouchA]' });
expect(onDown).toHaveBeenCalled();
});

it('shows highlight when down is true', () => {
const { container } = render(
<svg>
<Knob xScale={xScale} yScale={yScale} down />
</svg>
);
expect(container.querySelectorAll('circle').length).toBe(3);
});

it('uses custom scales', () => {
const xScale = new Scale(0, 1, 0, 300);
const yScale = new Scale(0, 1, 300, 0);
const { container } = render(
<svg>
<Knob xScale={xScale} yScale={yScale} />
</svg>
);
const line = container.querySelector('line');
expect(line?.getAttribute('x1')).toEqual('0');
expect(line?.getAttribute('y1')).toEqual('300');
expect(line?.getAttribute('x2')).toEqual('75');
expect(line?.getAttribute('y2')).toEqual('225');
});
});

0 comments on commit becc382

Please sign in to comment.