669 lines
20 KiB
JavaScript
669 lines
20 KiB
JavaScript
const React = require("react");
|
|
const { render } = require("ink-testing-library");
|
|
const TuiApplication = require("../../../src/tui/TuiApplication.jsx");
|
|
|
|
// Mock all the services and providers
|
|
jest.mock("../../../src/tui/providers/AppProvider.jsx");
|
|
jest.mock("../../../src/tui/hooks/useServices.js");
|
|
jest.mock("../../../src/tui/components/common/LoadingIndicator.jsx");
|
|
jest.mock("../../../src/tui/components/common/ErrorDisplay.jsx");
|
|
|
|
describe("Error Handling and Recovery Integration Tests", () => {
|
|
let mockAppState;
|
|
let mockServices;
|
|
let mockUseInput;
|
|
|
|
beforeEach(() => {
|
|
// Reset all mocks
|
|
jest.clearAllMocks();
|
|
|
|
// Mock AppProvider
|
|
mockAppState = {
|
|
currentScreen: "main",
|
|
navigateTo: jest.fn(),
|
|
navigateBack: jest.fn(),
|
|
getScreenState: jest.fn(),
|
|
saveScreenState: jest.fn(),
|
|
updateConfiguration: jest.fn(),
|
|
getConfiguration: jest.fn(() => ({
|
|
targetTag: "test-tag",
|
|
shopDomain: "test-shop.myshopify.com",
|
|
accessToken: "test-token",
|
|
})),
|
|
};
|
|
|
|
require("../../../src/tui/providers/AppProvider.jsx").useAppState = jest.fn(
|
|
() => mockAppState
|
|
);
|
|
|
|
// Mock Services
|
|
mockServices = {
|
|
getAllSchedules: jest.fn(),
|
|
addSchedule: jest.fn(),
|
|
updateSchedule: jest.fn(),
|
|
deleteSchedule: jest.fn(),
|
|
getLogFiles: jest.fn(),
|
|
readLogFile: jest.fn(),
|
|
parseLogContent: jest.fn(),
|
|
filterLogs: jest.fn(),
|
|
fetchAllTags: jest.fn(),
|
|
getTagDetails: jest.fn(),
|
|
calculateTagStatistics: jest.fn(),
|
|
searchTags: jest.fn(),
|
|
};
|
|
|
|
require("../../../src/tui/hooks/useServices.js").useServices = jest.fn(
|
|
() => mockServices
|
|
);
|
|
|
|
// Mock useInput
|
|
mockUseInput = jest.fn();
|
|
require("ink").useInput = mockUseInput;
|
|
|
|
// Mock common components
|
|
require("../../../src/tui/components/common/LoadingIndicator.jsx").LoadingIndicator =
|
|
({ children }) =>
|
|
React.createElement("div", { "data-testid": "loading" }, children);
|
|
|
|
require("../../../src/tui/components/common/ErrorDisplay.jsx").ErrorDisplay =
|
|
({ error, onRetry }) =>
|
|
React.createElement(
|
|
"div",
|
|
{
|
|
"data-testid": "error",
|
|
onClick: onRetry,
|
|
},
|
|
error?.message || "An error occurred"
|
|
);
|
|
});
|
|
|
|
describe("Network Error Handling", () => {
|
|
test("should handle network timeouts gracefully in scheduling screen", async () => {
|
|
const networkError = new Error("Network timeout");
|
|
networkError.code = "NETWORK_TIMEOUT";
|
|
|
|
mockServices.getAllSchedules.mockRejectedValue(networkError);
|
|
mockAppState.currentScreen = "scheduling";
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Network timeout");
|
|
expect(lastFrame()).toContain("Check your internet connection");
|
|
});
|
|
|
|
test("should handle connection refused errors in tag analysis screen", async () => {
|
|
const connectionError = new Error("Connection refused");
|
|
connectionError.code = "ECONNREFUSED";
|
|
|
|
mockServices.fetchAllTags.mockRejectedValue(connectionError);
|
|
mockAppState.currentScreen = "tagAnalysis";
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Connection refused");
|
|
expect(lastFrame()).toContain("Unable to connect to Shopify");
|
|
});
|
|
|
|
test("should provide retry functionality for network errors", async () => {
|
|
const networkError = new Error("Network error");
|
|
|
|
mockServices.getLogFiles
|
|
.mockRejectedValueOnce(networkError)
|
|
.mockResolvedValueOnce([]);
|
|
|
|
mockAppState.currentScreen = "viewLogs";
|
|
|
|
let inputHandler;
|
|
mockUseInput.mockImplementation((handler) => {
|
|
inputHandler = handler;
|
|
});
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Network error");
|
|
|
|
// Retry operation
|
|
inputHandler("r");
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(mockServices.getLogFiles).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
test("should implement exponential backoff for repeated network failures", async () => {
|
|
const networkError = new Error("Network unstable");
|
|
|
|
mockServices.fetchAllTags
|
|
.mockRejectedValueOnce(networkError)
|
|
.mockRejectedValueOnce(networkError)
|
|
.mockResolvedValueOnce([]);
|
|
|
|
mockAppState.currentScreen = "tagAnalysis";
|
|
|
|
let inputHandler;
|
|
mockUseInput.mockImplementation((handler) => {
|
|
inputHandler = handler;
|
|
});
|
|
|
|
render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
// First retry
|
|
inputHandler("r");
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
// Second retry (should have longer delay)
|
|
inputHandler("r");
|
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
|
|
expect(mockServices.fetchAllTags).toHaveBeenCalledTimes(3);
|
|
});
|
|
});
|
|
|
|
describe("API Error Handling", () => {
|
|
test("should handle Shopify API rate limiting", async () => {
|
|
const rateLimitError = new Error("Rate limit exceeded");
|
|
rateLimitError.code = "RATE_LIMITED";
|
|
rateLimitError.retryAfter = 5;
|
|
|
|
mockServices.fetchAllTags.mockRejectedValue(rateLimitError);
|
|
mockAppState.currentScreen = "tagAnalysis";
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Rate limit exceeded");
|
|
expect(lastFrame()).toContain("Please wait 5 seconds");
|
|
});
|
|
|
|
test("should handle authentication errors", async () => {
|
|
const authError = new Error("Invalid access token");
|
|
authError.code = "UNAUTHORIZED";
|
|
|
|
mockServices.fetchAllTags.mockRejectedValue(authError);
|
|
mockAppState.currentScreen = "tagAnalysis";
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Invalid access token");
|
|
expect(lastFrame()).toContain("Check your Shopify credentials");
|
|
expect(lastFrame()).toContain("Go to Configuration");
|
|
});
|
|
|
|
test("should handle API permission errors", async () => {
|
|
const permissionError = new Error("Insufficient permissions");
|
|
permissionError.code = "FORBIDDEN";
|
|
|
|
mockServices.getTagDetails.mockRejectedValue(permissionError);
|
|
mockAppState.currentScreen = "tagAnalysis";
|
|
|
|
let inputHandler;
|
|
mockUseInput.mockImplementation((handler) => {
|
|
inputHandler = handler;
|
|
});
|
|
|
|
mockServices.fetchAllTags.mockResolvedValue([
|
|
{ tag: "test-tag", productCount: 1, variantCount: 1, totalValue: 100 },
|
|
]);
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
// Try to view tag details
|
|
inputHandler("", { return: true });
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Insufficient permissions");
|
|
expect(lastFrame()).toContain(
|
|
"Your API token may not have the required permissions"
|
|
);
|
|
});
|
|
|
|
test("should handle API version compatibility errors", async () => {
|
|
const versionError = new Error("API version not supported");
|
|
versionError.code = "API_VERSION_MISMATCH";
|
|
|
|
mockServices.fetchAllTags.mockRejectedValue(versionError);
|
|
mockAppState.currentScreen = "tagAnalysis";
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("API version not supported");
|
|
expect(lastFrame()).toContain("Please update the application");
|
|
});
|
|
});
|
|
|
|
describe("File System Error Handling", () => {
|
|
test("should handle missing schedules.json file gracefully", async () => {
|
|
const fileError = new Error("ENOENT: no such file or directory");
|
|
fileError.code = "ENOENT";
|
|
|
|
mockServices.getAllSchedules.mockRejectedValue(fileError);
|
|
mockAppState.currentScreen = "scheduling";
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("No schedules found");
|
|
expect(lastFrame()).toContain("Create your first schedule");
|
|
});
|
|
|
|
test("should handle corrupted log files", async () => {
|
|
const mockLogFiles = [
|
|
{ filename: "corrupted.md", size: 1024, operationCount: 5 },
|
|
];
|
|
|
|
mockServices.getLogFiles.mockResolvedValue(mockLogFiles);
|
|
mockServices.readLogFile.mockResolvedValue("Corrupted content");
|
|
mockServices.parseLogContent.mockImplementation(() => {
|
|
throw new Error("Failed to parse log content");
|
|
});
|
|
|
|
mockAppState.currentScreen = "viewLogs";
|
|
|
|
let inputHandler;
|
|
mockUseInput.mockImplementation((handler) => {
|
|
inputHandler = handler;
|
|
});
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
// Select corrupted log file
|
|
inputHandler("", { return: true });
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Failed to parse log content");
|
|
expect(lastFrame()).toContain("Showing raw content");
|
|
});
|
|
|
|
test("should handle permission denied errors for file operations", async () => {
|
|
const permissionError = new Error("Permission denied");
|
|
permissionError.code = "EACCES";
|
|
|
|
mockServices.addSchedule.mockRejectedValue(permissionError);
|
|
mockAppState.currentScreen = "scheduling";
|
|
|
|
mockServices.getAllSchedules.mockResolvedValue([]);
|
|
|
|
let inputHandler;
|
|
mockUseInput.mockImplementation((handler) => {
|
|
inputHandler = handler;
|
|
});
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
// Try to create new schedule
|
|
inputHandler("n");
|
|
inputHandler("", { return: true });
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Permission denied");
|
|
expect(lastFrame()).toContain("Check file permissions");
|
|
});
|
|
|
|
test("should handle disk space errors", async () => {
|
|
const diskSpaceError = new Error("No space left on device");
|
|
diskSpaceError.code = "ENOSPC";
|
|
|
|
mockServices.addSchedule.mockRejectedValue(diskSpaceError);
|
|
mockAppState.currentScreen = "scheduling";
|
|
|
|
mockServices.getAllSchedules.mockResolvedValue([]);
|
|
|
|
let inputHandler;
|
|
mockUseInput.mockImplementation((handler) => {
|
|
inputHandler = handler;
|
|
});
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
// Try to create new schedule
|
|
inputHandler("n");
|
|
inputHandler("", { return: true });
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("No space left on device");
|
|
expect(lastFrame()).toContain("Free up disk space");
|
|
});
|
|
});
|
|
|
|
describe("Validation Error Handling", () => {
|
|
test("should handle form validation errors in scheduling screen", async () => {
|
|
mockServices.getAllSchedules.mockResolvedValue([]);
|
|
|
|
const validationError = new Error("Invalid schedule data");
|
|
validationError.code = "VALIDATION_ERROR";
|
|
validationError.details = {
|
|
scheduledTime: "Invalid date format",
|
|
operationType: "Must be 'update' or 'rollback'",
|
|
};
|
|
|
|
mockServices.addSchedule.mockRejectedValue(validationError);
|
|
mockAppState.currentScreen = "scheduling";
|
|
|
|
let inputHandler;
|
|
mockUseInput.mockImplementation((handler) => {
|
|
inputHandler = handler;
|
|
});
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
// Try to create invalid schedule
|
|
inputHandler("n");
|
|
inputHandler("", { return: true });
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Invalid date format");
|
|
expect(lastFrame()).toContain("Must be 'update' or 'rollback'");
|
|
});
|
|
|
|
test("should handle configuration validation errors", async () => {
|
|
const configError = new Error("Invalid configuration");
|
|
configError.code = "CONFIG_INVALID";
|
|
|
|
mockAppState.updateConfiguration.mockImplementation(() => {
|
|
throw configError;
|
|
});
|
|
|
|
mockServices.fetchAllTags.mockResolvedValue([
|
|
{ tag: "test-tag", productCount: 1, variantCount: 1, totalValue: 100 },
|
|
]);
|
|
|
|
mockAppState.currentScreen = "tagAnalysis";
|
|
|
|
let inputHandler;
|
|
mockUseInput.mockImplementation((handler) => {
|
|
inputHandler = handler;
|
|
});
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
// Try to update configuration with invalid tag
|
|
inputHandler("c");
|
|
inputHandler("y");
|
|
|
|
expect(lastFrame()).toContain("Invalid configuration");
|
|
});
|
|
});
|
|
|
|
describe("Recovery Mechanisms", () => {
|
|
test("should automatically retry failed operations with exponential backoff", async () => {
|
|
const transientError = new Error("Temporary service unavailable");
|
|
transientError.code = "SERVICE_UNAVAILABLE";
|
|
|
|
mockServices.fetchAllTags
|
|
.mockRejectedValueOnce(transientError)
|
|
.mockRejectedValueOnce(transientError)
|
|
.mockResolvedValueOnce([]);
|
|
|
|
mockAppState.currentScreen = "tagAnalysis";
|
|
|
|
let inputHandler;
|
|
mockUseInput.mockImplementation((handler) => {
|
|
inputHandler = handler;
|
|
});
|
|
|
|
render(React.createElement(TuiApplication));
|
|
|
|
// Should automatically retry
|
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
|
|
expect(mockServices.fetchAllTags).toHaveBeenCalledTimes(3);
|
|
});
|
|
|
|
test("should provide manual retry option for persistent errors", async () => {
|
|
const persistentError = new Error("Service down for maintenance");
|
|
|
|
mockServices.getAllSchedules
|
|
.mockRejectedValue(persistentError)
|
|
.mockRejectedValue(persistentError)
|
|
.mockResolvedValue([]);
|
|
|
|
mockAppState.currentScreen = "scheduling";
|
|
|
|
let inputHandler;
|
|
mockUseInput.mockImplementation((handler) => {
|
|
inputHandler = handler;
|
|
});
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Service down for maintenance");
|
|
expect(lastFrame()).toContain("Press 'r' to retry");
|
|
|
|
// Manual retry
|
|
inputHandler("r");
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(mockServices.getAllSchedules).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
test("should fallback to cached data when available", async () => {
|
|
const networkError = new Error("Network unavailable");
|
|
|
|
// Mock cached data
|
|
mockAppState.getScreenState.mockReturnValue({
|
|
cachedTags: [
|
|
{
|
|
tag: "cached-tag",
|
|
productCount: 5,
|
|
variantCount: 15,
|
|
totalValue: 500,
|
|
},
|
|
],
|
|
lastFetch: Date.now() - 300000, // 5 minutes ago
|
|
});
|
|
|
|
mockServices.fetchAllTags.mockRejectedValue(networkError);
|
|
mockAppState.currentScreen = "tagAnalysis";
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("cached-tag");
|
|
expect(lastFrame()).toContain("Using cached data");
|
|
expect(lastFrame()).toContain("5 minutes ago");
|
|
});
|
|
|
|
test("should gracefully degrade functionality when services are unavailable", async () => {
|
|
const serviceError = new Error("All services unavailable");
|
|
|
|
mockServices.getAllSchedules.mockRejectedValue(serviceError);
|
|
mockServices.getLogFiles.mockRejectedValue(serviceError);
|
|
mockServices.fetchAllTags.mockRejectedValue(serviceError);
|
|
|
|
// Test each screen handles degraded mode
|
|
const screens = ["scheduling", "viewLogs", "tagAnalysis"];
|
|
|
|
for (const screen of screens) {
|
|
mockAppState.currentScreen = screen;
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Service unavailable");
|
|
expect(lastFrame()).toContain("Limited functionality");
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("Error State Management", () => {
|
|
test("should clear error state when operation succeeds", async () => {
|
|
const temporaryError = new Error("Temporary error");
|
|
|
|
mockServices.getAllSchedules
|
|
.mockRejectedValueOnce(temporaryError)
|
|
.mockResolvedValueOnce([]);
|
|
|
|
mockAppState.currentScreen = "scheduling";
|
|
|
|
let inputHandler;
|
|
mockUseInput.mockImplementation((handler) => {
|
|
inputHandler = handler;
|
|
});
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Temporary error");
|
|
|
|
// Retry and succeed
|
|
inputHandler("r");
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).not.toContain("Temporary error");
|
|
expect(lastFrame()).toContain("No schedules found");
|
|
});
|
|
|
|
test("should persist error state across screen navigation", async () => {
|
|
const persistentError = new Error("Configuration error");
|
|
|
|
mockServices.fetchAllTags.mockRejectedValue(persistentError);
|
|
mockAppState.currentScreen = "tagAnalysis";
|
|
|
|
let inputHandler;
|
|
mockUseInput.mockImplementation((handler) => {
|
|
inputHandler = handler;
|
|
});
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Configuration error");
|
|
|
|
// Navigate away and back
|
|
inputHandler("", { escape: true });
|
|
mockAppState.currentScreen = "main";
|
|
|
|
// Navigate back to tag analysis
|
|
inputHandler("t");
|
|
mockAppState.currentScreen = "tagAnalysis";
|
|
|
|
// Error should be saved in screen state
|
|
expect(mockAppState.saveScreenState).toHaveBeenCalledWith(
|
|
"tagAnalysis",
|
|
expect.objectContaining({
|
|
error: expect.any(Object),
|
|
})
|
|
);
|
|
});
|
|
|
|
test("should provide error context and troubleshooting guidance", async () => {
|
|
const contextualError = new Error("Shop not found");
|
|
contextualError.code = "SHOP_NOT_FOUND";
|
|
contextualError.context = {
|
|
shopDomain: "invalid-shop.myshopify.com",
|
|
suggestion: "Verify your shop domain in configuration",
|
|
};
|
|
|
|
mockServices.fetchAllTags.mockRejectedValue(contextualError);
|
|
mockAppState.currentScreen = "tagAnalysis";
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Shop not found");
|
|
expect(lastFrame()).toContain("invalid-shop.myshopify.com");
|
|
expect(lastFrame()).toContain("Verify your shop domain");
|
|
});
|
|
});
|
|
|
|
describe("Critical Error Handling", () => {
|
|
test("should handle application crashes gracefully", async () => {
|
|
const criticalError = new Error("Critical system error");
|
|
criticalError.code = "CRITICAL";
|
|
|
|
// Mock a critical error that would crash the app
|
|
mockServices.getAllSchedules.mockImplementation(() => {
|
|
throw criticalError;
|
|
});
|
|
|
|
mockAppState.currentScreen = "scheduling";
|
|
|
|
// Should not crash the entire application
|
|
expect(() => {
|
|
render(React.createElement(TuiApplication));
|
|
}).not.toThrow();
|
|
});
|
|
|
|
test("should provide safe mode when multiple services fail", async () => {
|
|
const systemError = new Error("System failure");
|
|
|
|
// All services fail
|
|
Object.keys(mockServices).forEach((service) => {
|
|
mockServices[service].mockRejectedValue(systemError);
|
|
});
|
|
|
|
mockAppState.currentScreen = "main";
|
|
|
|
const { lastFrame } = render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(lastFrame()).toContain("Safe mode");
|
|
expect(lastFrame()).toContain("Limited functionality available");
|
|
});
|
|
|
|
test("should log critical errors for debugging", async () => {
|
|
const criticalError = new Error("Memory allocation failed");
|
|
criticalError.code = "ENOMEM";
|
|
|
|
mockServices.fetchAllTags.mockRejectedValue(criticalError);
|
|
mockAppState.currentScreen = "tagAnalysis";
|
|
|
|
// Mock console.error to capture error logging
|
|
const consoleSpy = jest
|
|
.spyOn(console, "error")
|
|
.mockImplementation(() => {});
|
|
|
|
render(React.createElement(TuiApplication));
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
|
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
expect.stringContaining("Critical error"),
|
|
expect.any(Error)
|
|
);
|
|
|
|
consoleSpy.mockRestore();
|
|
});
|
|
});
|
|
});
|