Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
317 changes: 222 additions & 95 deletions packages/ts-plugin/e2e-test/feature/code-fix.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,184 @@ import {
import { createIFF } from '../test-util/fixture.js';
import { formatPath, launchTsserver, normalizeCodeFixActions } from '../test-util/tsserver.js';

describe('Get Code Fixes', async () => {
test.each([
{
namedExports: false,
importStatement: "import styles from './a.module.css';",
},
{
namedExports: true,
importStatement: "import * as styles from './a.module.css';",
},
])(
'fixMissingCSSRule inserts a new CSS rule for a missing class property (namedExports: $namedExports)',
async ({ namedExports, importStatement }) => {
const tsserver = launchTsserver();
const iff = await createIFF({
'a.tsx': dedent`
${importStatement}
import bStyles from './b.module.css';
styles.a_1;
bStyles.b_2;
`,
'a.module.css': '',
'b.module.css': dedent`
.b_1 {
color: red;
}
`,
'tsconfig.json': dedent`
{
"cmkOptions": {
"enabled": true,
"namedExports": ${namedExports}
}
}
`,
});
await tsserver.sendUpdateOpen({
openFiles: [{ file: iff.paths['tsconfig.json'] }],
});

const res1 = await tsserver.sendGetCodeFixes({
errorCodes: [PROPERTY_DOES_NOT_EXIST_ERROR_CODES[0]],
file: iff.paths['a.tsx'],
startLine: 3,
startOffset: 11,
endLine: 3,
endOffset: 11,
});
expect(normalizeCodeFixActions(res1.body!)).toStrictEqual(
normalizeCodeFixActions([
{
fixName: 'fixMissingCSSRule',
changes: [
{
fileName: formatPath(iff.paths['a.module.css']),
textChanges: [{ start: { line: 1, offset: 1 }, end: { line: 1, offset: 1 }, newText: '\n.a_1 {\n \n}' }],
},
],
},
]),
);

const res2 = await tsserver.sendGetCodeFixes({
errorCodes: [PROPERTY_DOES_NOT_EXIST_ERROR_CODES[1]],
file: iff.paths['a.tsx'],
startLine: 3,
startOffset: 11,
endLine: 3,
endOffset: 11,
});
expect(normalizeCodeFixActions(res2.body!)).toStrictEqual(
normalizeCodeFixActions([
{
fixName: 'fixMissingCSSRule',
changes: [
{
fileName: formatPath(iff.paths['a.module.css']),
textChanges: [{ start: { line: 1, offset: 1 }, end: { line: 1, offset: 1 }, newText: '\n.a_1 {\n \n}' }],
},
],
},
]),
);

const res3 = await tsserver.sendGetCodeFixes({
errorCodes: [PROPERTY_DOES_NOT_EXIST_ERROR_CODES[0]],
file: iff.paths['a.tsx'],
startLine: 4,
startOffset: 12,
endLine: 4,
endOffset: 12,
});
expect(normalizeCodeFixActions(res3.body!)).toStrictEqual(
normalizeCodeFixActions([
{
fixName: 'fixMissingCSSRule',
changes: [
{
fileName: formatPath(iff.paths['b.module.css']),
textChanges: [{ start: { line: 3, offset: 2 }, end: { line: 3, offset: 2 }, newText: '\n.b_2 {\n \n}' }],
},
],
},
]),
);
},
);

test.each([
{
name: 'auto-import inserts default import statement if namedExports is false',
namedExports: false,
importStatement: `import styles from "./a.module.css";`,
},
{
name: 'auto-import inserts namespace import statement if namedExports is true',
namedExports: true,
importStatement: `import * as styles from "./a.module.css";`,
},
])('$name', async ({ namedExports, importStatement }) => {
const tsserver = launchTsserver();
const iff = await createIFF({
'a.tsx': dedent`
import styles from './a.module.css';
import bStyles from './b.module.css';
styles.a_1;
bStyles.b_2;
`,
'b.tsx': dedent`
'index.ts': dedent`
styles;
`,
'a.module.css': '',
'b.module.css': dedent`
.b_1 {
color: red;
'tsconfig.json': dedent`
{
"cmkOptions": {
"enabled": true,
"namedExports": ${namedExports}
}
}
`,
// Generated files should be excluded from completion candidates.
'generated/generated.module.css.d.ts': dedent`
});
await tsserver.sendUpdateOpen({
openFiles: [{ file: iff.paths['tsconfig.json'] }],
});
const res = await tsserver.sendGetCodeFixes({
errorCodes: [CANNOT_FIND_NAME_ERROR_CODE],
file: iff.paths['index.ts'],
startLine: 1,
startOffset: 1,
endLine: 1,
endOffset: 7,
});
expect(normalizeCodeFixActions(res.body!)).toStrictEqual(
normalizeCodeFixActions([
{
fixName: 'import',
changes: [
{
fileName: formatPath(iff.paths['index.ts']),
textChanges: [
{
start: { line: 1, offset: 1 },
end: { line: 1, offset: 1 },
newText: `${importStatement}${ts.sys.newLine}${ts.sys.newLine}`,
},
],
},
],
},
]),
);
});

test('auto-import excludes generated files from suggestions', async () => {
const tsserver = launchTsserver();
const iff = await createIFF({
'index.ts': dedent`
styles;
`,
'generated/a.module.css.d.ts': dedent`
const styles: {};
export default styles;
`,
'tsconfig.json': dedent`
{
"compilerOptions": {},
"cmkOptions": {
"enabled": true,
"dtsOutDir": "generated"
Expand All @@ -44,108 +196,83 @@ describe('Get Code Fixes', async () => {
await tsserver.sendUpdateOpen({
openFiles: [{ file: iff.paths['tsconfig.json'] }],
});
const res = await tsserver.sendGetCodeFixes({
errorCodes: [CANNOT_FIND_NAME_ERROR_CODE],
file: iff.paths['index.ts'],
startLine: 1,
startOffset: 1,
endLine: 1,
endOffset: 7,
});
expect(normalizeCodeFixActions(res.body!)).toStrictEqual([]);
});

describe('auto-import suggests named exports instead of namespace import when prioritizeNamedImports is true', async () => {
const tsserver = launchTsserver();
const iff = await createIFF({
'index.ts': dedent`
styles;
a_1;
`,
'a.module.css': dedent`
.a_1 { color: red; }
`,
'tsconfig.json': dedent`
{
"cmkOptions": {
"enabled": true,
"namedExports": true,
"prioritizeNamedImports": true
}
}
`,
});
await tsserver.sendUpdateOpen({
openFiles: [{ file: iff.paths['tsconfig.json'] }],
});
test.each([
{
name: 'styles',
file: iff.paths['b.tsx'],
line: 1,
offset: 1,
errorCodes: [CANNOT_FIND_NAME_ERROR_CODE],
file: iff.paths['index.ts'],
startLine: 1,
startOffset: 1,
endLine: 1,
endOffset: 7,
expected: [],
},
{
name: 'a_1',
file: iff.paths['index.ts'],
startLine: 2,
startOffset: 1,
endLine: 2,
endOffset: 4,
expected: [
{
fixName: 'import',
changes: [
{
fileName: formatPath(iff.paths['b.tsx']),
textChanges: [
{
start: { line: 1, offset: 1 },
end: { line: 1, offset: 1 },
newText: `import styles from "./a.module.css";${ts.sys.newLine}${ts.sys.newLine}`,
},
],
},
],
},
{
fixName: 'import',
changes: [
{
fileName: formatPath(iff.paths['b.tsx']),
fileName: formatPath(iff.paths['index.ts']),
textChanges: [
{
start: { line: 1, offset: 1 },
end: { line: 1, offset: 1 },
newText: `import styles from "./b.module.css";${ts.sys.newLine}${ts.sys.newLine}`,
newText: `import { a_1 } from "./a.module.css";${ts.sys.newLine}${ts.sys.newLine}`,
},
],
},
],
},
],
},
{
name: 'styles.a_1',
file: iff.paths['a.tsx'],
line: 3,
offset: 11,
errorCodes: [PROPERTY_DOES_NOT_EXIST_ERROR_CODES[0]],
expected: [
{
fixName: 'fixMissingCSSRule',
changes: [
{
fileName: formatPath(iff.paths['a.module.css']),
textChanges: [{ start: { line: 1, offset: 1 }, end: { line: 1, offset: 1 }, newText: '\n.a_1 {\n \n}' }],
},
],
},
],
},
{
name: 'styles.a_1',
file: iff.paths['a.tsx'],
line: 3,
offset: 11,
errorCodes: [PROPERTY_DOES_NOT_EXIST_ERROR_CODES[1]],
expected: [
{
fixName: 'fixMissingCSSRule',
changes: [
{
fileName: formatPath(iff.paths['a.module.css']),
textChanges: [{ start: { line: 1, offset: 1 }, end: { line: 1, offset: 1 }, newText: '\n.a_1 {\n \n}' }],
},
],
},
],
},
{
name: 'bStyles.b_2',
file: iff.paths['a.tsx'],
line: 4,
offset: 12,
errorCodes: [PROPERTY_DOES_NOT_EXIST_ERROR_CODES[0]],
expected: [
{
fixName: 'fixMissingCSSRule',
changes: [
{
fileName: formatPath(iff.paths['b.module.css']),
textChanges: [{ start: { line: 3, offset: 2 }, end: { line: 3, offset: 2 }, newText: '\n.b_2 {\n \n}' }],
},
],
},
],
},
])('$name', async ({ file, line, offset, errorCodes, expected }) => {
])('$name', async ({ file, startLine, startOffset, endLine, endOffset, expected }) => {
const res = await tsserver.sendGetCodeFixes({
errorCodes,
errorCodes: [2304],
file,
startLine: line,
startOffset: offset,
endLine: line,
endOffset: offset,
startLine,
startOffset,
endLine,
endOffset,
});
expect(normalizeCodeFixActions(res.body!)).toStrictEqual(normalizeCodeFixActions(expected));
});
Expand Down
Loading
Loading