Skip to content

Commit 2def3ca

Browse files
test: bump patch coverage.
1 parent 42d2f09 commit 2def3ca

1 file changed

Lines changed: 207 additions & 0 deletions

File tree

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import assert from 'node:assert/strict'
2+
import fs from 'node:fs/promises'
3+
import os from 'node:os'
4+
import path from 'node:path'
5+
import test from 'node:test'
6+
import { pathToFileURL } from 'node:url'
7+
8+
import { collectStyleImports } from '../src/moduleGraph.ts'
9+
import type { CssResolver } from '../src/types.js'
10+
11+
interface Project {
12+
root: string
13+
file: (rel: string) => string
14+
writeFile: (rel: string, contents: string) => Promise<string>
15+
cleanup: () => Promise<void>
16+
}
17+
18+
async function createProject(prefix: string): Promise<Project> {
19+
const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix))
20+
const writeFile = async (rel: string, contents: string): Promise<string> => {
21+
const target = path.join(root, rel)
22+
await fs.mkdir(path.dirname(target), { recursive: true })
23+
await fs.writeFile(target, contents, 'utf8')
24+
return target
25+
}
26+
return {
27+
root,
28+
file: rel => path.join(root, rel),
29+
writeFile,
30+
cleanup: () => fs.rm(root, { recursive: true, force: true }),
31+
}
32+
}
33+
34+
async function realpathAll(paths: string[]): Promise<string[]> {
35+
return Promise.all(paths.map(filePath => fs.realpath(filePath)))
36+
}
37+
38+
test('collectStyleImports walks modules, resolves variants, and dedupes styles', async () => {
39+
const project = await createProject('knighted-module-graph-basic-')
40+
try {
41+
await project.writeFile('styles/shared.css', '.shared { color: red; }')
42+
await project.writeFile('styles/component.scss', '.component { color: blue; }')
43+
await project.writeFile('styles/legacy.css', '.legacy { color: green; }')
44+
await project.writeFile('styles/exported.css', '.exported { color: purple; }')
45+
await project.writeFile('styles/async.css', '.async { color: orange; }')
46+
await project.writeFile('styles/nested/deep.css', '.deep { color: teal; }')
47+
await project.writeFile('styles/virtual.css', '.virtual { color: pink; }')
48+
await project.writeFile('styles/url.css', '.url { color: black; }')
49+
50+
const fileUrl = pathToFileURL(project.file('styles/url.css')).href
51+
const entrySource = `import '${fileUrl}'
52+
import './styles/shared.css?inline#hash'
53+
import './component.js'
54+
import './missing/script.js'
55+
import 'node:fs'
56+
const builtin = require('fs')
57+
await import('./async-chunk.js')
58+
await import('https://cdn.knighted.dev/widget.js')
59+
import '@virtual/style'
60+
void builtin
61+
`
62+
await project.writeFile('entry.ts', entrySource)
63+
64+
const componentSource = `import './styles/component.scss'
65+
const duplicate = require('./styles/shared.css')
66+
import legacyStyles = require('./styles/legacy.css')
67+
export * from './styles/exported.css?raw'
68+
const viaProperty = require.resolve('./styles/component.scss')
69+
void duplicate
70+
void legacyStyles
71+
void viaProperty
72+
`
73+
await project.writeFile('component.ts', componentSource)
74+
75+
const asyncChunkSource = `import './styles/async.css?url#data'
76+
export async function load() {
77+
await import('./nested/deep.js')
78+
return import('node:path')
79+
}
80+
`
81+
await project.writeFile('async-chunk.tsx', asyncChunkSource)
82+
83+
const nestedSource = `import '../styles/nested/deep.css'
84+
`
85+
await project.writeFile('nested/deep.ts', nestedSource)
86+
87+
const resolver: CssResolver = async specifier => {
88+
if (specifier === '@virtual/style') {
89+
return pathToFileURL(project.file('styles/virtual.css')).href
90+
}
91+
return undefined
92+
}
93+
94+
const styles = await collectStyleImports(project.file('entry.ts'), {
95+
cwd: project.root,
96+
styleExtensions: ['.css', '.scss'],
97+
filter: () => true,
98+
resolver,
99+
})
100+
101+
const expected = [
102+
project.file('styles/url.css'),
103+
project.file('styles/shared.css'),
104+
project.file('styles/component.scss'),
105+
project.file('styles/legacy.css'),
106+
project.file('styles/exported.css'),
107+
project.file('styles/async.css'),
108+
project.file('styles/nested/deep.css'),
109+
project.file('styles/virtual.css'),
110+
]
111+
112+
assert.deepEqual(await realpathAll(styles), await realpathAll(expected))
113+
} finally {
114+
await project.cleanup()
115+
}
116+
})
117+
118+
test('collectStyleImports honors tsconfig paths loaded from a file', async () => {
119+
const project = await createProject('knighted-module-graph-tsconfig-file-')
120+
try {
121+
await project.writeFile(
122+
'configs/tsconfig.json',
123+
JSON.stringify(
124+
{
125+
compilerOptions: {
126+
baseUrl: '../src',
127+
paths: {
128+
'@theme/*': ['styles/*'],
129+
'@pkg/*': ['pkg/*/index'],
130+
'@core': ['styles/core/index.css'],
131+
},
132+
},
133+
},
134+
null,
135+
2,
136+
),
137+
)
138+
139+
await project.writeFile('src/styles/colors.css', '.colors { color: coral; }')
140+
await project.writeFile('src/styles/button.css', '.button { color: salmon; }')
141+
await project.writeFile('src/styles/core/index.css', '.core { color: navy; }')
142+
143+
const pkgSource = `import '@theme/button.css'
144+
`
145+
await project.writeFile('src/pkg/button/index.ts', pkgSource)
146+
147+
const entrySource = `import '@theme/colors.css'
148+
import '@core'
149+
import '@pkg/button'
150+
`
151+
await project.writeFile('src/entry.ts', entrySource)
152+
153+
const styles = await collectStyleImports(project.file('src/entry.ts'), {
154+
cwd: project.root,
155+
styleExtensions: ['.css'],
156+
filter: filePath => filePath.startsWith(project.root),
157+
graphOptions: {
158+
tsConfig: project.file('configs'),
159+
},
160+
})
161+
162+
assert.deepEqual(styles, [
163+
project.file('src/styles/colors.css'),
164+
project.file('src/styles/core/index.css'),
165+
project.file('src/styles/button.css'),
166+
])
167+
} finally {
168+
await project.cleanup()
169+
}
170+
})
171+
172+
test('collectStyleImports resolves inline tsconfig objects with directory indexes', async () => {
173+
const project = await createProject('knighted-module-graph-tsconfig-inline-')
174+
try {
175+
await project.writeFile('styles/direct.css', '.direct { color: cyan; }')
176+
await project.writeFile('blocks/panel/index.css', '.panel { color: magenta; }')
177+
178+
const entrySource = `import '@direct'
179+
import '@blocks/panel'
180+
`
181+
await project.writeFile('entry.ts', entrySource)
182+
183+
const styles = await collectStyleImports(project.file('entry.ts'), {
184+
cwd: project.root,
185+
styleExtensions: ['.css'],
186+
filter: filePath => filePath.startsWith(project.root),
187+
graphOptions: {
188+
tsConfig: {
189+
compilerOptions: {
190+
baseUrl: '.',
191+
paths: {
192+
'@direct': ['styles/direct.css'],
193+
'@blocks/*': ['blocks/*'],
194+
},
195+
},
196+
},
197+
},
198+
})
199+
200+
assert.deepEqual(styles, [
201+
project.file('styles/direct.css'),
202+
project.file('blocks/panel/index.css'),
203+
])
204+
} finally {
205+
await project.cleanup()
206+
}
207+
})

0 commit comments

Comments
 (0)