-
- 0 && driveWidth < 5 ? 5 : driveWidth}px` }}>
-
-
-
-
-
+
);
};
diff --git a/src/views/NewSettings/utils/index.ts b/src/views/NewSettings/utils/index.ts
index 74d8e540ca..3ec1b8537c 100644
--- a/src/views/NewSettings/utils/index.ts
+++ b/src/views/NewSettings/utils/index.ts
@@ -2,3 +2,4 @@ export * from './LogsUtils';
export * from './membersUtils';
export * from './productUtils';
export * from './suscriptionUtils';
+export * from './usageBarUtils';
diff --git a/src/views/NewSettings/utils/usageBarUtils.test.ts b/src/views/NewSettings/utils/usageBarUtils.test.ts
new file mode 100644
index 0000000000..6954487ec7
--- /dev/null
+++ b/src/views/NewSettings/utils/usageBarUtils.test.ts
@@ -0,0 +1,61 @@
+import { describe, expect, test } from 'vitest';
+import { getUsageBarPercentages } from './usageBarUtils';
+
+const GB = 1024 ** 3;
+
+const arrange = (overrides = {}) =>
+ getUsageBarPercentages({
+ driveUsageBytes: 0,
+ backupsUsageBytes: 0,
+ spaceLimitBytes: 10 * GB,
+ ...overrides,
+ });
+
+describe('getUsageBarPercentages', () => {
+ test('when usage is under the limit, then percentages are relative to the limit and not over limit', () => {
+ const result = arrange({ driveUsageBytes: 2.5 * GB, backupsUsageBytes: 5 * GB });
+
+ expect(result).toEqual({ drivePercentage: 25, backupPercentage: 50, isOverLimit: false });
+ });
+
+ test('when usage is exactly the limit, then the bar is full and not over limit', () => {
+ const result = arrange({ driveUsageBytes: 4 * GB, backupsUsageBytes: 6 * GB });
+
+ expect(result).toEqual({ drivePercentage: 40, backupPercentage: 60, isOverLimit: false });
+ });
+
+ test('when drive usage is far over the limit, then the drive segment takes the whole bar', () => {
+ const result = arrange({ driveUsageBytes: 33.8 * GB, spaceLimitBytes: GB });
+
+ expect(result).toEqual({ drivePercentage: 100, backupPercentage: 0, isOverLimit: true });
+ });
+
+ test('when drive plus backups are over the limit, then they are scaled to 100% keeping their ratio', () => {
+ const result = arrange({ driveUsageBytes: 15 * GB, backupsUsageBytes: 5 * GB });
+
+ expect(result).toEqual({ drivePercentage: 75, backupPercentage: 25, isOverLimit: true });
+ });
+
+ test('when the limit is zero and there is usage, then the bar is full and over limit', () => {
+ const result = arrange({ driveUsageBytes: GB, spaceLimitBytes: 0 });
+
+ expect(result).toEqual({ drivePercentage: 100, backupPercentage: 0, isOverLimit: true });
+ });
+
+ test('when the limit is invalid and there is no usage, then nothing is drawn', () => {
+ const result = arrange({ spaceLimitBytes: Number.NaN });
+
+ expect(result).toEqual({ drivePercentage: 0, backupPercentage: 0, isOverLimit: false });
+ });
+
+ test('when values are not finite, then percentages are never NaN or Infinity', () => {
+ const result = arrange({ driveUsageBytes: Number.NaN, backupsUsageBytes: Infinity, spaceLimitBytes: Infinity });
+
+ expect(Number.isFinite(result.drivePercentage)).toBe(true);
+ expect(Number.isFinite(result.backupPercentage)).toBe(true);
+ });
+
+ test('when usage is zero, then both segments are empty', () => {
+ expect(arrange()).toEqual({ drivePercentage: 0, backupPercentage: 0, isOverLimit: false });
+ });
+});
diff --git a/src/views/NewSettings/utils/usageBarUtils.ts b/src/views/NewSettings/utils/usageBarUtils.ts
new file mode 100644
index 0000000000..a368c0b954
--- /dev/null
+++ b/src/views/NewSettings/utils/usageBarUtils.ts
@@ -0,0 +1,42 @@
+interface UsageBarPercentagesParams {
+ driveUsageBytes: number;
+ backupsUsageBytes: number;
+ spaceLimitBytes: number;
+}
+
+interface UsageBarPercentages {
+ drivePercentage: number;
+ backupPercentage: number;
+ isOverLimit: boolean;
+}
+
+const toNonNegativeNumber = (value: number): number => (Number.isFinite(value) && value > 0 ? value : 0);
+
+/**
+ * Computes the share of the bar (0-100) taken by the Drive and Backups segments.
+ * Over the limit (or with an invalid limit and some usage) both segments are scaled
+ * proportionally so together they fill exactly 100%.
+ */
+export const getUsageBarPercentages = ({
+ driveUsageBytes,
+ backupsUsageBytes,
+ spaceLimitBytes,
+}: UsageBarPercentagesParams): UsageBarPercentages => {
+ const driveUsage = toNonNegativeNumber(driveUsageBytes);
+ const backupsUsage = toNonNegativeNumber(backupsUsageBytes);
+ const spaceLimit = toNonNegativeNumber(spaceLimitBytes);
+ const totalUsage = driveUsage + backupsUsage;
+ const isOverLimit = totalUsage > spaceLimit;
+
+ if (totalUsage === 0) {
+ return { drivePercentage: 0, backupPercentage: 0, isOverLimit };
+ }
+
+ const denominator = isOverLimit ? totalUsage : spaceLimit;
+
+ return {
+ drivePercentage: (driveUsage * 100) / denominator,
+ backupPercentage: (backupsUsage * 100) / denominator,
+ isOverLimit,
+ };
+};