• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts.tscWatch {
2    describe("unittests:: tsc-watch:: forceConsistentCasingInFileNames", () => {
3        const loggerFile: File = {
4            path: `${projectRoot}/logger.ts`,
5            content: `export class logger { }`
6        };
7        const anotherFile: File = {
8            path: `${projectRoot}/another.ts`,
9            content: `import { logger } from "./logger"; new logger();`
10        };
11        const tsconfig: File = {
12            path: `${projectRoot}/tsconfig.json`,
13            content: JSON.stringify({
14                compilerOptions: { forceConsistentCasingInFileNames: true }
15            })
16        };
17
18        function verifyConsistentFileNames({ subScenario, changes }: { subScenario: string; changes: TscWatchCompileChange[]; }) {
19            verifyTscWatch({
20                scenario: "forceConsistentCasingInFileNames",
21                subScenario,
22                commandLineArgs: ["--w", "--p", tsconfig.path],
23                sys: () => createWatchedSystem([loggerFile, anotherFile, tsconfig, libFile, tsconfig]),
24                changes
25            });
26        }
27
28        verifyConsistentFileNames({
29            subScenario: "when changing module name with different casing",
30            changes: [
31                {
32                    caption: "Change module name from logger to Logger",
33                    change: sys => sys.writeFile(anotherFile.path, anotherFile.content.replace("./logger", "./Logger")),
34                    timeouts: runQueuedTimeoutCallbacks,
35                }
36            ]
37        });
38
39        verifyConsistentFileNames({
40            subScenario: "when renaming file with different casing",
41            changes: [
42                {
43                    caption: "Change name of file from logger to Logger",
44                    change: sys => sys.renameFile(loggerFile.path, `${projectRoot}/Logger.ts`),
45                    timeouts: runQueuedTimeoutCallbacks,
46                }
47            ]
48        });
49
50        verifyTscWatch({
51            scenario: "forceConsistentCasingInFileNames",
52            subScenario: "when relative information file location changes",
53            commandLineArgs: ["--w", "--p", ".", "--explainFiles"],
54            sys: () => {
55                const moduleA: File = {
56                    path: `${projectRoot}/moduleA.ts`,
57                    content: `import a = require("./ModuleC")`
58                };
59                const moduleB: File = {
60                    path: `${projectRoot}/moduleB.ts`,
61                    content: `import a = require("./moduleC")`
62                };
63                const moduleC: File = {
64                    path: `${projectRoot}/moduleC.ts`,
65                    content: `export const x = 10;`
66                };
67                const tsconfig: File = {
68                    path: `${projectRoot}/tsconfig.json`,
69                    content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } })
70                };
71                return createWatchedSystem([moduleA, moduleB, moduleC, libFile, tsconfig], { currentDirectory: projectRoot });
72            },
73            changes: [
74                {
75                    caption: "Prepend a line to moduleA",
76                    change: sys => sys.prependFile(`${projectRoot}/moduleA.ts`, `// some comment
77                    `),
78                    timeouts: runQueuedTimeoutCallbacks,
79                }
80            ],
81        });
82
83        verifyTscWatch({
84            scenario: "forceConsistentCasingInFileNames",
85            subScenario: "jsxImportSource option changed",
86            commandLineArgs: ["--w", "--p", ".", "--explainFiles"],
87            sys: () => createWatchedSystem([
88                libFile,
89                {
90                    path: `${projectRoot}/node_modules/react/Jsx-runtime/index.d.ts`,
91                    content: `export namespace JSX {
92    interface Element {}
93    interface IntrinsicElements {
94        div: {
95            propA?: boolean;
96        };
97    }
98}
99export function jsx(...args: any[]): void;
100export function jsxs(...args: any[]): void;
101export const Fragment: unique symbol;
102`,
103                },
104                {
105                    path: `${projectRoot}/node_modules/react/package.json`,
106                    content: JSON.stringify({ name: "react", version: "0.0.1" })
107                },
108                {
109                    path: `${projectRoot}/index.tsx`,
110                    content: `export const App = () => <div propA={true}></div>;`
111                },
112                {
113                    path: `${projectRoot}/tsconfig.json`,
114                    content: JSON.stringify({
115                        compilerOptions: { jsx: "react-jsx", jsxImportSource: "react", forceConsistentCasingInFileNames: true },
116                        files: ["node_modules/react/jsx-Runtime/index.d.ts", "index.tsx"] // NB: casing does not match disk
117                    })
118                }
119            ], { currentDirectory: projectRoot }),
120            changes: emptyArray,
121        });
122
123        function verifyWindowsStyleRoot(subScenario: string, windowsStyleRoot: string, projectRootRelative: string) {
124            verifyTscWatch({
125                scenario: "forceConsistentCasingInFileNames",
126                subScenario,
127                commandLineArgs: ["--w", "--p", `${windowsStyleRoot}/${projectRootRelative}`, "--explainFiles"],
128                sys: () => {
129                    const moduleA: File = {
130                        path: `${windowsStyleRoot}/${projectRootRelative}/a.ts`,
131                        content: `
132export const a = 1;
133export const b = 2;
134`
135                    };
136                    const moduleB: File = {
137                        path: `${windowsStyleRoot}/${projectRootRelative}/b.ts`,
138                        content: `
139import { a } from "${windowsStyleRoot.toLocaleUpperCase()}/${projectRootRelative}/a"
140import { b } from "${windowsStyleRoot.toLocaleLowerCase()}/${projectRootRelative}/a"
141
142a;b;
143`
144                    };
145                    const tsconfig: File = {
146                        path: `${windowsStyleRoot}/${projectRootRelative}/tsconfig.json`,
147                        content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } })
148                    };
149                    return createWatchedSystem([moduleA, moduleB, libFile, tsconfig], { windowsStyleRoot, useCaseSensitiveFileNames: false });
150                },
151                changes: [
152                    {
153                        caption: "Prepend a line to moduleA",
154                        change: sys => sys.prependFile(`${windowsStyleRoot}/${projectRootRelative}/a.ts`, `// some comment
155                        `),
156                        timeouts: runQueuedTimeoutCallbacks,
157                    }
158                ],
159            });
160        }
161
162        verifyWindowsStyleRoot("when Windows-style drive root is lowercase", "c:/", "project");
163        verifyWindowsStyleRoot("when Windows-style drive root is uppercase", "C:/", "project");
164
165        function verifyFileSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) {
166            verifyTscWatch({
167                scenario: "forceConsistentCasingInFileNames",
168                subScenario,
169                commandLineArgs: ["--w", "--p", ".", "--explainFiles"],
170                sys: () => {
171                    const moduleA: File = {
172
173                        path: diskPath,
174                        content: `
175export const a = 1;
176export const b = 2;
177`
178                    };
179                    const symlinkA: SymLink = {
180                        path: `${projectRoot}/link.ts`,
181                        symLink: targetPath,
182                    };
183                    const moduleB: File = {
184                        path: `${projectRoot}/b.ts`,
185                        content: `
186import { a } from "${importedPath}";
187import { b } from "./link";
188
189a;b;
190`
191                    };
192                    const tsconfig: File = {
193                        path: `${projectRoot}/tsconfig.json`,
194                        content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true } })
195                    };
196                    return createWatchedSystem([moduleA, symlinkA, moduleB, libFile, tsconfig], { currentDirectory: projectRoot });
197                },
198                changes: [
199                    {
200                        caption: "Prepend a line to moduleA",
201                        change: sys => sys.prependFile(diskPath, `// some comment
202                        `),
203                        timeouts: runQueuedTimeoutCallbacks,
204                    }
205                ],
206            });
207        }
208
209        verifyFileSymlink("when both file symlink target and import match disk", `${projectRoot}/XY.ts`, `${projectRoot}/XY.ts`, `./XY`);
210        verifyFileSymlink("when file symlink target matches disk but import does not", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./XY`);
211        verifyFileSymlink("when import matches disk but file symlink target does not", `${projectRoot}/XY.ts`, `${projectRoot}/XY.ts`, `./Xy`);
212        verifyFileSymlink("when import and file symlink target agree but do not match disk", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./Xy`);
213        verifyFileSymlink("when import, file symlink target, and disk are all different", `${projectRoot}/XY.ts`, `${projectRoot}/Xy.ts`, `./yX`);
214
215        function verifyDirSymlink(subScenario: string, diskPath: string, targetPath: string, importedPath: string) {
216            verifyTscWatch({
217                scenario: "forceConsistentCasingInFileNames",
218                subScenario,
219                commandLineArgs: ["--w", "--p", ".", "--explainFiles"],
220                sys: () => {
221                    const moduleA: File = {
222
223                        path: `${diskPath}/a.ts`,
224                        content: `
225export const a = 1;
226export const b = 2;
227`
228                    };
229                    const symlinkA: SymLink = {
230                        path: `${projectRoot}/link`,
231                        symLink: targetPath,
232                    };
233                    const moduleB: File = {
234                        path: `${projectRoot}/b.ts`,
235                        content: `
236import { a } from "${importedPath}/a";
237import { b } from "./link/a";
238
239a;b;
240`
241                    };
242                    const tsconfig: File = {
243                        path: `${projectRoot}/tsconfig.json`,
244                        // Use outFile because otherwise the real and linked files will have the same output path
245                        content: JSON.stringify({ compilerOptions: { forceConsistentCasingInFileNames: true, outFile: "out.js", module: "system" } })
246                    };
247                    return createWatchedSystem([moduleA, symlinkA, moduleB, libFile, tsconfig], { currentDirectory: projectRoot });
248                },
249                changes: [
250                    {
251                        caption: "Prepend a line to moduleA",
252                        change: sys => sys.prependFile(`${diskPath}/a.ts`, `// some comment
253                        `),
254                        timeouts: runQueuedTimeoutCallbacks,
255                    }
256                ],
257            });
258        }
259
260        verifyDirSymlink("when both directory symlink target and import match disk", `${projectRoot}/XY`, `${projectRoot}/XY`, `./XY`);
261        verifyDirSymlink("when directory symlink target matches disk but import does not", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./XY`);
262        verifyDirSymlink("when import matches disk but directory symlink target does not", `${projectRoot}/XY`, `${projectRoot}/XY`, `./Xy`);
263        verifyDirSymlink("when import and directory symlink target agree but do not match disk", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./Xy`);
264        verifyDirSymlink("when import, directory symlink target, and disk are all different", `${projectRoot}/XY`, `${projectRoot}/Xy`, `./yX`);
265    });
266}
267