Skip to content

Commit 1b5e51c

Browse files
committed
Write testcode
1 parent e66db9c commit 1b5e51c

File tree

3 files changed

+273
-1
lines changed

3 files changed

+273
-1
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { expect, test } from 'bun:test'
2+
import * as indexModule from '../index'
3+
4+
test('index.ts exports', () => {
5+
expect({ ...indexModule }).toEqual({
6+
devupApiWebpackPlugin: expect.any(Function),
7+
default: expect.any(Function),
8+
})
9+
})
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import { beforeEach, expect, mock, spyOn, test } from 'bun:test'
2+
import { join } from 'node:path'
3+
import type { DevupApiOptions } from '@devup-api/core'
4+
import * as generator from '@devup-api/generator'
5+
import * as utils from '@devup-api/utils'
6+
import type { Compiler } from 'webpack'
7+
import { DefinePlugin } from 'webpack'
8+
import { devupApiWebpackPlugin } from '../plugin'
9+
10+
let mockCreateTmpDirAsync: ReturnType<typeof spyOn>
11+
let mockReadOpenapiAsync: ReturnType<typeof spyOn>
12+
let mockWriteInterfaceAsync: ReturnType<typeof spyOn>
13+
let mockCreateUrlMap: ReturnType<typeof spyOn>
14+
let mockGenerateInterface: ReturnType<typeof spyOn>
15+
16+
const mockSchema = {
17+
openapi: '3.1.0',
18+
paths: {
19+
'/users': {
20+
get: {
21+
operationId: 'getUsers',
22+
responses: {
23+
'200': {
24+
content: {
25+
'application/json': {
26+
schema: {
27+
type: 'array',
28+
items: { type: 'string' },
29+
},
30+
},
31+
},
32+
},
33+
},
34+
},
35+
},
36+
},
37+
} as const
38+
39+
const mockUrlMap = {
40+
getUsers: {
41+
method: 'GET' as const,
42+
url: '/users',
43+
},
44+
'/users': {
45+
method: 'GET' as const,
46+
url: '/users',
47+
},
48+
}
49+
50+
const mockInterfaceContent = 'export interface Test {}'
51+
52+
const createMockCompiler = (): Compiler & {
53+
_storedCallback?: (params: unknown, cb: (error?: Error) => void) => void
54+
} => {
55+
const storedCallback: {
56+
callback?: (params: unknown, cb: (error?: Error) => void) => void
57+
} = {}
58+
const tapAsyncMock = mock(
59+
(
60+
_name: string,
61+
callback: (params: unknown, cb: (error?: Error) => void) => void,
62+
) => {
63+
storedCallback.callback = callback
64+
},
65+
)
66+
const hooks = {
67+
beforeCompile: {
68+
tapAsync: tapAsyncMock,
69+
},
70+
}
71+
const compiler = {
72+
hooks,
73+
} as unknown as Compiler & {
74+
_storedCallback?: (params: unknown, cb: (error?: Error) => void) => void
75+
}
76+
Object.defineProperty(compiler, '_storedCallback', {
77+
get() {
78+
return storedCallback.callback
79+
},
80+
enumerable: true,
81+
configurable: true,
82+
})
83+
return compiler
84+
}
85+
86+
beforeEach(() => {
87+
mockCreateTmpDirAsync = spyOn(utils, 'createTmpDirAsync').mockResolvedValue(
88+
'df',
89+
)
90+
mockReadOpenapiAsync = spyOn(utils, 'readOpenapiAsync').mockResolvedValue(
91+
mockSchema as never,
92+
)
93+
mockWriteInterfaceAsync = spyOn(
94+
utils,
95+
'writeInterfaceAsync',
96+
).mockResolvedValue(undefined)
97+
mockCreateUrlMap = spyOn(generator, 'createUrlMap').mockReturnValue(
98+
mockUrlMap as never,
99+
)
100+
mockGenerateInterface = spyOn(generator, 'generateInterface').mockReturnValue(
101+
mockInterfaceContent,
102+
)
103+
mockCreateTmpDirAsync.mockClear()
104+
mockReadOpenapiAsync.mockClear()
105+
mockWriteInterfaceAsync.mockClear()
106+
mockCreateUrlMap.mockClear()
107+
mockGenerateInterface.mockClear()
108+
})
109+
110+
test('devupApiWebpackPlugin constructor initializes with default options', () => {
111+
const plugin = new devupApiWebpackPlugin()
112+
expect(plugin.options).toEqual({})
113+
expect(plugin.initialized).toBe(false)
114+
})
115+
116+
test.each([
117+
[{ tempDir: 'custom-dir' }],
118+
[{ openapiFile: 'custom-openapi.json' }],
119+
[
120+
{
121+
tempDir: 'custom-dir',
122+
openapiFile: 'custom-openapi.json',
123+
convertCase: 'snake' as const,
124+
},
125+
],
126+
] as const)('devupApiWebpackPlugin constructor initializes with options: %s', (options: DevupApiOptions) => {
127+
const plugin = new devupApiWebpackPlugin(options)
128+
expect(plugin.options).toEqual(options)
129+
expect(plugin.initialized).toBe(false)
130+
})
131+
132+
test('devupApiWebpackPlugin apply method registers beforeCompile hook', () => {
133+
const plugin = new devupApiWebpackPlugin()
134+
const compiler = createMockCompiler()
135+
plugin.apply(compiler)
136+
expect(compiler.hooks.beforeCompile.tapAsync).toHaveBeenCalledWith(
137+
'devup-api',
138+
expect.any(Function),
139+
)
140+
expect(compiler._storedCallback).toBeDefined()
141+
})
142+
143+
test.each([
144+
[undefined],
145+
[{ tempDir: 'custom-dir' }],
146+
[{ openapiFile: 'custom-openapi.json' }],
147+
[
148+
{
149+
tempDir: 'custom-dir',
150+
openapiFile: 'custom-openapi.json',
151+
convertCase: 'pascal' as const,
152+
},
153+
],
154+
] as const)('devupApiWebpackPlugin beforeCompile hook executes correctly: %s', async (options:
155+
| DevupApiOptions
156+
| undefined) => {
157+
const plugin = new devupApiWebpackPlugin(options)
158+
const compiler = createMockCompiler()
159+
const definePluginApplySpy = spyOn(
160+
DefinePlugin.prototype,
161+
'apply',
162+
).mockImplementation(() => {})
163+
plugin.apply(compiler)
164+
165+
const callback = compiler._storedCallback
166+
expect(callback).toBeDefined()
167+
168+
const mockCallback = mock(() => {})
169+
await callback?.(null, mockCallback)
170+
171+
expect(mockCreateTmpDirAsync).toHaveBeenCalledWith(options?.tempDir)
172+
expect(mockReadOpenapiAsync).toHaveBeenCalledWith(options?.openapiFile)
173+
expect(mockGenerateInterface).toHaveBeenCalledWith(mockSchema, options || {})
174+
expect(mockWriteInterfaceAsync).toHaveBeenCalledWith(
175+
join('df', 'api.d.ts'),
176+
mockInterfaceContent,
177+
)
178+
expect(mockCreateUrlMap).toHaveBeenCalledWith(mockSchema, options || {})
179+
expect(definePluginApplySpy).toHaveBeenCalledWith(compiler)
180+
expect(mockCallback).toHaveBeenCalled()
181+
expect(plugin.initialized).toBe(true)
182+
definePluginApplySpy.mockRestore()
183+
})
184+
185+
test('devupApiWebpackPlugin beforeCompile hook does not add DefinePlugin when urlMap is null', async () => {
186+
mockCreateUrlMap.mockReturnValueOnce(null as never)
187+
const plugin = new devupApiWebpackPlugin()
188+
const compiler = createMockCompiler()
189+
const definePluginApplySpy = spyOn(
190+
DefinePlugin.prototype,
191+
'apply',
192+
).mockImplementation(() => {})
193+
plugin.apply(compiler)
194+
195+
const callback = compiler._storedCallback
196+
197+
const mockCallback = mock(() => {})
198+
await callback?.(null, mockCallback)
199+
200+
expect(definePluginApplySpy).not.toHaveBeenCalled()
201+
expect(mockCallback).toHaveBeenCalled()
202+
definePluginApplySpy.mockRestore()
203+
})
204+
205+
test('devupApiWebpackPlugin beforeCompile hook does not add DefinePlugin when urlMap is undefined', async () => {
206+
mockCreateUrlMap.mockReturnValueOnce(undefined as never)
207+
const plugin = new devupApiWebpackPlugin()
208+
const compiler = createMockCompiler()
209+
const definePluginApplySpy = spyOn(
210+
DefinePlugin.prototype,
211+
'apply',
212+
).mockImplementation(() => {})
213+
plugin.apply(compiler)
214+
215+
const callback = compiler._storedCallback
216+
217+
const mockCallback = mock(() => {})
218+
await callback?.(null, mockCallback)
219+
220+
expect(definePluginApplySpy).not.toHaveBeenCalled()
221+
expect(mockCallback).toHaveBeenCalled()
222+
definePluginApplySpy.mockRestore()
223+
})
224+
225+
test('devupApiWebpackPlugin beforeCompile hook only runs once when called multiple times', async () => {
226+
const plugin = new devupApiWebpackPlugin()
227+
const compiler = createMockCompiler()
228+
plugin.apply(compiler)
229+
230+
const callback = compiler._storedCallback
231+
232+
const mockCallback1 = mock(() => {})
233+
const mockCallback2 = mock(() => {})
234+
235+
await Promise.all([
236+
callback?.(null, mockCallback1),
237+
callback?.(null, mockCallback2),
238+
])
239+
240+
expect(mockCreateTmpDirAsync).toHaveBeenCalledTimes(1)
241+
expect(mockReadOpenapiAsync).toHaveBeenCalledTimes(1)
242+
expect(mockGenerateInterface).toHaveBeenCalledTimes(1)
243+
expect(mockWriteInterfaceAsync).toHaveBeenCalledTimes(1)
244+
expect(mockCreateUrlMap).toHaveBeenCalledTimes(1)
245+
expect(mockCallback1).toHaveBeenCalled()
246+
expect(mockCallback2).toHaveBeenCalled()
247+
})
248+
249+
test('devupApiWebpackPlugin beforeCompile hook handles errors correctly', async () => {
250+
const error = new Error('Test error')
251+
mockCreateTmpDirAsync.mockRejectedValueOnce(error)
252+
const plugin = new devupApiWebpackPlugin()
253+
const compiler = createMockCompiler()
254+
plugin.apply(compiler)
255+
256+
const callback = compiler._storedCallback
257+
258+
const mockCallback = mock(() => {})
259+
await callback?.(null, mockCallback)
260+
261+
expect(mockCallback).toHaveBeenCalledWith(error)
262+
expect(plugin.initialized).toBe(false)
263+
})

packages/webpack-plugin/src/plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { DefinePlugin } from 'webpack'
1111

1212
export class devupApiWebpackPlugin {
1313
options: DevupApiOptions
14-
private initialized = false
14+
initialized = false
1515

1616
constructor(options?: DevupApiOptions) {
1717
this.options = options || {}

0 commit comments

Comments
 (0)