Skip to content

Commit

Permalink
test: add missing abnormal condition tests (#64)
Browse files Browse the repository at this point in the history
* test: add missing tests

* ci: add configuration for code climate
  • Loading branch information
mato533 authored Nov 1, 2024
1 parent 74b3319 commit 27fa6a1
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 27 deletions.
19 changes: 18 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [18.x, 20.x]
node-version: [20.x]
steps:
- uses: mato533/cicd-actions/setup-pnpm@main
with:
Expand All @@ -37,3 +37,20 @@ jobs:

- name: Build
run: pnpm run build

coverage:
name: Upload coverage to code climate
runs-on: ubuntu-latest
needs:
- test
if: github.event_name == 'push'
steps:
- uses: mato533/cicd-actions/setup-pnpm@main
with:
cache: 'pnpm'

- uses: paambaati/codeclimate-action@v9
env:
CC_TEST_REPORTER_ID: ${{ secrets.CODE_CLIMATE }}
with:
coverageCommand: pnpm run coverage
24 changes: 24 additions & 0 deletions src/__tests__/logger.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {
preloadLogger,
initializeMain,
initializePreload,
// todo: remove this method on next major version
oldInitializeMain,
oldInitializePreload,
} from '../utils/logger'

import type { LogLevel } from '../utils/logger'
Expand Down Expand Up @@ -73,3 +76,24 @@ describe.each([
})
})
})

// todo: remove this method on next major version
describe.each([
['main', oldInitializeMain],
['preload', oldInitializePreload],
])('Logger (%s)', (procName: string, initialize) => {
const logSpy = vi.spyOn(console, 'log')

beforeEach(() => {
logSpy.mockReset()
})

it('log test', () => {
initialize({ logger: { [procName]: new DefaultLogger() } })

expect(logSpy).toHaveBeenCalledOnce()

const message = logSpy.mock.calls[0][0]
expect(message.includes('[DEPRECATION NOTICE]')).toBeTruthy()
})
})
11 changes: 11 additions & 0 deletions src/__tests__/main.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ describe('main', () => {
mocks.ipcMain.handle.mockClear()
})

it('invalid api structure', () => {
const apiHandlers = {
dummy: {
fn1: (_e: IpcMainInvokeEvent, arg1: string) => arg1,
},
}

// @ts-expect-error
expect(() => registerIpcHandler(apiHandlers)).toThrowError('Implementation error')
})

