TUI is a doomed path. Stick with CLI
This commit is contained in:
239
tests/tui/utils/PerformanceOptimizer.test.js
Normal file
239
tests/tui/utils/PerformanceOptimizer.test.js
Normal file
@@ -0,0 +1,239 @@
|
||||
const PerformanceOptimizer = require("../../../src/tui/utils/PerformanceOptimizer.js");
|
||||
|
||||
describe("PerformanceOptimizer", () => {
|
||||
let optimizer;
|
||||
|
||||
beforeEach(() => {
|
||||
optimizer = new PerformanceOptimizer();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
optimizer.destroy();
|
||||
});
|
||||
|
||||
describe("debounce", () => {
|
||||
it("should debounce function calls", (done) => {
|
||||
let callCount = 0;
|
||||
const testFunction = () => {
|
||||
callCount++;
|
||||
};
|
||||
|
||||
const debouncedFunction = optimizer.debounce(testFunction, 100, "test");
|
||||
|
||||
// Call multiple times rapidly
|
||||
debouncedFunction();
|
||||
debouncedFunction();
|
||||
debouncedFunction();
|
||||
|
||||
// Should not have been called yet
|
||||
expect(callCount).toBe(0);
|
||||
|
||||
// Wait for debounce delay
|
||||
setTimeout(() => {
|
||||
expect(callCount).toBe(1);
|
||||
done();
|
||||
}, 150);
|
||||
});
|
||||
});
|
||||
|
||||
describe("throttle", () => {
|
||||
it("should throttle function calls", (done) => {
|
||||
let callCount = 0;
|
||||
const testFunction = () => {
|
||||
callCount++;
|
||||
};
|
||||
|
||||
const throttledFunction = optimizer.throttle(testFunction, 100, "test");
|
||||
|
||||
// Call multiple times rapidly
|
||||
throttledFunction();
|
||||
throttledFunction();
|
||||
throttledFunction();
|
||||
|
||||
// Should have been called once immediately
|
||||
expect(callCount).toBe(1);
|
||||
|
||||
// Wait and call again
|
||||
setTimeout(() => {
|
||||
throttledFunction();
|
||||
expect(callCount).toBe(2);
|
||||
done();
|
||||
}, 150);
|
||||
});
|
||||
});
|
||||
|
||||
describe("memoize", () => {
|
||||
it("should memoize function results", () => {
|
||||
let callCount = 0;
|
||||
const expensiveFunction = (x, y) => {
|
||||
callCount++;
|
||||
return x + y;
|
||||
};
|
||||
|
||||
const memoizedFunction = optimizer.memoize(expensiveFunction);
|
||||
|
||||
// First call
|
||||
const result1 = memoizedFunction(1, 2);
|
||||
expect(result1).toBe(3);
|
||||
expect(callCount).toBe(1);
|
||||
|
||||
// Second call with same arguments
|
||||
const result2 = memoizedFunction(1, 2);
|
||||
expect(result2).toBe(3);
|
||||
expect(callCount).toBe(1); // Should not have called function again
|
||||
|
||||
// Third call with different arguments
|
||||
const result3 = memoizedFunction(2, 3);
|
||||
expect(result3).toBe(5);
|
||||
expect(callCount).toBe(2);
|
||||
});
|
||||
|
||||
it("should limit cache size", () => {
|
||||
const testFunction = (x) => x * 2;
|
||||
const memoizedFunction = optimizer.memoize(testFunction, undefined, 2);
|
||||
|
||||
// Fill cache beyond limit
|
||||
memoizedFunction(1);
|
||||
memoizedFunction(2);
|
||||
memoizedFunction(3); // Should evict first entry
|
||||
|
||||
// Verify first entry was evicted
|
||||
let callCount = 0;
|
||||
const countingFunction = (x) => {
|
||||
callCount++;
|
||||
return x * 2;
|
||||
};
|
||||
const countingMemoized = optimizer.memoize(
|
||||
countingFunction,
|
||||
undefined,
|
||||
2
|
||||
);
|
||||
|
||||
countingMemoized(1);
|
||||
countingMemoized(2);
|
||||
expect(callCount).toBe(2);
|
||||
|
||||
countingMemoized(3); // Should evict entry for 1
|
||||
expect(callCount).toBe(3);
|
||||
|
||||
countingMemoized(1); // Should call function again since it was evicted
|
||||
expect(callCount).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createVirtualScrolling", () => {
|
||||
it("should calculate virtual scrolling data correctly", () => {
|
||||
const items = Array.from({ length: 100 }, (_, i) => ({
|
||||
id: i,
|
||||
name: `Item ${i}`,
|
||||
}));
|
||||
const result = optimizer.createVirtualScrolling(items, 300, 30, 150);
|
||||
|
||||
expect(result.totalHeight).toBe(3000); // 100 items * 30px each
|
||||
expect(result.startIndex).toBe(5); // 150px / 30px per item
|
||||
expect(result.visibleItems.length).toBeGreaterThan(0);
|
||||
expect(result.visibleItems.length).toBeLessThanOrEqual(12); // Visible count + buffer
|
||||
});
|
||||
});
|
||||
|
||||
describe("createLazyLoading", () => {
|
||||
it("should create lazy loading data correctly", () => {
|
||||
const items = Array.from({ length: 100 }, (_, i) => ({
|
||||
id: i,
|
||||
name: `Item ${i}`,
|
||||
}));
|
||||
const result = optimizer.createLazyLoading(items, 10, 5);
|
||||
|
||||
expect(result.startIndex).toBe(5); // 10 - 5
|
||||
expect(result.endIndex).toBe(20); // 10 + 5*2
|
||||
expect(result.loadedItems.length).toBe(15); // 20 - 5
|
||||
expect(result.hasMore).toBe(true);
|
||||
expect(result.hasPrevious).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("memory management", () => {
|
||||
it("should track memory usage", () => {
|
||||
const stats = optimizer.getMemoryUsage();
|
||||
expect(stats).toHaveProperty("estimatedSizeBytes");
|
||||
expect(stats).toHaveProperty("estimatedSizeMB");
|
||||
expect(stats).toHaveProperty("cacheEntries");
|
||||
expect(stats).toHaveProperty("eventListeners");
|
||||
expect(stats).toHaveProperty("activeTimers");
|
||||
expect(stats).toHaveProperty("memoryPressure");
|
||||
});
|
||||
|
||||
it("should clean up expired cache entries", () => {
|
||||
// Add some cache entries
|
||||
optimizer.componentCache.set("test1", {
|
||||
data: "test",
|
||||
timestamp: Date.now() - 10000,
|
||||
});
|
||||
optimizer.componentCache.set("test2", {
|
||||
data: "test",
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
optimizer.cleanupExpiredCache(5000); // 5 second max age
|
||||
|
||||
expect(optimizer.componentCache.has("test1")).toBe(false);
|
||||
expect(optimizer.componentCache.has("test2")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("event listener management", () => {
|
||||
it("should register and cleanup event listeners", () => {
|
||||
const mockTarget = {
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
};
|
||||
|
||||
const handler = () => {};
|
||||
|
||||
optimizer.registerEventListener(
|
||||
"component1",
|
||||
"click",
|
||||
handler,
|
||||
mockTarget
|
||||
);
|
||||
expect(mockTarget.addEventListener).toHaveBeenCalledWith(
|
||||
"click",
|
||||
handler
|
||||
);
|
||||
|
||||
optimizer.cleanupEventListeners("component1");
|
||||
expect(mockTarget.removeEventListener).toHaveBeenCalledWith(
|
||||
"click",
|
||||
handler
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("batched updates", () => {
|
||||
it("should batch updates correctly", (done) => {
|
||||
let batchedUpdates = [];
|
||||
const updateFunction = async (batch) => {
|
||||
batchedUpdates.push(batch);
|
||||
};
|
||||
|
||||
const batchedUpdate = optimizer.createBatchedUpdate(
|
||||
updateFunction,
|
||||
3,
|
||||
50
|
||||
);
|
||||
|
||||
// Add updates
|
||||
batchedUpdate("update1");
|
||||
batchedUpdate("update2");
|
||||
batchedUpdate("update3");
|
||||
batchedUpdate("update4");
|
||||
|
||||
// Wait for batching
|
||||
setTimeout(() => {
|
||||
expect(batchedUpdates.length).toBeGreaterThan(0);
|
||||
expect(batchedUpdates[0]).toEqual(["update1", "update2", "update3"]);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
130
tests/tui/utils/inputValidator.test.js
Normal file
130
tests/tui/utils/inputValidator.test.js
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Input Validator Tests
|
||||
* Tests for comprehensive input validation
|
||||
* Requirements: 5.4, 5.6
|
||||
*/
|
||||
|
||||
const inputValidator = require("../../../src/tui/utils/inputValidator");
|
||||
|
||||
describe("InputValidator Tests", () => {
|
||||
test("should validate operation type correctly", () => {
|
||||
const validResult = inputValidator.validateField("operationType", "update");
|
||||
expect(validResult.isValid).toBe(true);
|
||||
expect(validResult.value).toBe("update");
|
||||
|
||||
const invalidResult = inputValidator.validateField(
|
||||
"operationType",
|
||||
"invalid"
|
||||
);
|
||||
expect(invalidResult.isValid).toBe(false);
|
||||
expect(invalidResult.errors).toContain(
|
||||
"operationType must be one of: update, rollback"
|
||||
);
|
||||
});
|
||||
|
||||
test("should validate scheduled time correctly", () => {
|
||||
const futureDate = new Date(Date.now() + 86400000).toISOString();
|
||||
const validResult = inputValidator.validateField(
|
||||
"scheduledTime",
|
||||
futureDate
|
||||
);
|
||||
expect(validResult.isValid).toBe(true);
|
||||
|
||||
const pastDate = new Date(Date.now() - 86400000).toISOString();
|
||||
const invalidResult = inputValidator.validateField(
|
||||
"scheduledTime",
|
||||
pastDate
|
||||
);
|
||||
expect(invalidResult.isValid).toBe(false);
|
||||
});
|
||||
|
||||
test("should validate shop domain correctly", () => {
|
||||
const validDomain = "test-store.myshopify.com";
|
||||
const validResult = inputValidator.validateField("shopDomain", validDomain);
|
||||
expect(validResult.isValid).toBe(true);
|
||||
|
||||
const invalidDomain = "invalid domain";
|
||||
const invalidResult = inputValidator.validateField(
|
||||
"shopDomain",
|
||||
invalidDomain
|
||||
);
|
||||
expect(invalidResult.isValid).toBe(false);
|
||||
});
|
||||
|
||||
test("should validate price adjustment correctly", () => {
|
||||
const validPercentage = 25.5;
|
||||
const validResult = inputValidator.validateField(
|
||||
"priceAdjustment",
|
||||
validPercentage
|
||||
);
|
||||
expect(validResult.isValid).toBe(true);
|
||||
expect(validResult.value).toBe(25.5);
|
||||
|
||||
const invalidPercentage = 1500; // Too high
|
||||
const invalidResult = inputValidator.validateField(
|
||||
"priceAdjustment",
|
||||
invalidPercentage
|
||||
);
|
||||
expect(invalidResult.isValid).toBe(false);
|
||||
});
|
||||
|
||||
test("should validate multiple fields", () => {
|
||||
const data = {
|
||||
operationType: "update",
|
||||
scheduledTime: new Date(Date.now() + 86400000).toISOString(),
|
||||
recurrence: "weekly",
|
||||
description: "Test schedule",
|
||||
};
|
||||
|
||||
const result = inputValidator.validateFields(data);
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.data.operationType).toBe("update");
|
||||
expect(result.data.recurrence).toBe("weekly");
|
||||
});
|
||||
|
||||
test("should handle optional fields correctly", () => {
|
||||
const data = {
|
||||
operationType: "update",
|
||||
scheduledTime: new Date(Date.now() + 86400000).toISOString(),
|
||||
recurrence: "once",
|
||||
// description is optional and missing
|
||||
};
|
||||
|
||||
const result = inputValidator.validateFields(data);
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.data.description).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should convert string numbers to numbers", () => {
|
||||
const result = inputValidator.validateField("priceAdjustment", "25.5");
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.value).toBe(25.5);
|
||||
expect(typeof result.value).toBe("number");
|
||||
});
|
||||
|
||||
test("should sanitize input strings", () => {
|
||||
const dirtyInput = " test string with \x00 control chars ";
|
||||
const sanitized = inputValidator.sanitizeInput(dirtyInput, {
|
||||
trim: true,
|
||||
removeControlChars: true,
|
||||
});
|
||||
|
||||
expect(sanitized).toBe("test string with control chars");
|
||||
});
|
||||
|
||||
test("should validate string length limits", () => {
|
||||
const longDescription = "x".repeat(501);
|
||||
const result = inputValidator.validateField("description", longDescription);
|
||||
|
||||
expect(result.isValid).toBe(false);
|
||||
expect(
|
||||
result.errors.some((error) => error.includes("500 characters"))
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("should validate required fields", () => {
|
||||
const result = inputValidator.validateField("operationType", "");
|
||||
expect(result.isValid).toBe(false);
|
||||
expect(result.errors).toContain("operationType is required");
|
||||
});
|
||||
});
|
||||
122
tests/tui/utils/stateManager.test.js
Normal file
122
tests/tui/utils/stateManager.test.js
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* State Manager Tests
|
||||
* Tests for state management and cleanup functionality
|
||||
* Requirements: 5.4, 5.6
|
||||
*/
|
||||
|
||||
const stateManager = require("../../../src/tui/utils/stateManager");
|
||||
|
||||
describe("StateManager Tests", () => {
|
||||
beforeEach(() => {
|
||||
// Clear all states before each test
|
||||
stateManager.clearAllStates();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Cleanup after each test
|
||||
stateManager.clearAllStates();
|
||||
});
|
||||
|
||||
test("should register screen handlers", () => {
|
||||
const mockCleanup = jest.fn();
|
||||
const mockValidate = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ isValid: true, errors: [] });
|
||||
|
||||
stateManager.registerScreen("test-screen", {
|
||||
cleanup: mockCleanup,
|
||||
validate: mockValidate,
|
||||
});
|
||||
|
||||
expect(stateManager.cleanupHandlers.has("test-screen")).toBe(true);
|
||||
expect(stateManager.stateValidators.has("test-screen")).toBe(true);
|
||||
});
|
||||
|
||||
test("should save and restore screen state", async () => {
|
||||
const testState = {
|
||||
selectedIndex: 5,
|
||||
formData: { name: "test" },
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
await stateManager.saveScreenState("test-screen", testState);
|
||||
const restoredState = await stateManager.restoreScreenState("test-screen");
|
||||
|
||||
expect(restoredState.selectedIndex).toBe(5);
|
||||
expect(restoredState.formData.name).toBe("test");
|
||||
expect(restoredState._metadata).toBeUndefined(); // Metadata should be stripped
|
||||
});
|
||||
|
||||
test("should perform screen transitions with cleanup", async () => {
|
||||
const mockCleanup = jest.fn().mockResolvedValue();
|
||||
|
||||
stateManager.registerScreen("from-screen", {
|
||||
cleanup: mockCleanup,
|
||||
});
|
||||
|
||||
const currentState = { data: "test" };
|
||||
|
||||
await stateManager.switchScreen("from-screen", "to-screen", currentState);
|
||||
|
||||
expect(mockCleanup).toHaveBeenCalled();
|
||||
expect(stateManager.activeScreen).toBe("to-screen");
|
||||
});
|
||||
|
||||
test("should validate states", async () => {
|
||||
const mockValidator = jest.fn().mockResolvedValue({
|
||||
isValid: false,
|
||||
errors: ["Test error"],
|
||||
});
|
||||
|
||||
stateManager.registerScreen("test-screen", {
|
||||
validate: mockValidator,
|
||||
});
|
||||
|
||||
await stateManager.saveScreenState("test-screen", { data: "test" });
|
||||
|
||||
const report = await stateManager.validateAllStates();
|
||||
|
||||
expect(report.invalidStates).toBe(1);
|
||||
expect(report.errors).toHaveLength(1);
|
||||
expect(mockValidator).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("should provide memory statistics", () => {
|
||||
stateManager.saveScreenState("screen1", { data: "test1" });
|
||||
stateManager.saveScreenState("screen2", { data: "test2" });
|
||||
|
||||
const stats = stateManager.getMemoryStats();
|
||||
|
||||
expect(stats.screenCount).toBe(2);
|
||||
expect(stats.totalSize).toBeGreaterThan(0);
|
||||
expect(stats.screenSizes).toHaveProperty("screen1");
|
||||
expect(stats.screenSizes).toHaveProperty("screen2");
|
||||
});
|
||||
|
||||
test("should track navigation history", async () => {
|
||||
await stateManager.switchScreen("screen1", "screen2", {});
|
||||
await stateManager.switchScreen("screen2", "screen3", {});
|
||||
|
||||
const history = stateManager.getHistory(5);
|
||||
|
||||
expect(history).toHaveLength(2);
|
||||
expect(history[0].from).toBe("screen2");
|
||||
expect(history[0].to).toBe("screen3");
|
||||
expect(history[1].from).toBe("screen1");
|
||||
expect(history[1].to).toBe("screen2");
|
||||
});
|
||||
|
||||
test("should clear screen states", () => {
|
||||
stateManager.saveScreenState("screen1", { data: "test1" });
|
||||
stateManager.saveScreenState("screen2", { data: "test2" });
|
||||
|
||||
expect(stateManager.screenStates.size).toBe(2);
|
||||
|
||||
stateManager.clearScreenState("screen1");
|
||||
expect(stateManager.screenStates.size).toBe(1);
|
||||
expect(stateManager.screenStates.has("screen1")).toBe(false);
|
||||
|
||||
stateManager.clearAllStates();
|
||||
expect(stateManager.screenStates.size).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user