Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ catalog:
"@eslint/js": 9.39.1
"@headlessui/react": ^1.7.19
"@iarna/toml": ^2.2.5
"@jest/globals": ^29.7.0
"@mdi/js": 7.4.47
"@microsoft/api-extractor": ^7.57.6
"@msgpack/msgpack": ^2.7.0
Expand Down
1 change: 1 addition & 0 deletions src/renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@babel/register": "catalog:",
"@electron/remote": "catalog:",
"@headlessui/react": "catalog:",
"@jest/globals": "catalog:",
"@mdi/js": "catalog:",
"@microsoft/api-extractor": "catalog:",
"@msgpack/msgpack": "catalog:",
Expand Down
148 changes: 105 additions & 43 deletions src/renderer/src/__tests__/NotificationAggregator.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { NotificationAggregator } from '../extensions/mod_management/NotificationAggregator';
import {
afterEach,
beforeEach,
describe,
expect,
jest,
test,
} from "@jest/globals";
import { NotificationAggregator } from "../extensions/mod_management/NotificationAggregator";

// Mock API for testing
const mockApi = {
showErrorNotification: jest.fn(),
sendNotification: jest.fn(),
};

describe('NotificationAggregator', () => {
describe("NotificationAggregator", () => {
let aggregator: NotificationAggregator;

beforeEach(() => {
Expand All @@ -19,101 +27,155 @@ describe('NotificationAggregator', () => {
jest.useRealTimers();
});

test('should show notifications immediately when aggregation is not active', async () => {
test("should show notifications immediately when aggregation is not active", async () => {
aggregator.addNotification(
'test-session',
'error',
'Test Error',
'Test message',
'TestMod',
{ allowReport: false }
"test-session",
"error",
"Test Error",
"Test message",
"TestMod",
{ allowReport: false },
);

// Run any pending timers/setImmediate
jest.runAllTimers();

expect(mockApi.showErrorNotification).toHaveBeenCalledWith('Test Error', 'Test message', {
message: 'TestMod',
allowReport: false,
actions: undefined,
});
expect(mockApi.showErrorNotification).toHaveBeenCalledWith(
"Test Error",
"Test message",
{
message: "TestMod",
allowReport: false,
actions: undefined,
},
);
});

test('should aggregate similar notifications', async () => {
aggregator.startAggregation('test-session', 0);
test("should aggregate similar notifications", async () => {
aggregator.startAggregation("test-session", 0);

// Add multiple similar notifications
aggregator.addNotification('test-session', 'error', 'Failed to install dependency', 'Download failed', 'Mod1');
aggregator.addNotification('test-session', 'error', 'Failed to install dependency', 'Download failed', 'Mod2');
aggregator.addNotification('test-session', 'error', 'Failed to install dependency', 'Download failed', 'Mod3');
aggregator.addNotification(
"test-session",
"error",
"Failed to install dependency",
"Download failed",
"Mod1",
);
aggregator.addNotification(
"test-session",
"error",
"Failed to install dependency",
"Download failed",
"Mod2",
);
aggregator.addNotification(
"test-session",
"error",
"Failed to install dependency",
"Download failed",
"Mod3",
);

// Flush aggregation and wait for completion
await aggregator.flushAggregation('test-session');
await aggregator.flushAggregation("test-session");

expect(mockApi.showErrorNotification).toHaveBeenCalledTimes(1);
expect(mockApi.showErrorNotification).toHaveBeenCalledWith(
"Failed to install dependency (3 dependencies)",
expect.stringContaining('Affected dependencies: Mod1, Mod2, Mod3'),
expect.stringContaining("Affected dependencies: Mod1, Mod2, Mod3"),
expect.objectContaining({
allowReport: undefined,
id: expect.stringContaining("aggregated-")
})
id: expect.stringContaining("aggregated-"),
}),
);
});

test('should handle different error types separately', async () => {
aggregator.startAggregation('test-session', 0);
test("should handle different error types separately", async () => {
aggregator.startAggregation("test-session", 0);

// Use different titles to ensure they are grouped separately
aggregator.addNotification('test-session', 'error', 'Download failed', 'Connection error', 'Mod1');
aggregator.addNotification('test-session', 'error', 'Invalid URL', 'Malformed URL', 'Mod2');
aggregator.addNotification(
"test-session",
"error",
"Download failed",
"Connection error",
"Mod1",
);
aggregator.addNotification(
"test-session",
"error",
"Invalid URL",
"Malformed URL",
"Mod2",
);

await aggregator.flushAggregation('test-session');
await aggregator.flushAggregation("test-session");

expect(mockApi.showErrorNotification).toHaveBeenCalledTimes(2);
});

test('should handle many dependencies by truncating the list', async () => {
aggregator.startAggregation('test-session', 0);
test("should handle many dependencies by truncating the list", async () => {
aggregator.startAggregation("test-session", 0);

// Add more than 5 dependencies
for (let i = 1; i <= 7; i++) {
aggregator.addNotification('test-session', 'error', 'Failed to install dependency', 'Download failed', `Mod${i}`);
aggregator.addNotification(
"test-session",
"error",
"Failed to install dependency",
"Download failed",
`Mod${i}`,
);
}

await aggregator.flushAggregation('test-session');
await aggregator.flushAggregation("test-session");

expect(mockApi.showErrorNotification).toHaveBeenCalledWith(
"Failed to install dependency (7 dependencies)",
expect.stringContaining("and 2 more"),
expect.objectContaining({
allowReport: undefined,
id: expect.stringContaining("aggregated-error-Failed to install dependency")
})
id: expect.stringContaining(
"aggregated-error-Failed to install dependency",
),
}),
);
});

test('should auto-flush on timeout', async () => {
aggregator.startAggregation('test-session', 100); // 100ms timeout
test("should auto-flush on timeout", async () => {
aggregator.startAggregation("test-session", 100); // 100ms timeout

aggregator.addNotification('test-session', 'error', 'Test Error', 'Test message', 'TestMod');
aggregator.addNotification(
"test-session",
"error",
"Test Error",
"Test message",
"TestMod",
);

// Advance timers past the timeout to trigger the auto-flush
jest.advanceTimersByTime(150);

// Stop the aggregation (which flushes remaining notifications)
await aggregator.stopAggregation('test-session');
await aggregator.stopAggregation("test-session");

expect(mockApi.showErrorNotification).toHaveBeenCalledTimes(1);
});

test('should stop aggregation and flush notifications', async () => {
aggregator.startAggregation('test-session', 0);
aggregator.addNotification('test-session', 'error', 'Test Error', 'Test message', 'TestMod');
test("should stop aggregation and flush notifications", async () => {
aggregator.startAggregation("test-session", 0);
aggregator.addNotification(
"test-session",
"error",
"Test Error",
"Test message",
"TestMod",
);

await aggregator.stopAggregation('test-session');
await aggregator.stopAggregation("test-session");

expect(mockApi.showErrorNotification).toHaveBeenCalledTimes(1);
expect(aggregator.isAggregating('test-session')).toBe(false);
expect(aggregator.isAggregating("test-session")).toBe(false);
});
});
1 change: 1 addition & 0 deletions src/renderer/src/__tests__/desktopFileEscaping.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { describe, expect, test } from "@jest/globals";
import {
escapeDesktopExecFilePath,
escapeDesktopFilePath,
Expand Down
Loading
Loading