Skip to content

Commit

Permalink
fix(utils): Update getPercentage to optionally not throw errors
Browse files Browse the repository at this point in the history
  • Loading branch information
mlaursen committed Nov 27, 2021
1 parent 05ec620 commit ff8a1d6
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 32 deletions.
12 changes: 10 additions & 2 deletions packages/form/src/slider/utils.ts
Expand Up @@ -248,11 +248,19 @@ export const getDragPercentage = ({
let thumb1Percentage =
dragging && draggingIndex === 0
? dragValue
: getPercentage(min, max, thumb1Value);
: getPercentage({
min,
max,
value: thumb1Value,
});

let thumb2Percentage: number | undefined;
if (typeof thumb2Value === "number") {
const percentage = getPercentage(min, max, thumb2Value);
const percentage = getPercentage({
min,
max,
value: thumb2Value,
});
thumb1Percentage = Math.min(thumb1Percentage, percentage);
thumb2Percentage =
dragging && draggingIndex === 1
Expand Down
2 changes: 1 addition & 1 deletion packages/progress/src/CircularProgress.tsx
Expand Up @@ -110,7 +110,7 @@ export const CircularProgress = forwardRef<
) {
let progress: number | undefined;
if (typeof value === "number") {
progress = getPercentage(min, max, value);
progress = getPercentage({ min, max, value });
}

const svgStyle = useMemo<CSSProperties | undefined>(() => {
Expand Down
2 changes: 1 addition & 1 deletion packages/progress/src/LinearProgress.tsx
Expand Up @@ -76,7 +76,7 @@ export const LinearProgress = forwardRef<HTMLSpanElement, LinearProgressProps>(

let progress: number | undefined;
if (typeof value === "number") {
progress = getPercentage(min, max, value);
progress = getPercentage({ min, max, value });
}
const barStyle = useMemo(() => {
if (typeof progress !== "number") {
Expand Down
91 changes: 74 additions & 17 deletions packages/utils/src/__tests__/getPercentage.ts
@@ -1,38 +1,95 @@
import { getPercentage } from "../getPercentage";
import { getPercentage, GetPercentageOptions } from "../getPercentage";

describe("getPercentage", () => {
it("should throw a RangeError if the min is greater than the max", () => {
const expected = new RangeError(
"A range must have the min value less than the max value"
);
expect(() => getPercentage(0, -100, 0)).toThrow(expected);
expect(() => getPercentage(0, 0, 0)).toThrow(expected);
expect(() => getPercentage(0, -100, 20)).toThrow(expected);
expect(() => getPercentage(0, 0, 20)).toThrow(expected);
const options1: GetPercentageOptions = {
min: 0,
max: -100,
value: 0,
};
const options2: GetPercentageOptions = {
min: 0,
max: 0,
value: 0,
};
const options3: GetPercentageOptions = {
min: 0,
max: -100,
value: 20,
};
const options4: GetPercentageOptions = {
min: 0,
max: 0,
value: 20,
};
expect(() => getPercentage(options1)).toThrow(expected);
expect(() => getPercentage(options2)).toThrow(expected);
expect(() => getPercentage(options3)).toThrow(expected);
expect(() => getPercentage(options4)).toThrow(expected);
expect(() => getPercentage({ ...options1, validate: false })).not.toThrow(
expected
);
expect(() => getPercentage({ ...options2, validate: false })).not.toThrow(
expected
);
expect(() => getPercentage({ ...options3, validate: false })).not.toThrow(
expected
);
expect(() => getPercentage({ ...options4, validate: false })).not.toThrow(
expected
);
});

it("should throw a RangeError if the value is not between the min anx max", () => {
const expected = new RangeError(
"A value must be between the min and max values"
);
expect(() => getPercentage(0, 100, -1)).toThrow(expected);
expect(() => getPercentage(0, 1, -1)).toThrow(expected);
expect(() => getPercentage(0, 1, -0.5)).toThrow(expected);
const options1: GetPercentageOptions = {
min: 0,
max: 100,
value: -1,
};
const options2: GetPercentageOptions = {
min: 0,
max: 1,
value: -1,
};
const options3: GetPercentageOptions = {
min: 0,
max: 1,
value: -0.5,
};

expect(() => getPercentage(options1)).toThrow(expected);
expect(() => getPercentage(options2)).toThrow(expected);
expect(() => getPercentage(options3)).toThrow(expected);
expect(() => getPercentage({ ...options1, validate: false })).not.toThrow(
expected
);
expect(() => getPercentage({ ...options2, validate: false })).not.toThrow(
expected
);
expect(() => getPercentage({ ...options3, validate: false })).not.toThrow(
expected
);
});

it("should return the value as a decimal between 0 and 1 representing the current percentage", () => {
expect(getPercentage(0, 100, 20)).toBe(0.2);
expect(getPercentage(0, 10, 3)).toBe(0.3);
expect(getPercentage(0, 1, 0.5)).toBe(0.5);
expect(getPercentage({ min: 0, max: 100, value: 20 })).toBe(0.2);
expect(getPercentage({ min: 0, max: 10, value: 3 })).toBe(0.3);
expect(getPercentage({ min: 0, max: 1, value: 0.5 })).toBe(0.5);
});

it("should always return a positive percentage", () => {
expect(getPercentage(-100, 0, -20)).toBe(0.8);
expect(getPercentage(-10, 0, -3)).toBe(0.7);
expect(getPercentage(-1, 0, -0.5)).toBe(0.5);
expect(getPercentage({ min: -100, max: 0, value: -20 })).toBe(0.8);
expect(getPercentage({ min: -10, max: 0, value: -3 })).toBe(0.7);
expect(getPercentage({ min: -1, max: 0, value: -0.5 })).toBe(0.5);

expect(getPercentage(-100, 100, 0)).toBe(0.5);
expect(getPercentage(-100, 0, 0)).toBe(1);
expect(getPercentage(-100, 0, -25)).toBe(0.75);
expect(getPercentage({ min: -100, max: 100, value: 0 })).toBe(0.5);
expect(getPercentage({ min: -100, max: 0, value: 0 })).toBe(1);
expect(getPercentage({ min: -100, max: 0, value: -25 })).toBe(0.75);
});
});
55 changes: 44 additions & 11 deletions packages/utils/src/getPercentage.ts
@@ -1,21 +1,54 @@
/** @remarks \@since 4.0.1 */
export interface GetPercentageOptions {
/**
* The min value allowed.
*/
min: number;

/**
* The max value allowed.
*/
max: number;

/**
* The current value
*/
value: number;

/**
* Boolean if the min, max, and value options should be validated to make sure
* they are within the correct range relative to each other.
*
* @defaultValue `true`
*/
validate?: boolean;
}

/**
* Gets the current percentage based on the min, max, and current value.
*
* @param min - the min value
* @param max - the max value
* @param value - the current value to compare against
* @returns the percentage that the `value` is between the `min` and `max`
* values.
* @internal
* @remarks \@since 4.0.1 uses an object for options instead of multiple
* arguments.
*/
export function getPercentage(min: number, max: number, value: number): number {
if (min >= max) {
throw new RangeError(
"A range must have the min value less than the max value"
);
}
export function getPercentage({
min,
max,
value,
validate = true,
}: GetPercentageOptions): number {
if (validate) {
if (min >= max) {
throw new RangeError(
"A range must have the min value less than the max value"
);
}

if (value > max || value < min) {
throw new RangeError("A value must be between the min and max values");
if (value > max || value < min) {
throw new RangeError("A value must be between the min and max values");
}
}

const range = max - min;
Expand Down

0 comments on commit ff8a1d6

Please sign in to comment.