#708 apply strictier linting rules (#717)

* #708 apply strictier linting rules and fix simple eslint bugs

* #708 fix eslint errors relate to promise

* #708 fix eslint import/no-extraneous-dependencies

* #708 fix eslint errors of react-hook

* #708 enable eslint check for typescript

---------

Co-authored-by: lethemanh <lethemanh@lethemanhs-MacBook-Pro.local>
This commit is contained in:
lethemanh
2026-04-01 22:15:10 +07:00
committed by GitHub
parent 2bff6aae78
commit cadfa70e60
321 changed files with 27452 additions and 27600 deletions
@@ -1,135 +1,135 @@
import type { AppDispatch, RootState } from "@/app/store";
import { store } from "@/app/store";
import { refreshCalendarWithSyncToken } from "@/features/Calendars/services";
import { getDisplayedCalendarRange } from "@/utils";
import { updateCalendars } from "@/websocket/messaging/updateCalendars";
import type { AppDispatch, RootState } from '@/app/store'
import { store } from '@/app/store'
import { refreshCalendarWithSyncToken } from '@/features/Calendars/services'
import { getDisplayedCalendarRange } from '@/utils'
import { updateCalendars } from '@/websocket/messaging/updateCalendars'
jest.mock("@/features/Calendars/services", () => ({
refreshCalendarWithSyncToken: jest.fn(),
}));
jest.mock('@/features/Calendars/services', () => ({
refreshCalendarWithSyncToken: jest.fn()
}))
jest.mock("@/utils", () => ({
jest.mock('@/utils', () => ({
getDisplayedCalendarRange: jest.fn(),
findCalendarById: jest.requireActual("@/utils").findCalendarById,
}));
findCalendarById: jest.requireActual('@/utils').findCalendarById
}))
jest.mock("@/app/store", () => ({
jest.mock('@/app/store', () => ({
store: {
getState: jest.fn(),
},
}));
getState: jest.fn()
}
}))
jest.useFakeTimers();
const mockDispatch = jest.fn();
jest.useFakeTimers()
const mockDispatch = jest.fn()
const mockRange = {
start: new Date("2025-01-15T10:00:00Z"),
end: new Date("2025-01-16T10:00:00Z"),
};
start: new Date('2025-01-15T10:00:00Z'),
end: new Date('2025-01-16T10:00:00Z')
}
const mockState = {
calendars: {
list: {
"cal1/entry1": { id: "cal1/entry1", name: "Calendar 1", syncToken: 1 },
"cal2/entry2": { id: "cal2/entry2", name: "Calendar 2", syncToken: 1 },
"cal/A": { id: "cal/A", name: "Cal A", syncToken: 1 },
"cal/B": { id: "cal/B", name: "Cal B", syncToken: 1 },
"cal/C": { id: "cal/C", name: "Cal C", syncToken: 1 },
'cal1/entry1': { id: 'cal1/entry1', name: 'Calendar 1', syncToken: 1 },
'cal2/entry2': { id: 'cal2/entry2', name: 'Calendar 2', syncToken: 1 },
'cal/A': { id: 'cal/A', name: 'Cal A', syncToken: 1 },
'cal/B': { id: 'cal/B', name: 'Cal B', syncToken: 1 },
'cal/C': { id: 'cal/C', name: 'Cal C', syncToken: 1 }
},
templist: {},
},
} as unknown as RootState;
templist: {}
}
} as unknown as RootState
const mockAccumulators: {
calendarsToRefresh: Map<string, any>;
calendarsToHide: Set<string>;
debouncedUpdateFn?: (dispatch: AppDispatch) => void;
shouldRefreshCalendarListRef: React.MutableRefObject<boolean>;
currentDebouncePeriod?: number;
calendarsToRefresh: Map<string, any>
calendarsToHide: Set<string>
debouncedUpdateFn?: (dispatch: AppDispatch) => void
shouldRefreshCalendarListRef: React.MutableRefObject<boolean>
currentDebouncePeriod?: number
} = {
calendarsToRefresh: new Map<string, any>(),
calendarsToHide: new Set(),
shouldRefreshCalendarListRef: { current: false },
currentDebouncePeriod: 0,
debouncedUpdateFn: undefined,
};
debouncedUpdateFn: undefined
}
describe("websocket messages storm", () => {
describe('websocket messages storm', () => {
beforeEach(() => {
(refreshCalendarWithSyncToken as unknown as jest.Mock).mockClear();
jest.clearAllMocks();
jest.clearAllTimers();
jest.resetModules();
(getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange);
(store.getState as jest.Mock).mockReturnValue(mockState);
window.WS_DEBOUNCE_PERIOD_MS = 500;
mockAccumulators.calendarsToRefresh = new Map<string, any>();
mockAccumulators.calendarsToHide = new Set();
mockAccumulators.shouldRefreshCalendarListRef.current = false;
mockAccumulators.currentDebouncePeriod = 0;
;(refreshCalendarWithSyncToken as unknown as jest.Mock).mockClear()
jest.clearAllMocks()
jest.clearAllTimers()
jest.resetModules()
;(getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange)
;(store.getState as jest.Mock).mockReturnValue(mockState)
window.WS_DEBOUNCE_PERIOD_MS = 500
mockAccumulators.calendarsToRefresh = new Map<string, any>()
mockAccumulators.calendarsToHide = new Set()
mockAccumulators.shouldRefreshCalendarListRef.current = false
mockAccumulators.currentDebouncePeriod = 0
mockAccumulators.debouncedUpdateFn = undefined;
});
it("debounces calendar updates during message storm", () => {
mockAccumulators.debouncedUpdateFn = undefined
})
it('debounces calendar updates during message storm', () => {
const mockMessage = {
"/calendars/cal1/entry1": {
syncToken: "ldsk",
},
};
'/calendars/cal1/entry1': {
syncToken: 'ldsk'
}
}
for (let i = 0; i < 50; i++) {
updateCalendars(mockMessage, mockDispatch, mockAccumulators);
updateCalendars(mockMessage, mockDispatch, mockAccumulators)
}
// Dispatch called once because of leading edge
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1);
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1)
// Trailing edge
jest.advanceTimersByTime(500);
jest.advanceTimersByTime(500)
// only one call for the last message + leading edge message
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(2);
});
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(2)
})
it("debounces calendar updates during message storm with multiple updates", () => {
it('debounces calendar updates during message storm with multiple updates', () => {
// Send a storm with mixed messages
for (let i = 0; i < 50; i++) {
if (i % 3 === 0)
updateCalendars(
{ "/calendars/cal/A": { syncToken: "ldskfjsld" + i } },
{ '/calendars/cal/A': { syncToken: 'ldskfjsld' + i } },
mockDispatch,
mockAccumulators
);
)
else if (i % 3 === 1)
updateCalendars(
{ "/calendars/cal/B": { syncToken: "ldskfjsld" + i } },
{ '/calendars/cal/B': { syncToken: 'ldskfjsld' + i } },
mockDispatch,
mockAccumulators
);
)
else
updateCalendars(
{ "/calendars/cal/C": { syncToken: "ldskfjsld" + i } },
{ '/calendars/cal/C': { syncToken: 'ldskfjsld' + i } },
mockDispatch,
mockAccumulators
);
)
}
// Dispatch called once because of leading edge
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1);
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1)
// Trailing edge
jest.advanceTimersByTime(500);
jest.advanceTimersByTime(500)
// Trailing edge updates once per calendar + the original leading edge
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(4);
});
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(4)
})
it("executes immediately when debounce is disabled", () => {
window.WS_DEBOUNCE_PERIOD_MS = 0;
it('executes immediately when debounce is disabled', () => {
window.WS_DEBOUNCE_PERIOD_MS = 0
updateCalendars(
{ "/calendars/cal1/entry1": { syncToken: "abc" } },
{ '/calendars/cal1/entry1': { syncToken: 'abc' } },
mockDispatch,
mockAccumulators
);
)
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1);
});
});
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1)
})
})
@@ -1,6 +1,6 @@
export function setupWebsocket() {
const originalWebSocket = global.WebSocket;
const webSocketInstances: any[] = [];
const originalWebSocket = global.WebSocket
const webSocketInstances: any[] = []
const mockWebSocket = jest.fn().mockImplementation((url: string) => {
const ws = {
url,
@@ -9,27 +9,27 @@ export function setupWebsocket() {
removeEventListener: jest.fn(),
send: jest.fn(),
close: jest.fn(),
_listeners: {} as Record<string, Function[]>,
};
_listeners: {} as Record<string, Function[]>
}
ws.addEventListener.mockImplementation((event, handler) => {
ws._listeners[event] ??= [];
ws._listeners[event].push(handler);
});
ws._listeners[event] ??= []
ws._listeners[event].push(handler)
})
ws.removeEventListener.mockImplementation((event, handler) => {
ws._listeners[event] =
ws._listeners[event]?.filter((h) => h !== handler) ?? [];
});
ws._listeners[event]?.filter(h => h !== handler) ?? []
})
webSocketInstances.push(ws);
return ws;
});
webSocketInstances.push(ws)
return ws
})
global.WebSocket = mockWebSocket as any;
global.WebSocket = mockWebSocket as any
const cleanup = () => {
global.WebSocket = originalWebSocket;
webSocketInstances.length = 0;
};
return { webSocketInstances, mockWebSocket, cleanup };
global.WebSocket = originalWebSocket
webSocketInstances.length = 0
}
return { webSocketInstances, mockWebSocket, cleanup }
}
@@ -1,147 +1,145 @@
import { AppDispatch, RootState, store } from "@/app/store";
import { refreshCalendarWithSyncToken } from "@/features/Calendars/services/refreshCalendar";
import { getDisplayedCalendarRange } from "@/utils/CalendarRangeManager";
import { updateCalendars } from "@/websocket/messaging/updateCalendars";
import { WS_INBOUND_EVENTS } from "@/websocket/protocols";
import { waitFor } from "@testing-library/dom";
import { AppDispatch, RootState, store } from '@/app/store'
import { refreshCalendarWithSyncToken } from '@/features/Calendars/services/refreshCalendar'
import { getDisplayedCalendarRange } from '@/utils/CalendarRangeManager'
import { updateCalendars } from '@/websocket/messaging/updateCalendars'
import { WS_INBOUND_EVENTS } from '@/websocket/protocols'
import { waitFor } from '@testing-library/dom'
jest.mock("@/features/Calendars/services/refreshCalendar");
jest.mock("@/utils/CalendarRangeManager");
jest.mock("@/app/store", () => ({
jest.mock('@/features/Calendars/services/refreshCalendar')
jest.mock('@/utils/CalendarRangeManager')
jest.mock('@/app/store', () => ({
store: {
getState: jest.fn(),
},
}));
getState: jest.fn()
}
}))
describe("updateCalendars", () => {
let mockDispatch: jest.Mock;
describe('updateCalendars', () => {
let mockDispatch: jest.Mock
const mockRange = {
start: new Date("2025-01-15T10:00:00Z"),
end: new Date("2025-01-16T10:00:00Z"),
};
start: new Date('2025-01-15T10:00:00Z'),
end: new Date('2025-01-16T10:00:00Z')
}
const mockState = {
calendars: {
list: {
"cal1/entry1": { id: "cal1/entry1", name: "Calendar 1", syncToken: 1 },
"cal2/entry2": { id: "cal2/entry2", name: "Calendar 2", syncToken: 1 },
'cal1/entry1': { id: 'cal1/entry1', name: 'Calendar 1', syncToken: 1 },
'cal2/entry2': { id: 'cal2/entry2', name: 'Calendar 2', syncToken: 1 }
},
templist: {},
},
} as unknown as RootState;
templist: {}
}
} as unknown as RootState
const mockAccumulators: {
calendarsToRefresh: Map<string, any>;
calendarsToHide: Set<string>;
debouncedUpdateFn?: (dispatch: AppDispatch) => void;
shouldRefreshCalendarListRef: React.MutableRefObject<boolean>;
currentDebouncePeriod?: number;
calendarsToRefresh: Map<string, any>
calendarsToHide: Set<string>
debouncedUpdateFn?: (dispatch: AppDispatch) => void
shouldRefreshCalendarListRef: React.MutableRefObject<boolean>
currentDebouncePeriod?: number
} = {
calendarsToRefresh: new Map<string, any>(),
calendarsToHide: new Set(),
shouldRefreshCalendarListRef: { current: false },
currentDebouncePeriod: 0,
debouncedUpdateFn: jest.fn(),
};
debouncedUpdateFn: jest.fn()
}
beforeEach(() => {
jest.clearAllMocks();
mockDispatch = jest.fn();
(getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange);
(store.getState as jest.Mock).mockReturnValue(mockState);
mockAccumulators.calendarsToRefresh = new Map<string, any>();
mockAccumulators.calendarsToHide = new Set();
mockAccumulators.currentDebouncePeriod = 0;
mockAccumulators.shouldRefreshCalendarListRef.current = false;
mockAccumulators.debouncedUpdateFn = jest.fn();
});
jest.clearAllMocks()
mockDispatch = jest.fn()
;(getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange)
;(store.getState as jest.Mock).mockReturnValue(mockState)
mockAccumulators.calendarsToRefresh = new Map<string, any>()
mockAccumulators.calendarsToHide = new Set()
mockAccumulators.currentDebouncePeriod = 0
mockAccumulators.shouldRefreshCalendarListRef.current = false
mockAccumulators.debouncedUpdateFn = jest.fn()
})
it("should not dispatch for non-object messages", () => {
updateCalendars(null, mockDispatch, mockAccumulators);
updateCalendars("string", mockDispatch, mockAccumulators);
updateCalendars(123, mockDispatch, mockAccumulators);
it('should not dispatch for non-object messages', () => {
updateCalendars(null, mockDispatch, mockAccumulators)
updateCalendars('string', mockDispatch, mockAccumulators)
updateCalendars(123, mockDispatch, mockAccumulators)
expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled();
});
expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled()
})
it("should dispatch for registered calendars", async () => {
it('should dispatch for registered calendars', async () => {
const message = {
[WS_INBOUND_EVENTS.CLIENT_REGISTERED]: [
"/calendars/cal1/entry1",
"/calendars/cal2/entry2",
],
};
'/calendars/cal1/entry1',
'/calendars/cal2/entry2'
]
}
updateCalendars(message, mockDispatch, mockAccumulators);
updateCalendars(message, mockDispatch, mockAccumulators)
await waitFor(() =>
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(2)
);
});
)
})
it("should dispatch for calendar path updates", async () => {
it('should dispatch for calendar path updates', async () => {
const message = {
"/calendars/cal1/entry1": { updated: true },
};
'/calendars/cal1/entry1': { updated: true }
}
updateCalendars(message, mockDispatch, mockAccumulators);
await waitFor(() =>
expect(refreshCalendarWithSyncToken).toHaveBeenCalled()
);
updateCalendars(message, mockDispatch, mockAccumulators)
await waitFor(() => expect(refreshCalendarWithSyncToken).toHaveBeenCalled())
expect(refreshCalendarWithSyncToken).toHaveBeenCalledWith({
calendar: mockState.calendars.list["cal1/entry1"],
calendar: mockState.calendars.list['cal1/entry1'],
calType: undefined,
calendarRange: mockRange,
});
});
calendarRange: mockRange
})
})
it("should use displayed calendar range", async () => {
it('should use displayed calendar range', async () => {
const message = {
"/calendars/cal1/entry1": {},
};
'/calendars/cal1/entry1': {}
}
updateCalendars(message, mockDispatch, mockAccumulators);
await waitFor(() => expect(getDisplayedCalendarRange).toHaveBeenCalled());
});
updateCalendars(message, mockDispatch, mockAccumulators)
await waitFor(() => expect(getDisplayedCalendarRange).toHaveBeenCalled())
})
it("should handle temp calendars", async () => {
it('should handle temp calendars', async () => {
const stateWithTemp = {
calendars: {
list: {},
templist: {
"temp1/entry1": {
id: "temp1/entry1",
name: "Temp Calendar",
syncToken: 1,
},
},
},
};
'temp1/entry1': {
id: 'temp1/entry1',
name: 'Temp Calendar',
syncToken: 1
}
}
}
}
(store.getState as jest.Mock).mockReturnValue(stateWithTemp);
;(store.getState as jest.Mock).mockReturnValue(stateWithTemp)
const message = {
"/calendars/temp1/entry1": {},
};
'/calendars/temp1/entry1': {}
}
updateCalendars(message, mockDispatch, mockAccumulators);
updateCalendars(message, mockDispatch, mockAccumulators)
await waitFor(() =>
expect(refreshCalendarWithSyncToken).toHaveBeenCalledWith({
calendar: stateWithTemp.calendars.templist["temp1/entry1"],
calType: "temp",
calendarRange: mockRange,
calendar: stateWithTemp.calendars.templist['temp1/entry1'],
calType: 'temp',
calendarRange: mockRange
})
);
});
)
})
it("should handle invalid calendar paths gracefully", () => {
it('should handle invalid calendar paths gracefully', () => {
const message = {
"/invalid/path": {},
"not-a-path": {},
};
'/invalid/path': {},
'not-a-path': {}
}
updateCalendars(message, mockDispatch, mockAccumulators);
updateCalendars(message, mockDispatch, mockAccumulators)
expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled();
});
});
expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled()
})
})