Skip to content

Commit 5e44b7f

Browse files
mizdraclaude
andauthored
test(ts-plugin): split remaining e2e tests into per-behavior cases (#382)
* test(ts-plugin): split refactor / rename-file / disabled / invalid-syntax / pure-css-file / ignore-generated-files e2e tests into per-behavior cases Each test now owns a minimal fixture and asserts a single behavior, completing the multi-PR series started in #378. Hard-coded line/offset literals are replaced with getRange / getLoc, and the inline diagnostic snapshot in ignore-generated-files is replaced with a structural toStrictEqual. Also adds a missing case to refactor.test.ts: the Create CSS Module file refactor is suppressed when the paired *.module.css already exists. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(ts-plugin): rename ignore-generated-files test to focus on the .d.ts exclusion behavior The previous name described the observable diagnostic; the new name foregrounds the actual invariant under test: ts-plugin removes generated .d.ts files from the module resolution path even when their directory is listed in rootDirs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 43f82ae commit 5e44b7f

6 files changed

Lines changed: 218 additions & 228 deletions

File tree

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,25 @@
11
import dedent from 'dedent';
22
import { expect, test } from 'vite-plus/test';
3-
import { createIFF } from './test-util/fixture.js';
3+
import { setupFixture } from './test-util/fixture.js';
44
import { launchTsserver, normalizeDefinitions } from './test-util/tsserver.js';
55

6-
test('does not provide language features when cmkOptions.enabled is false', async () => {
7-
const tsserver = launchTsserver();
8-
const iff = await createIFF({
6+
const tsserver = launchTsserver();
7+
8+
test('returns no Go to Definition results when cmkOptions.enabled is false', async () => {
9+
const { iff, getLoc } = await setupFixture({
10+
'tsconfig.json': `{ "cmkOptions": { "enabled": false } }`,
911
'index.ts': dedent`
1012
import styles from './a.module.css';
1113
styles.a_1;
1214
`,
13-
'a.module.css': dedent`
14-
.a_1 { color: red; }
15-
`,
16-
'tsconfig.json': dedent`
17-
{ "cmkOptions": { "enabled": false } }
18-
`,
19-
});
20-
await tsserver.sendUpdateOpen({
21-
openFiles: [{ file: iff.paths['index.ts'] }],
15+
'a.module.css': `.a_1 { color: red; }`,
2216
});
17+
await tsserver.sendUpdateOpen({ openFiles: [{ file: iff.paths['index.ts'] }] });
18+
2319
const res = await tsserver.sendDefinitionAndBoundSpan({
2420
file: iff.paths['index.ts'],
25-
line: 2,
26-
offset: 8,
21+
...getLoc('index.ts', 'a_1'),
2722
});
23+
2824
expect(normalizeDefinitions(res.body?.definitions ?? [])).toStrictEqual([]);
2925
});
Lines changed: 63 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,83 @@
1-
import dedent from 'dedent';
21
import { describe, expect, test } from 'vite-plus/test';
32
import { createCssModuleFileRefactor } from '../../src/language-service/feature/refactor.js';
4-
import { createIFF } from '../test-util/fixture.js';
3+
import { buildTSConfigJSON } from '../../src/test/builder.js';
4+
import { setupFixture } from '../test-util/fixture.js';
55
import { launchTsserver } from '../test-util/tsserver.js';
66

7-
describe('Refactor', async () => {
8-
const tsserver = launchTsserver();
9-
const iff = await createIFF({
10-
'a.tsx': '',
11-
'b.ts': '',
12-
'tsconfig.json': dedent`
13-
{
14-
"compilerOptions": { "jsx": "react-jsx" },
15-
"cmkOptions": {
16-
"enabled": true,
17-
"dtsOutDir": "generated"
18-
}
19-
}
20-
`,
21-
});
22-
await tsserver.sendUpdateOpen({
23-
openFiles: [{ file: iff.paths['a.tsx'] }],
24-
});
25-
test.each([
26-
{
27-
name: 'a.tsx',
7+
const tsserver = launchTsserver();
8+
9+
describe('Get Applicable Refactors', () => {
10+
test('offers Create CSS Module file for a component file when no paired CSS module exists', async () => {
11+
const { iff } = await setupFixture({
12+
'tsconfig.json': buildTSConfigJSON({ compilerOptions: { jsx: 'react-jsx' } }),
13+
'a.tsx': '',
14+
});
15+
await tsserver.sendUpdateOpen({ openFiles: [{ file: iff.paths['a.tsx'] }] });
16+
17+
const res = await tsserver.sendGetApplicableRefactors({
2818
file: iff.paths['a.tsx'],
2919
line: 1,
3020
offset: 1,
31-
expected: [createCssModuleFileRefactor],
32-
},
33-
{
34-
name: 'b.ts',
35-
file: iff.paths['b.ts'],
21+
});
22+
23+
expect(res.body).toStrictEqual([createCssModuleFileRefactor]);
24+
});
25+
26+
test('omits Create CSS Module file for a non-component file', async () => {
27+
const { iff } = await setupFixture({
28+
'tsconfig.json': buildTSConfigJSON(),
29+
'a.ts': '',
30+
});
31+
await tsserver.sendUpdateOpen({ openFiles: [{ file: iff.paths['a.ts'] }] });
32+
33+
const res = await tsserver.sendGetApplicableRefactors({
34+
file: iff.paths['a.ts'],
3635
line: 1,
3736
offset: 1,
38-
expected: [],
39-
},
40-
])('Get Applicable Refactors for $name', async ({ file, line, offset, expected }) => {
41-
const res = await tsserver.sendGetApplicableRefactors({
42-
file,
43-
line,
44-
offset,
4537
});
46-
expect(res.body).toStrictEqual(expected);
38+
39+
expect(res.body).toStrictEqual([]);
4740
});
48-
test.each([
49-
{
50-
name: 'a.tsx',
41+
42+
test('omits Create CSS Module file when the paired CSS module already exists', async () => {
43+
const { iff } = await setupFixture({
44+
'tsconfig.json': buildTSConfigJSON({ compilerOptions: { jsx: 'react-jsx' } }),
45+
'a.tsx': '',
46+
'a.module.css': '',
47+
});
48+
await tsserver.sendUpdateOpen({ openFiles: [{ file: iff.paths['a.tsx'] }] });
49+
50+
const res = await tsserver.sendGetApplicableRefactors({
5151
file: iff.paths['a.tsx'],
5252
line: 1,
5353
offset: 1,
54-
expected: [
55-
{
56-
fileName: iff.join('a.module.css'),
57-
textChanges: [{ start: { line: 0, offset: 0 }, end: { line: 0, offset: 0 }, newText: '' }],
58-
},
59-
],
60-
},
61-
])('Get Edits For Refactor for $name', async ({ file, line, offset, expected }) => {
54+
});
55+
56+
expect(res.body).toStrictEqual([]);
57+
});
58+
});
59+
60+
describe('Get Edits For Refactor', () => {
61+
test('emits an edit that creates a new empty CSS module file paired with the component file', async () => {
62+
const { iff } = await setupFixture({
63+
'tsconfig.json': buildTSConfigJSON({ compilerOptions: { jsx: 'react-jsx' } }),
64+
'a.tsx': '',
65+
});
66+
await tsserver.sendUpdateOpen({ openFiles: [{ file: iff.paths['a.tsx'] }] });
67+
6268
const res = await tsserver.sendGetEditsForRefactor({
6369
refactor: createCssModuleFileRefactor.name,
6470
action: createCssModuleFileRefactor.actions[0].name,
65-
file,
66-
line,
67-
offset,
71+
file: iff.paths['a.tsx'],
72+
line: 1,
73+
offset: 1,
6874
});
69-
expect(res.body?.edits).toStrictEqual(expected);
75+
76+
expect(res.body?.edits).toStrictEqual([
77+
{
78+
fileName: iff.join('a.module.css'),
79+
textChanges: [{ start: { line: 0, offset: 0 }, end: { line: 0, offset: 0 }, newText: '' }],
80+
},
81+
]);
7082
});
7183
});
Lines changed: 59 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,73 @@
1-
import dedent from 'dedent';
21
import { describe, expect, test } from 'vite-plus/test';
3-
import { createIFF } from '../test-util/fixture.js';
2+
import { buildStylesImport, buildTSConfigJSON } from '../../src/test/builder.js';
3+
import { setupFixture } from '../test-util/fixture.js';
44
import { formatPath, launchTsserver } from '../test-util/tsserver.js';
55

6-
describe('Rename File', async () => {
7-
const tsserver = launchTsserver();
8-
const iff = await createIFF({
9-
'index.ts': dedent`
10-
import styles from './a.module.css';
11-
`,
12-
'a.module.css': dedent`
13-
@import './b.module.css';
14-
@value c_1 from './c.module.css';
15-
`,
16-
'b.module.css': dedent`
17-
.b_1 { color: red; }
18-
`,
19-
'c.module.css': dedent`
20-
@value c_1: red;
21-
`,
22-
'tsconfig.json': dedent`
23-
{
24-
"compilerOptions": {
25-
"paths": { "@/*": ["./*"] }
26-
},
27-
"cmkOptions": {
28-
"enabled": true,
29-
"dtsOutDir": "generated"
30-
}
31-
}
32-
`,
33-
});
34-
await tsserver.sendUpdateOpen({
35-
openFiles: [{ file: iff.paths['index.ts'] }],
36-
});
37-
test.each([
38-
{
39-
name: 'a.module.css',
40-
oldFilePath: iff.paths['a.module.css'],
41-
newFilePath: iff.join('aa.module.css'),
42-
expected: [
6+
const tsserver = launchTsserver();
7+
8+
describe.each([{ namedExports: false }, { namedExports: true }])('namedExports: $namedExports', ({ namedExports }) => {
9+
describe('rewrites the import specifier when a CSS module is renamed', () => {
10+
test('from `import ... from` in TS', async () => {
11+
const { iff, getRange } = await setupFixture({
12+
'tsconfig.json': buildTSConfigJSON({ cmkOptions: { namedExports } }),
13+
'index.ts': buildStylesImport('./a.module.css', { namedExports }),
14+
'a.module.css': '',
15+
});
16+
await tsserver.sendUpdateOpen({ openFiles: [{ file: iff.paths['index.ts'] }] });
17+
18+
const res = await tsserver.sendGetEditsForFileRename({
19+
oldFilePath: iff.paths['a.module.css'],
20+
newFilePath: iff.join('aa.module.css'),
21+
});
22+
23+
expect(res.body).toStrictEqual([
4324
{
4425
fileName: formatPath(iff.paths['index.ts']),
45-
textChanges: [{ start: { line: 1, offset: 21 }, end: { line: 1, offset: 35 }, newText: './aa.module.css' }],
26+
textChanges: [{ ...getRange('index.ts', './a.module.css'), newText: './aa.module.css' }],
4627
},
47-
],
48-
},
49-
{
50-
name: 'b.module.css',
51-
oldFilePath: iff.paths['b.module.css'],
52-
newFilePath: iff.join('bb.module.css'),
53-
expected: [
28+
]);
29+
});
30+
31+
test('from `@import` in CSS', async () => {
32+
const { iff, getRange } = await setupFixture({
33+
'tsconfig.json': buildTSConfigJSON({ cmkOptions: { namedExports } }),
34+
'a.module.css': `@import './b.module.css';`,
35+
'b.module.css': '',
36+
});
37+
await tsserver.sendUpdateOpen({ openFiles: [{ file: iff.paths['a.module.css'] }] });
38+
39+
const res = await tsserver.sendGetEditsForFileRename({
40+
oldFilePath: iff.paths['b.module.css'],
41+
newFilePath: iff.join('bb.module.css'),
42+
});
43+
44+
expect(res.body).toStrictEqual([
5445
{
5546
fileName: formatPath(iff.paths['a.module.css']),
56-
textChanges: [{ start: { line: 1, offset: 10 }, end: { line: 1, offset: 24 }, newText: './bb.module.css' }],
47+
textChanges: [{ ...getRange('a.module.css', './b.module.css'), newText: './bb.module.css' }],
5748
},
58-
],
59-
},
60-
{
61-
name: 'c.module.css',
62-
oldFilePath: iff.paths['c.module.css'],
63-
newFilePath: iff.join('cc.module.css'),
64-
expected: [
49+
]);
50+
});
51+
52+
test('from `@value ... from` in CSS', async () => {
53+
const { iff, getRange } = await setupFixture({
54+
'tsconfig.json': buildTSConfigJSON({ cmkOptions: { namedExports } }),
55+
'a.module.css': `@value b_1 from './b.module.css';`,
56+
'b.module.css': `@value b_1: red;`,
57+
});
58+
await tsserver.sendUpdateOpen({ openFiles: [{ file: iff.paths['a.module.css'] }] });
59+
60+
const res = await tsserver.sendGetEditsForFileRename({
61+
oldFilePath: iff.paths['b.module.css'],
62+
newFilePath: iff.join('bb.module.css'),
63+
});
64+
65+
expect(res.body).toStrictEqual([
6566
{
6667
fileName: formatPath(iff.paths['a.module.css']),
67-
textChanges: [{ start: { line: 2, offset: 18 }, end: { line: 2, offset: 32 }, newText: './cc.module.css' }],
68+
textChanges: [{ ...getRange('a.module.css', './b.module.css'), newText: './bb.module.css' }],
6869
},
69-
],
70-
},
71-
])('for $name', async ({ oldFilePath, newFilePath, expected }) => {
72-
const res = await tsserver.sendGetEditsForFileRename({ oldFilePath, newFilePath });
73-
expect(res.body).toStrictEqual(expected);
70+
]);
71+
});
7472
});
7573
});
Lines changed: 24 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,36 @@
11
import dedent from 'dedent';
22
import { expect, test } from 'vite-plus/test';
3-
import { createIFF } from './test-util/fixture.js';
3+
import { setupFixture } from './test-util/fixture.js';
44
import { launchTsserver } from './test-util/tsserver.js';
55

6-
test('report the import of files where .d.ts exists but .module.css does not exist', async () => {
7-
const tsserver = launchTsserver();
8-
const iff = await createIFF({
9-
// `a.module.css` is not exist, and the .d.ts file is exist.
10-
// But `'./a.module.css'` should report an error.
11-
'index.ts': dedent`
12-
import styles from './a.module.css';
13-
`,
14-
'generated/a.module.css.d.ts': dedent`
15-
const styles: {};
16-
export default styles;
17-
`,
6+
const tsserver = launchTsserver();
7+
8+
test('excludes generated .d.ts files from module resolution even when listed in rootDirs', async () => {
9+
const { iff, getRange } = await setupFixture({
1810
'tsconfig.json': dedent`
1911
{
2012
"compilerOptions": {
21-
"rootDirs": [".", "generated"],
13+
"rootDirs": [".", "generated"]
2214
},
23-
"cmkOptions": {
24-
"enabled": true
25-
}
15+
"cmkOptions": { "enabled": true }
2616
}
2717
`,
18+
'index.ts': `import styles from './a.module.css';`,
19+
'generated/a.module.css.d.ts': dedent`
20+
const styles: {};
21+
export default styles;
22+
`,
2823
});
29-
await tsserver.sendUpdateOpen({
30-
openFiles: [{ file: iff.paths['index.ts'] }],
31-
});
32-
const res = await tsserver.sendSemanticDiagnosticsSync({
33-
file: iff.paths['index.ts'],
34-
});
35-
expect(res.body).toMatchInlineSnapshot(`
36-
[
37-
{
38-
"category": "error",
39-
"code": 2307,
40-
"end": {
41-
"line": 1,
42-
"offset": 36,
43-
},
44-
"start": {
45-
"line": 1,
46-
"offset": 20,
47-
},
48-
"text": "Cannot find module './a.module.css' or its corresponding type declarations.",
49-
},
50-
]
51-
`);
24+
await tsserver.sendUpdateOpen({ openFiles: [{ file: iff.paths['index.ts'] }] });
25+
26+
const res = await tsserver.sendSemanticDiagnosticsSync({ file: iff.paths['index.ts'] });
27+
28+
expect(res.body).toStrictEqual([
29+
{
30+
category: 'error',
31+
code: 2307,
32+
...getRange('index.ts', `'./a.module.css'`),
33+
text: `Cannot find module './a.module.css' or its corresponding type declarations.`,
34+
},
35+
]);
5236
});

0 commit comments

Comments
 (0)