Skip to content

Commit 3ae8577

Browse files
committed
Codex unit tests for move
1 parent abe7594 commit 3ae8577

File tree

2 files changed

+114
-1
lines changed

2 files changed

+114
-1
lines changed

src/preload.test.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import path from 'node:path'
2+
import { beforeEach, describe, expect, it, vi } from 'vitest'
3+
4+
const fsPromisesMock = vi.hoisted(() => ({
5+
rename: vi.fn(),
6+
lstat: vi.fn(),
7+
mkdir: vi.fn(),
8+
cp: vi.fn(),
9+
rm: vi.fn(),
10+
access: vi.fn(),
11+
readFile: vi.fn(),
12+
writeFile: vi.fn(),
13+
readdir: vi.fn(),
14+
stat: vi.fn(),
15+
constants: { R_OK: 4, W_OK: 2 },
16+
}))
17+
18+
vi.mock('node:fs/promises', () => ({
19+
default: fsPromisesMock,
20+
}))
21+
22+
vi.mock('electron', () => ({
23+
contextBridge: {
24+
exposeInMainWorld: vi.fn(),
25+
},
26+
ipcRenderer: {
27+
on: vi.fn(),
28+
invoke: vi.fn(),
29+
removeListener: vi.fn(),
30+
},
31+
}))
32+
33+
vi.mock('chokidar', () => ({
34+
default: {
35+
watch: vi.fn(() => ({
36+
on: vi.fn(),
37+
off: vi.fn(),
38+
})),
39+
},
40+
}))
41+
42+
import { move } from '@src/preload'
43+
44+
describe('move', () => {
45+
beforeEach(() => {
46+
vi.clearAllMocks()
47+
fsPromisesMock.lstat.mockResolvedValue({
48+
isDirectory: () => false,
49+
})
50+
fsPromisesMock.rename.mockResolvedValue(undefined)
51+
fsPromisesMock.mkdir.mockResolvedValue(undefined)
52+
fsPromisesMock.cp.mockResolvedValue(undefined)
53+
fsPromisesMock.rm.mockResolvedValue(undefined)
54+
})
55+
56+
it('renames directly when possible', async () => {
57+
const result = await move('source.txt', 'dest.txt')
58+
59+
expect(result).toBeUndefined()
60+
expect(fsPromisesMock.rename).toHaveBeenCalledTimes(1)
61+
expect(fsPromisesMock.rename).toHaveBeenCalledWith('source.txt', 'dest.txt')
62+
expect(fsPromisesMock.mkdir).not.toHaveBeenCalled()
63+
expect(fsPromisesMock.cp).not.toHaveBeenCalled()
64+
expect(fsPromisesMock.rm).not.toHaveBeenCalled()
65+
})
66+
67+
it('creates destination directories when rename fails with ENOENT', async () => {
68+
const destination = path.join('tmp', 'dir', 'file.txt')
69+
fsPromisesMock.rename
70+
.mockRejectedValueOnce({ code: 'ENOENT' })
71+
.mockResolvedValueOnce(undefined)
72+
73+
await move('source.txt', destination)
74+
75+
expect(fsPromisesMock.mkdir).toHaveBeenCalledWith(path.join('tmp', 'dir'), {
76+
recursive: true,
77+
})
78+
expect(fsPromisesMock.rename).toHaveBeenCalledTimes(2)
79+
expect(fsPromisesMock.cp).not.toHaveBeenCalled()
80+
expect(fsPromisesMock.rm).not.toHaveBeenCalled()
81+
})
82+
83+
it('falls back to copy and delete when rename fails with EXDEV', async () => {
84+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
85+
fsPromisesMock.rename.mockRejectedValueOnce({ code: 'EXDEV' })
86+
fsPromisesMock.cp.mockResolvedValueOnce('copied')
87+
fsPromisesMock.rm.mockResolvedValueOnce('removed')
88+
89+
const result = await move('source.txt', 'dest.txt')
90+
91+
expect(result).toEqual(['copied', 'removed'])
92+
expect(fsPromisesMock.cp).toHaveBeenCalledWith('source.txt', 'dest.txt', {
93+
recursive: true,
94+
})
95+
expect(fsPromisesMock.rm).toHaveBeenCalledWith('source.txt', {
96+
recursive: true,
97+
})
98+
consoleSpy.mockRestore()
99+
})
100+
101+
it('returns the error for non-EXDEV failures', async () => {
102+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
103+
const error = { code: 'EACCES' }
104+
fsPromisesMock.rename.mockRejectedValueOnce(error)
105+
106+
const result = await move('source.txt', 'dest.txt')
107+
108+
expect(result).toBe(error)
109+
expect(fsPromisesMock.cp).not.toHaveBeenCalled()
110+
expect(fsPromisesMock.rm).not.toHaveBeenCalled()
111+
consoleSpy.mockRestore()
112+
})
113+
})

src/preload.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ const stat = (path: string) => {
123123
* creating any folders necessary to do so,
124124
* and falling back to copy-and-delete if rename fails.
125125
*/
126-
async function move(source: string | URL, destination: string | URL) {
126+
export async function move(source: string | URL, destination: string | URL) {
127127
const isDir = (await fs.lstat(source)).isDirectory()
128128
return fs
129129
.rename(source, destination)

0 commit comments

Comments
 (0)