Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: import ipc objects from main & preload #33

Merged
merged 4 commits into from
Nov 6, 2024
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
refactor(test): mock electron ipc objects
  • Loading branch information
goosewobbler committed Nov 6, 2024
commit 737ea2822be36cd9a96024d5353163633f9b9e20
89 changes: 42 additions & 47 deletions packages/zutron/test/main.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
import { mainZustandBridge, createDispatch } from '../src/main';
import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
import type { StoreApi } from 'zustand';
import type { AnyState } from '../src/index.js';

const mockIpcMain = {
emit: vi.fn(),
handle: vi.fn(),
on: vi.fn(),
};

vi.mock('electron', () => ({
ipcMain: mockIpcMain,
default: {
ipcMain: mockIpcMain,
},
}));

let { mainZustandBridge, createDispatch } = await import('../src/main.js');

describe('createDispatch', () => {
let store: Record<string, Mock>;
let mockStore: Record<string, Mock>;

beforeEach(() => {
store = {
mockStore = {
getState: vi.fn(),
setState: vi.fn(),
subscribe: vi.fn(),
@@ -20,19 +34,19 @@ describe('createDispatch', () => {

beforeEach(() => {
testState.testAction = vi.fn();
store.getState.mockReturnValue(testState);
mockStore.getState.mockReturnValue(testState);
});

it('should call a handler with the expected payload - string action', () => {
const dispatch = createDispatch(store as unknown as StoreApi<AnyState>);
const dispatch = createDispatch(mockStore as unknown as StoreApi<AnyState>);

dispatch('testAction', { test: 'payload' });

expect(testState.testAction).toHaveBeenCalledWith({ test: 'payload' });
});

it('should call a handler with the expected payload - object action', () => {
const dispatch = createDispatch(store as unknown as StoreApi<AnyState>);
const dispatch = createDispatch(mockStore as unknown as StoreApi<AnyState>);

dispatch({ type: 'testAction', payload: { test: 'payload' } });

@@ -48,15 +62,15 @@ describe('createDispatch', () => {
});

it('should call a handler with the expected payload - string action', () => {
const dispatch = createDispatch(store as unknown as StoreApi<AnyState>, { handlers: testHandlers });
const dispatch = createDispatch(mockStore as unknown as StoreApi<AnyState>, { handlers: testHandlers });

dispatch('testAction', { test: 'payload' });

expect(testHandlers.testAction).toHaveBeenCalledWith({ test: 'payload' });
});

it('should call a handler with the expected payload - object action', () => {
const dispatch = createDispatch(store as unknown as StoreApi<AnyState>, { handlers: testHandlers });
const dispatch = createDispatch(mockStore as unknown as StoreApi<AnyState>, { handlers: testHandlers });

dispatch({ type: 'testAction', payload: { test: 'payload' } });

@@ -77,44 +91,46 @@ describe('createDispatch', () => {
return existingState;
});

const dispatch = createDispatch(store as unknown as StoreApi<AnyState>, { reducer: testReducer });
const dispatch = createDispatch(mockStore as unknown as StoreApi<AnyState>, { reducer: testReducer });

dispatch({ type: 'testAction', payload: 'testPayload' });

expect(store.setState).toHaveBeenCalledWith(expect.any(Function));
expect(mockStore.setState).toHaveBeenCalledWith(expect.any(Function));

const newTestState = store.setState.mock.calls[0][0](testState);
const newTestState = mockStore.setState.mock.calls[0][0](testState);

expect(newTestState).toStrictEqual({ test: 'state', updated: 'state' });
});
});
});

describe('mainZustandBridge', () => {
const options: { handlers?: Record<string, Mock> } = {};
let mockIpcMain: Record<string, Mock>;
let options: { handlers?: Record<string, Mock> };
let mockStore: Record<string, Mock>;
let mockWindows: Record<string, Mock | { send: Mock }>[];

beforeEach(() => {
mockIpcMain = {
on: vi.fn(),
handle: vi.fn(),
emit: vi.fn(),
};
mockStore = {
getState: vi.fn(),
setState: vi.fn(),
subscribe: vi.fn(),
};
mockWindows = [{ isDestroyed: vi.fn().mockReturnValue(false), webContents: { send: vi.fn() } }];
options = {};
});

afterEach(() => {
mockIpcMain.on.mockClear();
mockIpcMain.handle.mockClear();
mockIpcMain.emit.mockClear();
mockStore.getState.mockClear();
mockStore.subscribe.mockClear();
});

it('should pass dispatch messages through to the store', () => {
options.handlers = { test: vi.fn() };

mainZustandBridge(
mockIpcMain as unknown as Electron.CrossProcessExports.IpcMain,
mockStore as unknown as StoreApi<AnyState>,
mockWindows as unknown as Electron.BrowserWindow[],
options,
@@ -128,16 +144,15 @@ describe('mainZustandBridge', () => {
});

it('should handle getState calls and return the sanitized state', async () => {
mockStore.getState.mockReturnValue({ test: 'state', testHandler: vi.fn() });
mockStore.getState.mockImplementation(() => ({ test: 'state', testHandler: vi.fn() }));

mainZustandBridge(
mockIpcMain as unknown as Electron.CrossProcessExports.IpcMain,
mockStore as unknown as StoreApi<AnyState>,
mockWindows as unknown as Electron.BrowserWindow[],
options,
);
expect(mockIpcMain.handle).toHaveBeenCalledWith('getState', expect.any(Function));
const getStateHandler = mockIpcMain.handle.mock.calls[0][1];
const getStateHandler = mockIpcMain.handle.mock.calls[0][1] as () => AnyState;

const state = getStateHandler();

@@ -148,12 +163,7 @@ describe('mainZustandBridge', () => {
it('should handle subscribe calls and send sanitized state to the window', async () => {
const browserWindows = mockWindows as unknown as Electron.BrowserWindow[];

mainZustandBridge(
mockIpcMain as unknown as Electron.CrossProcessExports.IpcMain,
mockStore as unknown as StoreApi<AnyState>,
browserWindows,
options,
);
mainZustandBridge(mockStore as unknown as StoreApi<AnyState>, browserWindows, options);
expect(mockIpcMain.on).toHaveBeenCalledWith('subscribe', expect.any(Function));
const subscribeHandler = mockIpcMain.on.mock.calls[0][1];

@@ -169,12 +179,7 @@ describe('mainZustandBridge', () => {
];
const browserWindows = mockWindows as unknown as Electron.BrowserWindow[];

mainZustandBridge(
mockIpcMain as unknown as Electron.CrossProcessExports.IpcMain,
mockStore as unknown as StoreApi<AnyState>,
browserWindows,
options,
);
mainZustandBridge(mockStore as unknown as StoreApi<AnyState>, browserWindows, options);
expect(mockIpcMain.on).toHaveBeenCalledWith('subscribe', expect.any(Function));
const subscribeHandler = mockIpcMain.on.mock.calls[0][1];

@@ -191,12 +196,7 @@ describe('mainZustandBridge', () => {
];
const browserWindows = mockWindows as unknown as Electron.BrowserWindow[];

mainZustandBridge(
mockIpcMain as unknown as Electron.CrossProcessExports.IpcMain,
mockStore as unknown as StoreApi<AnyState>,
browserWindows,
options,
);
mainZustandBridge(mockStore as unknown as StoreApi<AnyState>, browserWindows, options);
expect(mockIpcMain.on).toHaveBeenCalledWith('subscribe', expect.any(Function));
const subscribeHandler = mockIpcMain.on.mock.calls[0][1];

@@ -210,12 +210,7 @@ describe('mainZustandBridge', () => {
const browserWindows = mockWindows as unknown as Electron.BrowserWindow[];
mockStore.subscribe.mockImplementation(() => vi.fn());

const bridge = mainZustandBridge(
mockIpcMain as unknown as Electron.CrossProcessExports.IpcMain,
mockStore as unknown as StoreApi<AnyState>,
browserWindows,
options,
);
const bridge = mainZustandBridge(mockStore as unknown as StoreApi<AnyState>, browserWindows, options);

expect(bridge.unsubscribe).toStrictEqual(expect.any(Function));
expect(mockStore.subscribe).toHaveBeenCalledWith(expect.any(Function));
31 changes: 19 additions & 12 deletions packages/zutron/test/preload.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { vi, expect, describe, it, type Mock, beforeEach } from 'vitest';
import { preloadZustandBridge } from '../src/preload.js';
import { createIpcRendererMock } from './helpers.js';

const mockIpcRenderer = createIpcRendererMock();

vi.mock('electron', () => ({
ipcRenderer: mockIpcRenderer,
default: {
ipcRenderer: mockIpcRenderer,
},
}));

let { preloadZustandBridge } = await import('../src/preload.js');

describe('preloadZustandBridge', () => {
it('should return the expected handlers', () => {
const ipcRenderer = createIpcRendererMock();
const bridge = preloadZustandBridge(ipcRenderer);
const bridge = preloadZustandBridge();

expect(bridge.handlers).toBeDefined();
expect(bridge.handlers.dispatch).toStrictEqual(expect.any(Function));
@@ -14,27 +23,25 @@ describe('preloadZustandBridge', () => {
});

describe('handlers', () => {
let ipcRenderer: Record<string, Mock>;
let bridge: ReturnType<typeof preloadZustandBridge>;

beforeEach(() => {
ipcRenderer = createIpcRendererMock();
bridge = preloadZustandBridge(ipcRenderer as unknown as Electron.IpcRenderer);
bridge = preloadZustandBridge();
});

describe('dispatch', () => {
it('should call ipcRenderer.send', () => {
bridge.handlers.dispatch('action', { payload: 'data' });

expect(ipcRenderer.send).toHaveBeenCalledWith('dispatch', 'action', { payload: 'data' });
expect(mockIpcRenderer.send).toHaveBeenCalledWith('dispatch', 'action', { payload: 'data' });
});
});

describe('getState', () => {
it('should call ipcRenderer.invoke', () => {
bridge.handlers.getState();

expect(ipcRenderer.invoke).toHaveBeenCalledWith('getState');
expect(mockIpcRenderer.invoke).toHaveBeenCalledWith('getState');
});
});

@@ -44,8 +51,8 @@ describe('preloadZustandBridge', () => {

bridge.handlers.subscribe(callback);

expect(ipcRenderer.on).toHaveBeenCalledWith('subscribe', expect.any(Function));
ipcRenderer.on.mock.calls[0][1]('state', 'testState');
expect(mockIpcRenderer.on).toHaveBeenCalledWith('subscribe', expect.any(Function));
mockIpcRenderer.on.mock.calls[0][1]('state', 'testState');
expect(callback).toHaveBeenCalledWith('testState');
});

@@ -55,8 +62,8 @@ describe('preloadZustandBridge', () => {
const unsubscribe = bridge.handlers.subscribe(callback);
unsubscribe();

expect(ipcRenderer.off).toHaveBeenCalledWith('subscribe', expect.any(Function));
ipcRenderer.off.mock.calls[0][1]('state', 'testState');
expect(mockIpcRenderer.off).toHaveBeenCalledWith('subscribe', expect.any(Function));
mockIpcRenderer.off.mock.calls[0][1]('state', 'testState');
expect(callback).toHaveBeenCalledWith('testState');
});
});