powerhouse-rp-toolkit
Version:
Renaissance Periodization Training Toolkit for PowerHouseATX
460 lines (369 loc) • 14.7 kB
JavaScript
/**
* @jest-environment jsdom
*/
import * as ts from "../js/core/trainingState.js";
import {
exportAllData,
exportChart,
createBackup,
autoBackup,
importData,
exportFeedback,
} from "../js/algorithms/dataExport.js";
import {
mockTrainingState,
resetMockTrainingState,
} from "../__tests__/helpers/mockState.js";
import { vi } from "vitest";
// Mock file system operations for testing
global.URL = {
createObjectURL: vi.fn(() => "mock-object-url"),
revokeObjectURL: vi.fn(),
};
global.Blob = vi.fn((content, options) => ({
content,
options,
size: content[0].length,
type: options.type,
}));
// Mock DOM elements
document.createElement = vi.fn((tagName) => {
const element = {
tagName: tagName.toUpperCase(),
click: vi.fn(),
setAttribute: vi.fn(),
getAttribute: vi.fn(),
style: {},
href: "",
download: "",
};
if (tagName === "input") {
element.type = "";
element.files = [];
element.onchange = null;
}
return element;
});
vi.spyOn(document.body, "appendChild").mockImplementation(() => {});
vi.spyOn(document.body, "removeChild").mockImplementation(() => {});
// ----- isolate global singleton so later test-suites aren't polluted -----
const snapshot = ts.trainingState ? Object.assign({}, ts.trainingState) : {};
beforeEach(() => {
// start every test with a fresh clone of the pristine snapshot
if (ts.trainingState) {
Object.assign(ts.trainingState, Object.assign({}, snapshot));
}
// Reset mocks
vi.clearAllMocks();
});
afterAll(() => {
// restore original once the whole file is done
if (ts.trainingState) {
Object.assign(ts.trainingState, snapshot);
}
});
describe("Data Management Algorithm Tests", () => {
beforeEach(() => {
resetMockTrainingState();
});
describe("exportAllData", () => {
test("should export all training data as JSON", () => {
const result = exportAllData(mockTrainingState);
expect(result).toHaveProperty("success", true);
expect(result).toHaveProperty("filename");
expect(result).toHaveProperty("format", "json");
expect(result).toHaveProperty("size");
expect(result.filename).toContain("powerhouse-export");
expect(result.filename).toContain(".json");
expect(typeof result.size).toBe("number");
});
test("should include all training state data", () => {
const result = exportAllData(mockTrainingState);
expect(result).toHaveProperty("data");
expect(result.data).toHaveProperty("currentMesocycle");
expect(result.data).toHaveProperty("weeklyProgram");
expect(result.data).toHaveProperty("volumeLandmarks");
expect(result.data).toHaveProperty("exportMetadata");
});
test("should include export metadata", () => {
const result = exportAllData(mockTrainingState);
expect(result.data.exportMetadata).toHaveProperty("exportDate");
expect(result.data.exportMetadata).toHaveProperty("version");
expect(result.data.exportMetadata).toHaveProperty("dataIntegrity");
expect(typeof result.data.exportMetadata.exportDate).toBe("string");
expect(typeof result.data.exportMetadata.version).toBe("string");
});
test("should handle empty training state", () => {
const emptyState = {};
const result = exportAllData(emptyState);
expect(result).toHaveProperty("success", true);
expect(result).toHaveProperty("warnings");
expect(Array.isArray(result.warnings)).toBe(true);
});
test("should create downloadable file", () => {
const result = exportAllData(mockTrainingState);
expect(global.Blob).toHaveBeenCalled();
expect(global.URL.createObjectURL).toHaveBeenCalled();
expect(document.createElement).toHaveBeenCalledWith("a");
});
});
describe("exportChart", () => {
const mockChartData = {
type: "volume-progression",
muscle: "chest",
data: [
{ week: 1, volume: 12 },
{ week: 2, volume: 14 },
{ week: 3, volume: 16 },
],
};
test("should export chart as SVG", () => {
const result = exportChart(mockChartData, { format: "svg" });
expect(result).toHaveProperty("success", true);
expect(result).toHaveProperty("format", "svg");
expect(result).toHaveProperty("filename");
expect(result.filename).toContain("chart");
expect(result.filename).toContain(".svg");
});
test("should export chart as PNG", () => {
const result = exportChart(mockChartData, { format: "png" });
expect(result).toHaveProperty("success", true);
expect(result).toHaveProperty("format", "png");
expect(result).toHaveProperty("filename");
expect(result.filename).toContain(".png");
});
test("should include chart metadata", () => {
const result = exportChart(mockChartData, { format: "svg" });
expect(result).toHaveProperty("chartInfo");
expect(result.chartInfo).toHaveProperty("type");
expect(result.chartInfo).toHaveProperty("dataPoints");
expect(result.chartInfo).toHaveProperty("muscle");
expect(result.chartInfo.type).toBe("volume-progression");
expect(result.chartInfo.muscle).toBe("chest");
});
test("should handle invalid chart data", () => {
const invalidData = { type: "unknown" };
const result = exportChart(invalidData, { format: "svg" });
expect(result.success).toBe(false);
expect(result).toHaveProperty("error");
});
});
describe("createBackup", () => {
const mockTrainingState = {
currentMesocycle: { currentWeek: 3, length: 6 },
weeklyProgram: { actualVolume: { chest: [12, 14, 16] } },
volumeLandmarks: { chest: { mv: 8, mev: 10, mav: 16, mrv: 20 } },
};
test("should create compressed backup", () => {
const result = createBackup(mockTrainingState);
expect(result).toHaveProperty("success", true);
expect(result).toHaveProperty("backupId");
expect(result).toHaveProperty("filename");
expect(result).toHaveProperty("size");
expect(result).toHaveProperty("compression");
expect(result.filename).toContain("backup");
expect(typeof result.backupId).toBe("string");
expect(typeof result.compression).toBe("object");
});
test("should include backup metadata", () => {
const result = createBackup(mockTrainingState);
expect(result).toHaveProperty("metadata");
expect(result.metadata).toHaveProperty("created");
expect(result.metadata).toHaveProperty("version");
expect(result.metadata).toHaveProperty("checksum");
expect(typeof result.metadata.created).toBe("string");
expect(typeof result.metadata.checksum).toBe("string");
});
test("should compress data efficiently", () => {
const result = createBackup(mockTrainingState);
expect(result.compression).toHaveProperty("originalSize");
expect(result.compression).toHaveProperty("compressedSize");
expect(result.compression).toHaveProperty("ratio");
expect(result.compression.compressedSize).toBeLessThanOrEqual(
result.compression.originalSize,
);
expect(result.compression.ratio).toBeGreaterThan(0);
expect(result.compression.ratio).toBeLessThanOrEqual(1);
});
});
describe("autoBackup", () => {
const mockTrainingState = {
currentMesocycle: { currentWeek: 3, length: 6 },
weeklyProgram: { actualVolume: { chest: [12, 14, 16] } },
dataManagement: {
autoBackupEnabled: true,
lastBackup: null,
},
};
test("should create automatic backup when enabled", () => {
const result = autoBackup(mockTrainingState);
expect(result).toHaveProperty("success", true);
expect(result).toHaveProperty("backupCreated", true);
expect(result).toHaveProperty("nextBackup");
expect(typeof result.nextBackup).toBe("string");
});
test("should skip backup when disabled", () => {
const disabledState = {
...mockTrainingState,
dataManagement: { autoBackupEnabled: false },
};
const result = autoBackup(disabledState);
expect(result).toHaveProperty("success", true);
expect(result).toHaveProperty("backupCreated", false);
expect(result).toHaveProperty("reason");
expect(result.reason).toContain("disabled");
});
test("should respect backup frequency limits", () => {
const recentBackupState = {
...mockTrainingState,
dataManagement: {
autoBackupEnabled: true,
lastBackup: new Date().toISOString(), // Very recent backup
},
};
const result = autoBackup(recentBackupState);
expect(result).toHaveProperty("success", true);
expect(result).toHaveProperty("backupCreated", false);
expect(result).toHaveProperty("reason");
expect(result.reason).toContain("recent");
});
});
describe("importData", () => {
const mockFileContent = JSON.stringify({
currentMesocycle: { currentWeek: 2, length: 6 },
weeklyProgram: { actualVolume: { chest: [10, 12] } },
volumeLandmarks: { chest: { mv: 8, mev: 10, mav: 16, mrv: 20 } },
exportMetadata: {
version: "0.9.0-beta",
exportDate: "2024-01-01T00:00:00.000Z",
},
});
test("should import valid data successfully", () => {
const result = importData(mockFileContent);
expect(result).toHaveProperty("success", true);
expect(result).toHaveProperty("importedData");
expect(result).toHaveProperty("validation");
expect(result.importedData).toHaveProperty("currentMesocycle");
expect(result.importedData).toHaveProperty("weeklyProgram");
expect(result.importedData).toHaveProperty("volumeLandmarks");
});
test("should validate imported data structure", () => {
const result = importData(mockFileContent);
expect(result.validation).toHaveProperty("isValid", true);
expect(result.validation).toHaveProperty("version");
expect(result.validation).toHaveProperty("compatibility");
expect(typeof result.validation.compatibility).toBe("string");
});
test("should handle invalid JSON", () => {
const invalidJson = "{ invalid json content";
const result = importData(invalidJson);
expect(result.success).toBe(false);
expect(result).toHaveProperty("error");
expect(result.error).toContain("JSON");
});
test("should handle version incompatibility", () => {
const oldVersionData = JSON.stringify({
exportMetadata: { version: "0.1.0" },
someOldData: "value",
});
const result = importData(oldVersionData);
expect(result).toHaveProperty("validation");
expect(result.validation).toHaveProperty("compatibility");
});
test("should provide data transformation warnings", () => {
const partialData = JSON.stringify({
currentMesocycle: { currentWeek: 1 },
// Missing other required fields
exportMetadata: { version: "0.9.0-beta" },
});
const result = importData(partialData);
expect(result).toHaveProperty("warnings");
expect(Array.isArray(result.warnings)).toBe(true);
});
});
describe("exportFeedback", () => {
const mockTrainingState = {
currentMesocycle: { currentWeek: 3, length: 6 },
weeklyProgram: {
actualVolume: { chest: [12, 14, 16] },
sessions: [
{
date: "2024-01-01",
exercises: ["Bench Press", "Incline Press"],
feedback: { difficulty: 8, satisfaction: 7 },
},
],
},
};
test("should export feedback data as CSV", () => {
const result = exportFeedback(mockTrainingState);
expect(result).toHaveProperty("success", true);
expect(result).toHaveProperty("format", "csv");
expect(result).toHaveProperty("filename");
expect(result).toHaveProperty("rows");
expect(result.filename).toContain("feedback");
expect(result.filename).toContain(".csv");
expect(typeof result.rows).toBe("number");
});
test("should include session feedback metrics", () => {
const result = exportFeedback(mockTrainingState);
expect(result).toHaveProperty("data");
expect(typeof result.data).toBe("string");
expect(result.data).toContain("date");
expect(result.data).toContain("difficulty");
expect(result.data).toContain("satisfaction");
});
test("should handle missing feedback data", () => {
const noFeedbackState = {
currentMesocycle: { currentWeek: 1 },
weeklyProgram: { actualVolume: {} },
};
const result = exportFeedback(noFeedbackState);
expect(result).toHaveProperty("success", true);
expect(result).toHaveProperty("warnings");
expect(result.warnings).toContain("No feedback data found");
});
});
describe("Integration Tests", () => {
test("should support complete data lifecycle", () => {
const originalState = {
currentMesocycle: { currentWeek: 3, length: 6 },
weeklyProgram: { actualVolume: { chest: [12, 14, 16] } },
volumeLandmarks: { chest: { mv: 8, mev: 10, mav: 16, mrv: 20 } },
};
// 1. Export data
const exportResult = exportAllData(originalState);
expect(exportResult.success).toBe(true);
// 2. Create backup
const backupResult = createBackup(originalState);
expect(backupResult.success).toBe(true);
// 3. Import the exported data
const importResult = importData(JSON.stringify(exportResult.data));
expect(importResult.success).toBe(true);
// 4. Verify data integrity
expect(importResult.importedData.currentMesocycle.currentWeek).toBe(3);
expect(importResult.importedData.volumeLandmarks.chest.mv).toBe(8);
});
test("should handle auto-backup workflow", () => {
const state = {
currentMesocycle: { currentWeek: 1, length: 6 },
dataManagement: { autoBackupEnabled: true, lastBackup: null },
};
// Enable auto-backup
const autoResult = autoBackup(state);
expect(autoResult.success).toBe(true);
expect(autoResult.backupCreated).toBe(true);
// Subsequent call should respect frequency limits
const updatedState = {
...state,
dataManagement: {
autoBackupEnabled: true,
lastBackup: new Date().toISOString(),
},
};
const secondResult = autoBackup(updatedState);
expect(secondResult.success).toBe(true);
expect(secondResult.backupCreated).toBe(false);
});
});
});