Skip to content

Commit feff212

Browse files
committed
Support turbopack
1 parent 5741737 commit feff212

File tree

6 files changed

+441
-4
lines changed

6 files changed

+441
-4
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { existsSync } from 'node:fs'
2+
import { join } from 'node:path'
3+
4+
import { findRoot } from '../find-root'
5+
6+
vi.mock('node:fs')
7+
8+
describe('findRoot', () => {
9+
beforeEach(() => {
10+
vi.clearAllMocks()
11+
})
12+
13+
it('should return the first directory with package.json when found', () => {
14+
const mockDir = '/project/src/components'
15+
const expectedRoot = '/project'
16+
17+
vi.mocked(existsSync)
18+
.mockReturnValueOnce(false) // /project/src/components/package.json
19+
.mockReturnValueOnce(false) // /project/src/package.json
20+
.mockReturnValueOnce(true) // /project/package.json
21+
.mockReturnValueOnce(false) // /package.json
22+
23+
const result = findRoot(mockDir)
24+
25+
expect(result).toBe(expectedRoot)
26+
expect(existsSync).toHaveBeenCalledWith(
27+
join('/project/src/components', 'package.json'),
28+
)
29+
expect(existsSync).toHaveBeenCalledWith(
30+
join('/project/src', 'package.json'),
31+
)
32+
expect(existsSync).toHaveBeenCalledWith(join('/project', 'package.json'))
33+
})
34+
35+
it('should return process.cwd() when no package.json is found', () => {
36+
const mockDir = '/some/deep/nested/directory'
37+
const originalCwd = process.cwd()
38+
39+
vi.mocked(existsSync).mockReturnValue(false)
40+
41+
const result = findRoot(mockDir)
42+
43+
expect(result).toBe(originalCwd)
44+
})
45+
46+
it('should handle root directory correctly', () => {
47+
const mockDir = '/'
48+
49+
vi.mocked(existsSync).mockReturnValue(false)
50+
51+
const result = findRoot(mockDir)
52+
53+
expect(result).toBe(process.cwd())
54+
})
55+
56+
it('should find package.json in current directory', () => {
57+
const mockDir = '/project'
58+
59+
vi.mocked(existsSync)
60+
.mockReturnValueOnce(true) // /project/package.json
61+
.mockReturnValueOnce(false) // /package.json
62+
63+
const result = findRoot(mockDir)
64+
65+
expect(result).toBe('/project')
66+
})
67+
68+
it('should handle multiple package.json files and return the deepest one', () => {
69+
const mockDir = '/project/src/components/deep'
70+
const expectedRoot = '/project' // The function returns the last found package.json (closest to root)
71+
72+
vi.mocked(existsSync)
73+
.mockReturnValueOnce(false) // /project/src/components/deep/package.json
74+
.mockReturnValueOnce(false) // /project/src/components/package.json
75+
.mockReturnValueOnce(true) // /project/src/package.json
76+
.mockReturnValueOnce(true) // /project/package.json
77+
.mockReturnValueOnce(false) // /package.json
78+
79+
const result = findRoot(mockDir)
80+
81+
expect(result).toBe(expectedRoot)
82+
})
83+
84+
it('should handle Windows-style paths', () => {
85+
const mockDir = 'C:\\project\\src\\components'
86+
const expectedRoot = 'C:\\project'
87+
88+
vi.mocked(existsSync)
89+
.mockReturnValueOnce(false) // C:\project\src\components\package.json
90+
.mockReturnValueOnce(false) // C:\project\src\package.json
91+
.mockReturnValueOnce(true) // C:\project\package.json
92+
.mockReturnValueOnce(false) // C:\package.json
93+
94+
const result = findRoot(mockDir)
95+
96+
expect(result).toBe(expectedRoot)
97+
})
98+
99+
it('should handle relative paths', () => {
100+
const mockDir = './src/components'
101+
const expectedRoot = '.'
102+
103+
vi.mocked(existsSync)
104+
.mockReturnValueOnce(false) // ./src/components/package.json
105+
.mockReturnValueOnce(false) // ./src/package.json
106+
.mockReturnValueOnce(true) // ./package.json
107+
.mockReturnValueOnce(false) // ../package.json
108+
109+
const result = findRoot(mockDir)
110+
111+
expect(result).toBe(expectedRoot)
112+
})
113+
114+
it('should stop at filesystem root', () => {
115+
const mockDir = '/some/path'
116+
117+
// Mock existsSync to return false for all calls, simulating no package.json found
118+
vi.mocked(existsSync).mockReturnValue(false)
119+
120+
const result = findRoot(mockDir)
121+
122+
expect(result).toBe(process.cwd())
123+
})
124+
125+
it('should handle empty string input', () => {
126+
const mockDir = ''
127+
128+
vi.mocked(existsSync).mockReturnValue(false)
129+
130+
const result = findRoot(mockDir)
131+
132+
expect(result).toBe(process.cwd())
133+
})
134+
135+
it('should handle single character directory', () => {
136+
const mockDir = 'a'
137+
138+
vi.mocked(existsSync)
139+
.mockReturnValueOnce(false) // a/package.json
140+
.mockReturnValueOnce(false) // ./package.json
141+
142+
const result = findRoot(mockDir)
143+
144+
expect(result).toBe(process.cwd())
145+
})
146+
})
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import { globSync, writeFileSync } from 'node:fs'
2+
import { readFileSync } from 'node:fs'
3+
import { existsSync } from 'node:fs'
4+
import { join } from 'node:path'
5+
6+
import { codeExtract, registerTheme } from '@devup-ui/wasm'
7+
8+
import { findRoot } from '../find-root'
9+
import { preload } from '../preload'
10+
11+
// Mock dependencies
12+
vi.mock('node:fs')
13+
vi.mock('@devup-ui/wasm')
14+
vi.mock('../find-root')
15+
16+
// Mock globSync
17+
vi.mock('node:fs', () => ({
18+
globSync: vi.fn(),
19+
readFileSync: vi.fn(),
20+
writeFileSync: vi.fn(),
21+
mkdirSync: vi.fn(),
22+
existsSync: vi.fn(),
23+
}))
24+
25+
// Mock @devup-ui/wasm
26+
vi.mock('@devup-ui/wasm', () => ({
27+
codeExtract: vi.fn(),
28+
registerTheme: vi.fn(),
29+
}))
30+
31+
// Mock findRoot
32+
vi.mock('../find-root', () => ({
33+
findRoot: vi.fn(),
34+
}))
35+
36+
describe('preload', () => {
37+
beforeEach(() => {
38+
vi.clearAllMocks()
39+
40+
// Default mock implementations
41+
vi.mocked(findRoot).mockReturnValue('/project/root')
42+
vi.mocked(globSync).mockReturnValue([
43+
'src/App.tsx',
44+
'src/components/Button.tsx',
45+
])
46+
vi.mocked(readFileSync).mockReturnValue(
47+
'const Button = () => <div>Hello</div>',
48+
)
49+
vi.mocked(codeExtract).mockReturnValue({
50+
free: vi.fn(),
51+
cssFile: 'styles.css',
52+
css: '.button { color: red; }',
53+
code: '',
54+
map: '',
55+
updatedBaseStyle: false,
56+
[Symbol.dispose]: vi.fn(),
57+
})
58+
vi.mocked(existsSync).mockReturnValue(true)
59+
})
60+
61+
it('should find project root and collect files', () => {
62+
const excludeRegex = /node_modules/
63+
const libPackage = '@devup-ui/react'
64+
const singleCss = false
65+
const theme = { colors: { primary: 'blue' } }
66+
const cssDir = '/output/css'
67+
68+
preload(excludeRegex, libPackage, singleCss, theme, cssDir)
69+
70+
expect(findRoot).toHaveBeenCalledWith(process.cwd())
71+
expect(globSync).toHaveBeenCalledWith(
72+
['**/*.tsx', '**/*.ts', '**/*.js', '**/*.mjs'],
73+
{
74+
cwd: '/project/root',
75+
exclude: expect.any(Function),
76+
},
77+
)
78+
})
79+
80+
it('should register theme before processing files', () => {
81+
const theme = { colors: { primary: 'blue' } }
82+
83+
preload(/node_modules/, '@devup-ui/react', false, theme, '/output/css')
84+
85+
expect(registerTheme).toHaveBeenCalledWith(theme)
86+
})
87+
88+
it('should process each collected file', () => {
89+
const files = ['src/App.tsx', 'src/components/Button.tsx']
90+
vi.mocked(globSync).mockReturnValue(files)
91+
92+
preload(/node_modules/, '@devup-ui/react', false, {}, '/output/css')
93+
94+
expect(codeExtract).toHaveBeenCalledTimes(2)
95+
expect(codeExtract).toHaveBeenCalledWith(
96+
expect.stringMatching(/App\.tsx$/),
97+
'const Button = () => <div>Hello</div>',
98+
'@devup-ui/react',
99+
'/output/css',
100+
false,
101+
false,
102+
true,
103+
)
104+
})
105+
106+
it('should write CSS file when cssFile is returned', () => {
107+
vi.mocked(codeExtract).mockReturnValue({
108+
cssFile: 'styles.css',
109+
css: '.button { color: red; }',
110+
free: vi.fn(),
111+
code: '',
112+
map: '',
113+
updatedBaseStyle: false,
114+
[Symbol.dispose]: vi.fn(),
115+
})
116+
117+
preload(/node_modules/, '@devup-ui/react', false, {}, '/output/css')
118+
119+
expect(writeFileSync).toHaveBeenCalledWith(
120+
join('/output/css', 'styles.css'),
121+
'.button { color: red; }',
122+
'utf-8',
123+
)
124+
})
125+
126+
it('should not write CSS file when cssFile is null', () => {
127+
vi.mocked(codeExtract).mockReturnValue({
128+
cssFile: undefined,
129+
css: '.button { color: red; }',
130+
free: vi.fn(),
131+
code: '',
132+
map: '',
133+
updatedBaseStyle: false,
134+
[Symbol.dispose]: vi.fn(),
135+
})
136+
137+
preload(/node_modules/, '@devup-ui/react', false, {}, '/output/css')
138+
139+
expect(writeFileSync).not.toHaveBeenCalled()
140+
})
141+
142+
it('should handle empty CSS content', () => {
143+
vi.mocked(codeExtract).mockReturnValue({
144+
cssFile: 'styles.css',
145+
css: '',
146+
free: vi.fn(),
147+
code: '',
148+
map: '',
149+
updatedBaseStyle: false,
150+
[Symbol.dispose]: vi.fn(),
151+
})
152+
153+
preload(/node_modules/, '@devup-ui/react', false, {}, '/output/css')
154+
155+
expect(writeFileSync).toHaveBeenCalledWith(
156+
join('/output/css', 'styles.css'),
157+
'',
158+
'utf-8',
159+
)
160+
})
161+
162+
it('should handle undefined CSS content', () => {
163+
vi.mocked(codeExtract).mockReturnValue({
164+
cssFile: 'styles.css',
165+
css: undefined,
166+
free: vi.fn(),
167+
code: '',
168+
map: '',
169+
updatedBaseStyle: false,
170+
[Symbol.dispose]: vi.fn(),
171+
})
172+
173+
preload(/node_modules/, '@devup-ui/react', false, {}, '/output/css')
174+
175+
expect(writeFileSync).toHaveBeenCalledWith(
176+
join('/output/css', 'styles.css'),
177+
'',
178+
'utf-8',
179+
)
180+
})
181+
182+
it('should pass correct parameters to codeExtract', () => {
183+
const libPackage = '@devup-ui/react'
184+
const singleCss = true
185+
const cssDir = '/custom/css/dir'
186+
187+
preload(/node_modules/, libPackage, singleCss, {}, cssDir)
188+
189+
expect(codeExtract).toHaveBeenCalledWith(
190+
expect.stringMatching(/App\.tsx$/),
191+
'const Button = () => <div>Hello</div>',
192+
libPackage,
193+
cssDir,
194+
singleCss,
195+
false,
196+
true,
197+
)
198+
})
199+
200+
it('should handle multiple files with different CSS outputs', () => {
201+
const files = ['src/App.tsx', 'src/components/Button.tsx']
202+
vi.mocked(globSync).mockReturnValue(files)
203+
204+
vi.mocked(codeExtract)
205+
.mockReturnValueOnce({
206+
cssFile: 'app.css',
207+
css: '.app { margin: 0; }',
208+
free: vi.fn(),
209+
code: '',
210+
map: '',
211+
updatedBaseStyle: false,
212+
[Symbol.dispose]: vi.fn(),
213+
})
214+
.mockReturnValueOnce({
215+
free: vi.fn(),
216+
cssFile: 'button.css',
217+
css: '.button { color: blue; }',
218+
code: '',
219+
map: '',
220+
updatedBaseStyle: false,
221+
[Symbol.dispose]: vi.fn(),
222+
})
223+
224+
preload(/node_modules/, '@devup-ui/react', false, {}, '/output/css')
225+
226+
expect(writeFileSync).toHaveBeenCalledTimes(2)
227+
expect(writeFileSync).toHaveBeenCalledWith(
228+
join('/output/css', 'app.css'),
229+
'.app { margin: 0; }',
230+
'utf-8',
231+
)
232+
expect(writeFileSync).toHaveBeenCalledWith(
233+
join('/output/css', 'button.css'),
234+
'.button { color: blue; }',
235+
'utf-8',
236+
)
237+
})
238+
})

0 commit comments

Comments
 (0)