Skip to content

Commit 858661d

Browse files
committed
test: add test for Project
1 parent 10e4db0 commit 858661d

File tree

2 files changed

+238
-1
lines changed

2 files changed

+238
-1
lines changed

packages/codegen/src/project.test.ts

Lines changed: 236 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { access, chmod } from 'node:fs/promises';
1+
import { access, chmod, writeFile } from 'node:fs/promises';
22
import { TsConfigFileNotFoundError } from '@css-modules-kit/core';
3+
import dedent from 'dedent';
34
import { describe, expect, test } from 'vitest';
45
import { ReadCSSModuleFileError } from './error.js';
56
import { createProject } from './project.js';
@@ -31,6 +32,240 @@ describe('createProject', () => {
3132
);
3233
});
3334

35+
test('isWildcardMatchedFile', async () => {
36+
const iff = await createIFF({
37+
'tsconfig.json': dedent`
38+
{
39+
"include": ["src"],
40+
"exclude": ["src/excluded"]
41+
}
42+
`,
43+
});
44+
const project = createProject({ project: iff.rootDir });
45+
expect(project.isWildcardMatchedFile(iff.join('src/a.module.css'))).toBe(true);
46+
expect(project.isWildcardMatchedFile(iff.join('src/excluded/b.module.css'))).toBe(false);
47+
expect(project.isWildcardMatchedFile(iff.join('c.module.css'))).toBe(false);
48+
});
49+
50+
describe('addFile', () => {
51+
test('The diagnostics of the added file are reported and .d.ts file is emitted', async () => {
52+
const iff = await createIFF({
53+
'tsconfig.json': '{}',
54+
'src': {},
55+
});
56+
const project = createProject({ project: iff.rootDir });
57+
58+
// Even if the file is added, diagnostics will not change until notified by `addFile`.
59+
await writeFile(iff.join('src/a.module.css'), '.a_1 {');
60+
expect(formatDiagnostics(project.getDiagnostics(), iff.rootDir)).toMatchInlineSnapshot(`
61+
[
62+
{
63+
"category": "error",
64+
"text": "The file specified in tsconfig.json not found.",
65+
},
66+
]
67+
`);
68+
69+
project.addFile(iff.join('src/a.module.css'));
70+
expect(formatDiagnostics(project.getDiagnostics(), iff.rootDir)).toMatchInlineSnapshot(`
71+
[
72+
{
73+
"category": "error",
74+
"fileName": "<rootDir>/src/a.module.css",
75+
"length": 1,
76+
"start": {
77+
"column": 1,
78+
"line": 1,
79+
},
80+
"text": "Unclosed block",
81+
},
82+
]
83+
`);
84+
85+
await project.emitDtsFiles();
86+
await expect(access(iff.join('generated/src/a.module.css.d.ts'))).resolves.not.toThrow();
87+
});
88+
test('Adding a file may resolve diagnostics in files that import it directly or indirectly', async () => {
89+
// This test case suggests the following facts:
90+
// - The check stage cache for files that directly import the added file should be invalidated.
91+
// - The check stage cache for files that indirectly import the added file should also be invalidated.
92+
const iff = await createIFF({
93+
'tsconfig.json': '{}',
94+
'src/b.module.css': '@import "./a.module.css";', // directly
95+
'src/c.module.css': '@value a_1 from "./b.module.css";', // indirectly
96+
});
97+
const project = createProject({ project: iff.rootDir });
98+
expect(formatDiagnostics(project.getDiagnostics(), iff.rootDir)).toMatchInlineSnapshot(`
99+
[
100+
{
101+
"category": "error",
102+
"fileName": "<rootDir>/src/b.module.css",
103+
"length": 14,
104+
"start": {
105+
"column": 10,
106+
"line": 1,
107+
},
108+
"text": "Cannot import module './a.module.css'",
109+
},
110+
{
111+
"category": "error",
112+
"fileName": "<rootDir>/src/c.module.css",
113+
"length": 3,
114+
"start": {
115+
"column": 8,
116+
"line": 1,
117+
},
118+
"text": "Module './b.module.css' has no exported token 'a_1'.",
119+
},
120+
]
121+
`);
122+
await writeFile(iff.join('src/a.module.css'), '@value a_1: red;');
123+
project.addFile(iff.join('src/a.module.css'));
124+
expect(formatDiagnostics(project.getDiagnostics(), iff.rootDir)).toMatchInlineSnapshot(`[]`);
125+
});
126+
test('Adding a file may change the resolution results of import specifiers in other files', async () => {
127+
// This test case suggests the following facts:
128+
// - The check stage cache for files that import other files should be invalidated.
129+
// - Only independent files that do not import any other files can keep the check stage cache.
130+
const iff = await createIFF({
131+
'tsconfig.json': dedent`
132+
{
133+
"compilerOptions": {
134+
"paths": {
135+
"@/a.module.css": ["src/a-1.module.css", "src/a-2.module.css"]
136+
}
137+
}
138+
}
139+
`,
140+
'src/a-2.module.css': '@value a_2: red;',
141+
'src/b.module.css': '@import "@/a.module.css";',
142+
'src/c.module.css': '@value a_2 from "./b.module.css";',
143+
});
144+
const project = createProject({ project: iff.rootDir });
145+
expect(formatDiagnostics(project.getDiagnostics(), iff.rootDir)).toMatchInlineSnapshot(`[]`);
146+
await writeFile(iff.join('src/a-1.module.css'), '@value a_1: red;');
147+
project.addFile(iff.join('src/a-1.module.css'));
148+
expect(formatDiagnostics(project.getDiagnostics(), iff.rootDir)).toMatchInlineSnapshot(`
149+
[
150+
{
151+
"category": "error",
152+
"fileName": "<rootDir>/src/c.module.css",
153+
"length": 3,
154+
"start": {
155+
"column": 8,
156+
"line": 1,
157+
},
158+
"text": "Module './b.module.css' has no exported token 'a_2'.",
159+
},
160+
]
161+
`);
162+
});
163+
});
164+
165+
describe('updateFile', () => {
166+
test('The new diagnostics of the changed file are reported and new .d.ts file is emitted', async () => {
167+
const iff = await createIFF({
168+
'tsconfig.json': '{}',
169+
'src/a.module.css': '',
170+
});
171+
const project = createProject({ project: iff.rootDir });
172+
173+
// Even if the file is updated, diagnostics will not change until notified by `updateFile`.
174+
await writeFile(iff.join('src/a.module.css'), '.a_1 {');
175+
expect(formatDiagnostics(project.getDiagnostics(), iff.rootDir)).toMatchInlineSnapshot(`[]`);
176+
177+
// New syntactic diagnostics are reported
178+
project.updateFile(iff.join('src/a.module.css'));
179+
expect(formatDiagnostics(project.getDiagnostics(), iff.rootDir)).toMatchInlineSnapshot(`
180+
[
181+
{
182+
"category": "error",
183+
"fileName": "<rootDir>/src/a.module.css",
184+
"length": 1,
185+
"start": {
186+
"column": 1,
187+
"line": 1,
188+
},
189+
"text": "Unclosed block",
190+
},
191+
]
192+
`);
193+
194+
// New .d.ts file is emitted
195+
await project.emitDtsFiles();
196+
expect(await iff.readFile('generated/src/a.module.css.d.ts')).toMatchInlineSnapshot(`
197+
"// @ts-nocheck
198+
declare const styles = {
199+
a_1: '' as readonly string,
200+
};
201+
export default styles;
202+
"
203+
`);
204+
205+
// New semantic diagnostics are reported
206+
await writeFile(iff.join('src/a.module.css'), `@import './non-existent.module.css';`);
207+
project.updateFile(iff.join('src/a.module.css'));
208+
expect(formatDiagnostics(project.getDiagnostics(), iff.rootDir)).toMatchInlineSnapshot(`
209+
[
210+
{
211+
"category": "error",
212+
"fileName": "<rootDir>/src/a.module.css",
213+
"length": 25,
214+
"start": {
215+
"column": 10,
216+
"line": 1,
217+
},
218+
"text": "Cannot import module './non-existent.module.css'",
219+
},
220+
]
221+
`);
222+
});
223+
test('Changing a file may resolve diagnostics in files that import it directly or indirectly', async () => {
224+
// This test case suggests the following facts:
225+
// - The check stage cache for files that directly import the changed file should be invalidated.
226+
// - The check stage cache for files that indirectly import the changed file should also be invalidated.
227+
const iff = await createIFF({
228+
'tsconfig.json': '{}',
229+
'src/a.module.css': '',
230+
'src/b.module.css': dedent`
231+
@value a_1 from "./a.module.css";
232+
@import "./a.module.css";
233+
`,
234+
'src/c.module.css': '@value a_2 from "./b.module.css";',
235+
});
236+
const project = createProject({ project: iff.rootDir });
237+
expect(formatDiagnostics(project.getDiagnostics(), iff.rootDir)).toMatchInlineSnapshot(`
238+
[
239+
{
240+
"category": "error",
241+
"fileName": "<rootDir>/src/b.module.css",
242+
"length": 3,
243+
"start": {
244+
"column": 8,
245+
"line": 1,
246+
},
247+
"text": "Module './a.module.css' has no exported token 'a_1'.",
248+
},
249+
{
250+
"category": "error",
251+
"fileName": "<rootDir>/src/c.module.css",
252+
"length": 3,
253+
"start": {
254+
"column": 8,
255+
"line": 1,
256+
},
257+
"text": "Module './b.module.css' has no exported token 'a_2'.",
258+
},
259+
]
260+
`);
261+
await writeFile(iff.join('src/a.module.css'), '@value a_1: red; @value a_2: blue;');
262+
project.updateFile(iff.join('src/a.module.css'));
263+
expect(formatDiagnostics(project.getDiagnostics(), iff.rootDir)).toMatchInlineSnapshot(`[]`);
264+
});
265+
});
266+
267+
describe.todo('deleteFile', () => {});
268+
34269
describe('getDiagnostics', () => {
35270
test('returns empty array when no diagnostics', async () => {
36271
const iff = await createIFF({

packages/codegen/src/runner.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,5 @@ describe('runCMK', () => {
9494
await expect(access(iff.join('generated/src/old.module.css.d.ts'))).rejects.toThrow();
9595
});
9696
});
97+
98+
describe.todo('runCMKInWatchMode', () => {});

0 commit comments

Comments
 (0)