it('sender test', () => {
const _apiHandlers = {
invoke: {
Expand Down
76 changes: 60 additions & 16 deletions src/__tests__/preload.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ import { registerIpcHandler } from '../main'
import type { IpcBridgeApiGenerator } from '../preload'
import type { IpcMainInvokeEvent, IpcRendererEvent } from 'electron'

const _apiHandlers = {
invoke: {
fn1: (e: IpcMainInvokeEvent) => console.log(e),
fn2: (_e: IpcMainInvokeEvent, arg0: string) => `hello ${arg0}`,
},
on: {
fn1: (arg1: string) => arg1,
fn2: (arg1: number, arg2: number) => arg1 + arg2,
name1: {
fn2: (arg1: string) => arg1,
fn1: (arg1: number, arg2: number) => arg1 + arg2,
},
},
}
describe('preload', () => {
const mocks = vi.hoisted(() => {
return {
Expand All @@ -28,23 +42,13 @@ describe('preload', () => {

// disable console outputs
vi.spyOn(console, 'debug').mockImplementation(() => {})

beforeEach(() => {
mocks.ipcMain.handle.mockReset()
mocks.ipcRenderer.invoke.mockReset()
mocks.ipcRenderer.on.mockReset()
mocks.randomUUID.mockReset()
})
it('sender test', async () => {
const _apiHandlers = {
invoke: {
fn1: (e: IpcMainInvokeEvent) => console.log(e),
fn2: (_e: IpcMainInvokeEvent, arg0: string) => `hello ${arg0}`,
},
on: {
fn1: (arg1: string) => arg1,
fn2: (arg1: number, arg2: number) => arg1 + arg2,
name1: {
fn2: (arg1: string) => arg1,
fn1: (arg1: number, arg2: number) => arg1 + arg2,
},
},
}

registerIpcHandler(_apiHandlers)

const lastArgs = mocks.ipcMain.handle.mock.calls[0]
Expand Down Expand Up @@ -78,4 +82,44 @@ describe('preload', () => {
expect(callback).toHaveBeenCalledOnce()
expect(result).toBe(expectedValue)
})

it('error test', async () => {
mocks.ipcRenderer.invoke.mockImplementation((key: string) => {
if (key === API_CHANNEL_MAP) {
return undefined
}
})

expect(() =>
generateIpcBridgeApi<IpcBridgeApiGenerator<typeof _apiHandlers>>()
).rejects.toThrowError('electron-typed-ipc-bridge')
})
it('error test', async () => {
mocks.ipcRenderer.invoke.mockImplementation((key: string) => {
if (key === API_CHANNEL_MAP) {
return {
invalid: { api: 'uuid' },
}
}
})

expect(() =>
generateIpcBridgeApi<IpcBridgeApiGenerator<typeof _apiHandlers>>()
).rejects.toThrowError('Implementation error')
})

it('error test', async () => {
mocks.ipcRenderer.invoke.mockImplementation((key: string) => {
if (key === API_CHANNEL_MAP) {
return {
on: { api: 'uuid1a' },
invalid: { api: 'uuid2' },
}
}
})

expect(() =>
generateIpcBridgeApi<IpcBridgeApiGenerator<typeof _apiHandlers>>()
).rejects.toThrowError('Implementation error')
})
})
15 changes: 10 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,14 @@ const getCallback = (mode: string) => {
return createEmitter
case MODE.invoke:
return addHandler
default: {
const massage = `Implementation error: Top level is must be 'on' or 'invoke'. (${mode})`
log.error(massage)
throw new Error(massage)
}
}
}

const checkTruthy = (ipcBridgeApi: IpcBridgeApiHandler, channelMap: ChannelMap) => {
if (!ipcBridgeApi || !channelMap) {
const massage = `Implementation error: Top level must be 'on' or 'invoke'. Both properties are not set.`
log.error(massage)
throw new Error(massage)
}
}

Expand All @@ -108,6 +111,8 @@ const serializeApi = (
channelMap: ChannelMap,
path: string[]
) => {
checkTruthy(ipcBridgeApi, channelMap)

const callback = getCallback(path[0])

return Object.keys(channelMap).reduce((api, key) => {
Expand Down
38 changes: 33 additions & 5 deletions src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,45 @@ type IpcBridgeApiTypeGenerator<T extends IpcBridgeApiImplementation> =

type IpcBridgeApi = IpcBridgeApiTypeGenerator<IpcBridgeApiImplementation>

const requiredKeys = ['on', 'invoke'] as const

const hasRequiredKeys = (keys: string[]) => {
if (!requiredKeys.some((key) => keys.includes(key))) {
throw new Error()
}
}

const checkInvalidKeys = (keys: string[]) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
if (!keys.every((key) => requiredKeys.includes(key))) {
throw new Error()
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const checkTopLevelKeys = (channelMap: any) => {
try {
const keys = Object.keys(channelMap)

hasRequiredKeys(keys)

checkInvalidKeys(keys)
} catch (_) {
const message = `Implementation error: Top level must be 'on' or 'invoke'. Both properties are not set.`
log.error(message)
throw new Error(message)
}
}

const generateIpcBridgeApi = async <T extends IpcBridgeApi>(): Promise<T> => {
const result = await ipcRenderer.invoke(API_CHANNEL_MAP)
if (!result) {
log.error(` --> Failed to get mapping for api and channel `)
throw new Error(`'electron-typed-ipc-bridge' is not working correctly`)
}
checkTopLevelKeys(result)

log.info('Generation IpcBridgeAPI is stated.')

const endpoint = getEndpointApi(result) as T
Expand Down Expand Up @@ -96,11 +129,6 @@ const createIpcBridgeApi = (channel: string, path: string[]) => {
return createInvoker(channel, path)
case MODE.on:
return createReceiver(channel, path)
default: {
const message = `implementation error: ${path.join('.')} (channel: ${channel}`
log.error(message)
throw new Error(message)
}
}
}

Expand Down

0 comments on commit 27fa6a1

Please sign in to comment.