@@ -9,19 +9,18 @@ import { SassProcessor } from '../SassProcessor';
99
1010const projectFolder : string = PackageJsonLookup . instance . tryGetPackageFolderFor ( __dirname ) ! ;
1111const fixturesFolder : string = `${ projectFolder } /src/test/fixtures` ;
12- const testOutputFolder : string = `${ projectFolder } /temp/test-output` ;
1312
14- function createProcessor ( preserveIcssExports : boolean ) : {
13+ // Fake output folder paths — never actually written to disk because FileSystem.writeFileAsync is mocked.
14+ const CSS_OUTPUT_FOLDER : string = '/fake/output/css' ;
15+ const DTS_OUTPUT_FOLDER : string = '/fake/output/dts' ;
16+
17+ function createProcessor (
18+ terminalProvider : StringBufferTerminalProvider ,
19+ preserveIcssExports : boolean
20+ ) : {
1521 processor : SassProcessor ;
16- dtsOutputFolder : string ;
17- cssOutputFolder : string ;
1822 logger : MockScopedLogger ;
1923} {
20- const suffix : string = preserveIcssExports ? 'preserve' : 'strip' ;
21- const dtsOutputFolder : string = `${ testOutputFolder } /${ suffix } /dts` ;
22- const cssOutputFolder : string = `${ testOutputFolder } /${ suffix } /css` ;
23-
24- const terminalProvider : StringBufferTerminalProvider = new StringBufferTerminalProvider ( false ) ;
2524 const terminal : Terminal = new Terminal ( terminalProvider ) ;
2625 const logger : MockScopedLogger = new MockScopedLogger ( terminal ) ;
2726
@@ -30,91 +29,115 @@ function createProcessor(preserveIcssExports: boolean): {
3029 buildFolder : projectFolder ,
3130 concurrency : 1 ,
3231 srcFolder : fixturesFolder ,
33- dtsOutputFolders : [ dtsOutputFolder ] ,
34- cssOutputFolders : [ { folder : cssOutputFolder , shimModuleFormat : undefined } ] ,
32+ dtsOutputFolders : [ DTS_OUTPUT_FOLDER ] ,
33+ cssOutputFolders : [ { folder : CSS_OUTPUT_FOLDER , shimModuleFormat : undefined } ] ,
3534 exportAsDefault : true ,
3635 preserveIcssExports
3736 } ) ;
3837
39- return { processor, dtsOutputFolder , cssOutputFolder , logger } ;
38+ return { processor, logger } ;
4039}
4140
4241async function compileFixtureAsync ( processor : SassProcessor , fixtureFilename : string ) : Promise < void > {
43- const absolutePath : string = `${ fixturesFolder } /${ fixtureFilename } ` ;
44- await processor . compileFilesAsync ( new Set ( [ absolutePath ] ) ) ;
42+ await processor . compileFilesAsync ( new Set ( [ `${ fixturesFolder } /${ fixtureFilename } ` ] ) ) ;
4543}
4644
47- async function readCssOutputAsync ( cssOutputFolder : string , fixtureFilename : string ) : Promise < string > {
48- // Strip last extension (.scss/.sass), append .css
49- const withoutExt : string = fixtureFilename . slice ( 0 , fixtureFilename . lastIndexOf ( '.' ) ) ;
50- return await FileSystem . readFileAsync ( `${ cssOutputFolder } /${ withoutExt } .css` ) ;
51- }
45+ describe ( SassProcessor . name , ( ) => {
46+ let terminalProvider : StringBufferTerminalProvider ;
47+ /** Files captured by the mocked FileSystem.writeFileAsync, keyed by absolute path. */
48+ let writtenFiles : Map < string , string > ;
49+
50+ /** Returns the content written to a path whose last segment matches the given filename. */
51+ function getWrittenFile ( filename : string ) : string {
52+ for ( const [ filePath , content ] of writtenFiles ) {
53+ if ( filePath . endsWith ( `/${ filename } ` ) ) {
54+ return content ;
55+ }
56+ }
5257
53- async function readDtsOutputAsync ( dtsOutputFolder : string , fixtureFilename : string ) : Promise < string > {
54- return await FileSystem . readFileAsync ( `${ dtsOutputFolder } /${ fixtureFilename } .d.ts` ) ;
55- }
58+ throw new Error (
59+ `No file written matching ".../${ filename } ". Written paths:\n${ [ ...writtenFiles . keys ( ) ] . join ( '\n' ) } `
60+ ) ;
61+ }
5662
57- describe ( SassProcessor . name , ( ) => {
58- beforeEach ( async ( ) => {
59- await FileSystem . ensureEmptyFolderAsync ( testOutputFolder ) ;
63+ function getCssOutput ( fixtureFilename : string ) : string {
64+ // SassProcessor strips the last extension then appends .css
65+ // export-only.module.scss → export-only.module.css
66+ const withoutExt : string = fixtureFilename . slice ( 0 , fixtureFilename . lastIndexOf ( '.' ) ) ;
67+ return getWrittenFile ( `${ withoutExt } .css` ) ;
68+ }
69+
70+ function getDtsOutput ( fixtureFilename : string ) : string {
71+ return getWrittenFile ( `${ fixtureFilename } .d.ts` ) ;
72+ }
73+
74+ beforeEach ( ( ) => {
75+ terminalProvider = new StringBufferTerminalProvider ( ) ;
76+
77+ writtenFiles = new Map ( ) ;
78+ jest . spyOn ( FileSystem , 'writeFileAsync' ) . mockImplementation ( async ( filePath , content ) => {
79+ writtenFiles . set ( filePath as string , content as string ) ;
80+ } ) ;
81+ } ) ;
82+
83+ afterEach ( ( ) => {
84+ jest . restoreAllMocks ( ) ;
85+
86+ expect ( writtenFiles ) . toMatchSnapshot ( 'written-files' ) ;
87+ expect ( terminalProvider . getAllOutputAsChunks ( { asLines : true } ) ) . toMatchSnapshot ( 'terminal-output' ) ;
6088 } ) ;
6189
6290 describe ( 'export-only.module.scss' , ( ) => {
6391 it ( 'strips the :export block from CSS when preserveIcssExports is false' , async ( ) => {
64- const { processor, cssOutputFolder } = createProcessor ( false ) ;
92+ const { processor } = createProcessor ( terminalProvider , false ) ;
6593 await compileFixtureAsync ( processor , 'export-only.module.scss' ) ;
66- const css : string = await readCssOutputAsync ( cssOutputFolder , 'export-only.module.scss' ) ;
67- expect ( css ) . toMatchSnapshot ( ) ;
94+ const css : string = getCssOutput ( 'export-only.module.scss' ) ;
6895 expect ( css ) . not . toContain ( ':export' ) ;
6996 } ) ;
7097
7198 it ( 'preserves the :export block in CSS when preserveIcssExports is true' , async ( ) => {
72- const { processor, cssOutputFolder } = createProcessor ( true ) ;
99+ const { processor } = createProcessor ( terminalProvider , true ) ;
73100 await compileFixtureAsync ( processor , 'export-only.module.scss' ) ;
74- const css : string = await readCssOutputAsync ( cssOutputFolder , 'export-only.module.scss' ) ;
75- expect ( css ) . toMatchSnapshot ( ) ;
101+ const css : string = getCssOutput ( 'export-only.module.scss' ) ;
76102 expect ( css ) . toContain ( ':export' ) ;
77103 } ) ;
78104
79105 it ( 'generates the same .d.ts regardless of preserveIcssExports' , async ( ) => {
80- const { processor : processorFalse , dtsOutputFolder : dtsFalseFolder } = createProcessor ( false ) ;
81- const { processor : processorTrue , dtsOutputFolder : dtsTrueFolder } = createProcessor ( true ) ;
82-
106+ const { processor : processorFalse } = createProcessor ( terminalProvider , false ) ;
83107 await compileFixtureAsync ( processorFalse , 'export-only.module.scss' ) ;
84- await compileFixtureAsync ( processorTrue , 'export-only.module.scss' ) ;
108+ const dtsFalse : string = getDtsOutput ( 'export-only.module.scss' ) ;
109+
110+ writtenFiles . clear ( ) ;
85111
86- const dtsFalse : string = await readDtsOutputAsync ( dtsFalseFolder , 'export-only.module.scss' ) ;
87- const dtsTrue : string = await readDtsOutputAsync ( dtsTrueFolder , 'export-only.module.scss' ) ;
112+ const { processor : processorTrue } = createProcessor ( terminalProvider , true ) ;
113+ await compileFixtureAsync ( processorTrue , 'export-only.module.scss' ) ;
114+ const dtsTrue : string = getDtsOutput ( 'export-only.module.scss' ) ;
88115
89- expect ( dtsFalse ) . toMatchSnapshot ( ) ;
90116 expect ( dtsFalse ) . toEqual ( dtsTrue ) ;
91117 } ) ;
92118 } ) ;
93119
94120 describe ( 'classes-and-exports.module.scss' , ( ) => {
95121 it ( 'strips the :export block from CSS when preserveIcssExports is false' , async ( ) => {
96- const { processor, cssOutputFolder } = createProcessor ( false ) ;
122+ const { processor } = createProcessor ( terminalProvider , false ) ;
97123 await compileFixtureAsync ( processor , 'classes-and-exports.module.scss' ) ;
98- const css : string = await readCssOutputAsync ( cssOutputFolder , 'classes-and-exports.module.scss' ) ;
99- expect ( css ) . toMatchSnapshot ( ) ;
124+ const css : string = getCssOutput ( 'classes-and-exports.module.scss' ) ;
100125 expect ( css ) . not . toContain ( ':export' ) ;
101126 expect ( css ) . toContain ( '.root' ) ;
102127 } ) ;
103128
104129 it ( 'preserves the :export block in CSS when preserveIcssExports is true' , async ( ) => {
105- const { processor, cssOutputFolder } = createProcessor ( true ) ;
130+ const { processor } = createProcessor ( terminalProvider , true ) ;
106131 await compileFixtureAsync ( processor , 'classes-and-exports.module.scss' ) ;
107- const css : string = await readCssOutputAsync ( cssOutputFolder , 'classes-and-exports.module.scss' ) ;
108- expect ( css ) . toMatchSnapshot ( ) ;
132+ const css : string = getCssOutput ( 'classes-and-exports.module.scss' ) ;
109133 expect ( css ) . toContain ( ':export' ) ;
110134 expect ( css ) . toContain ( '.root' ) ;
111135 } ) ;
112136
113137 it ( 'generates correct .d.ts with both class names and :export values' , async ( ) => {
114- const { processor, dtsOutputFolder } = createProcessor ( false ) ;
138+ const { processor } = createProcessor ( terminalProvider , false ) ;
115139 await compileFixtureAsync ( processor , 'classes-and-exports.module.scss' ) ;
116- const dts : string = await readDtsOutputAsync ( dtsOutputFolder , 'classes-and-exports.module.scss' ) ;
117- expect ( dts ) . toMatchSnapshot ( ) ;
140+ const dts : string = getDtsOutput ( 'classes-and-exports.module.scss' ) ;
118141 expect ( dts ) . toContain ( 'root' ) ;
119142 expect ( dts ) . toContain ( 'highlighted' ) ;
120143 expect ( dts ) . toContain ( 'themeColor' ) ;
@@ -124,10 +147,9 @@ describe(SassProcessor.name, () => {
124147
125148 describe ( 'sass-variables-and-exports.module.scss (Sass variables, nesting, BEM)' , ( ) => {
126149 it ( 'resolves Sass variables and expands nested rules in CSS output' , async ( ) => {
127- const { processor, cssOutputFolder } = createProcessor ( false ) ;
150+ const { processor } = createProcessor ( terminalProvider , false ) ;
128151 await compileFixtureAsync ( processor , 'sass-variables-and-exports.module.scss' ) ;
129- const css : string = await readCssOutputAsync ( cssOutputFolder , 'sass-variables-and-exports.module.scss' ) ;
130- expect ( css ) . toMatchSnapshot ( ) ;
152+ const css : string = getCssOutput ( 'sass-variables-and-exports.module.scss' ) ;
131153 // Sass variables should be resolved to literal values
132154 expect ( css ) . toContain ( '#0078d4' ) ;
133155 expect ( css ) . toContain ( '#106ebe' ) ;
@@ -139,21 +161,19 @@ describe(SassProcessor.name, () => {
139161 } ) ;
140162
141163 it ( 'resolves Sass variables inside the :export block when preserveIcssExports is true' , async ( ) => {
142- const { processor, cssOutputFolder } = createProcessor ( true ) ;
164+ const { processor } = createProcessor ( terminalProvider , true ) ;
143165 await compileFixtureAsync ( processor , 'sass-variables-and-exports.module.scss' ) ;
144- const css : string = await readCssOutputAsync ( cssOutputFolder , 'sass-variables-and-exports.module.scss' ) ;
145- expect ( css ) . toMatchSnapshot ( ) ;
146- // The :export block should contain the resolved variable values, not the variable names
166+ const css : string = getCssOutput ( 'sass-variables-and-exports.module.scss' ) ;
167+ // The :export block should contain resolved values, not Sass variable names
147168 expect ( css ) . toContain ( ':export' ) ;
148169 expect ( css ) . toContain ( '#0078d4' ) ;
149170 expect ( css ) . not . toContain ( '$primary-color' ) ;
150171 } ) ;
151172
152173 it ( 'generates .d.ts with resolved :export keys as typed properties' , async ( ) => {
153- const { processor, dtsOutputFolder } = createProcessor ( false ) ;
174+ const { processor } = createProcessor ( terminalProvider , false ) ;
154175 await compileFixtureAsync ( processor , 'sass-variables-and-exports.module.scss' ) ;
155- const dts : string = await readDtsOutputAsync ( dtsOutputFolder , 'sass-variables-and-exports.module.scss' ) ;
156- expect ( dts ) . toMatchSnapshot ( ) ;
176+ const dts : string = getDtsOutput ( 'sass-variables-and-exports.module.scss' ) ;
157177 expect ( dts ) . toContain ( 'container' ) ;
158178 expect ( dts ) . toContain ( 'primaryColor' ) ;
159179 expect ( dts ) . toContain ( 'secondaryColor' ) ;
@@ -163,10 +183,9 @@ describe(SassProcessor.name, () => {
163183
164184 describe ( 'mixin-with-exports.module.scss (Sass @mixin)' , ( ) => {
165185 it ( 'expands @mixin calls in CSS output' , async ( ) => {
166- const { processor, cssOutputFolder } = createProcessor ( false ) ;
186+ const { processor } = createProcessor ( terminalProvider , false ) ;
167187 await compileFixtureAsync ( processor , 'mixin-with-exports.module.scss' ) ;
168- const css : string = await readCssOutputAsync ( cssOutputFolder , 'mixin-with-exports.module.scss' ) ;
169- expect ( css ) . toMatchSnapshot ( ) ;
188+ const css : string = getCssOutput ( 'mixin-with-exports.module.scss' ) ;
170189 // Mixin output should be inlined — no @mixin or @include in the output
171190 expect ( css ) . not . toContain ( '@mixin' ) ;
172191 expect ( css ) . not . toContain ( '@include' ) ;
@@ -176,20 +195,18 @@ describe(SassProcessor.name, () => {
176195 } ) ;
177196
178197 it ( 'preserves :export alongside expanded @mixin output when preserveIcssExports is true' , async ( ) => {
179- const { processor, cssOutputFolder } = createProcessor ( true ) ;
198+ const { processor } = createProcessor ( terminalProvider , true ) ;
180199 await compileFixtureAsync ( processor , 'mixin-with-exports.module.scss' ) ;
181- const css : string = await readCssOutputAsync ( cssOutputFolder , 'mixin-with-exports.module.scss' ) ;
182- expect ( css ) . toMatchSnapshot ( ) ;
200+ const css : string = getCssOutput ( 'mixin-with-exports.module.scss' ) ;
183201 expect ( css ) . toContain ( ':export' ) ;
184202 expect ( css ) . toContain ( 'display: flex' ) ;
185203 expect ( css ) . not . toContain ( '@mixin' ) ;
186204 } ) ;
187205
188206 it ( 'generates .d.ts with :export values and class names from @mixin-using file' , async ( ) => {
189- const { processor, dtsOutputFolder } = createProcessor ( false ) ;
207+ const { processor } = createProcessor ( terminalProvider , false ) ;
190208 await compileFixtureAsync ( processor , 'mixin-with-exports.module.scss' ) ;
191- const dts : string = await readDtsOutputAsync ( dtsOutputFolder , 'mixin-with-exports.module.scss' ) ;
192- expect ( dts ) . toMatchSnapshot ( ) ;
209+ const dts : string = getDtsOutput ( 'mixin-with-exports.module.scss' ) ;
193210 expect ( dts ) . toContain ( 'card' ) ;
194211 expect ( dts ) . toContain ( 'cardRadius' ) ;
195212 expect ( dts ) . toContain ( 'animationDuration' ) ;
@@ -198,33 +215,29 @@ describe(SassProcessor.name, () => {
198215
199216 describe ( 'extend-with-exports.module.scss (Sass @extend / placeholder selectors)' , ( ) => {
200217 it ( 'merges @extend selectors and strips :export when preserveIcssExports is false' , async ( ) => {
201- const { processor, cssOutputFolder } = createProcessor ( false ) ;
218+ const { processor } = createProcessor ( terminalProvider , false ) ;
202219 await compileFixtureAsync ( processor , 'extend-with-exports.module.scss' ) ;
203- const css : string = await readCssOutputAsync ( cssOutputFolder , 'extend-with-exports.module.scss' ) ;
204- expect ( css ) . toMatchSnapshot ( ) ;
205- // Placeholder %button-base should not appear literally; its rules should be merged into the
206- // selectors that @extend it
220+ const css : string = getCssOutput ( 'extend-with-exports.module.scss' ) ;
221+ // Placeholder %button-base should not appear literally; its rules should be merged
207222 expect ( css ) . not . toContain ( '%button-base' ) ;
208223 expect ( css ) . toContain ( '.primaryButton' ) ;
209224 expect ( css ) . toContain ( '.dangerButton' ) ;
210225 expect ( css ) . not . toContain ( ':export' ) ;
211226 } ) ;
212227
213228 it ( 'preserves :export alongside @extend-merged output when preserveIcssExports is true' , async ( ) => {
214- const { processor, cssOutputFolder } = createProcessor ( true ) ;
229+ const { processor } = createProcessor ( terminalProvider , true ) ;
215230 await compileFixtureAsync ( processor , 'extend-with-exports.module.scss' ) ;
216- const css : string = await readCssOutputAsync ( cssOutputFolder , 'extend-with-exports.module.scss' ) ;
217- expect ( css ) . toMatchSnapshot ( ) ;
231+ const css : string = getCssOutput ( 'extend-with-exports.module.scss' ) ;
218232 expect ( css ) . toContain ( ':export' ) ;
219233 expect ( css ) . toContain ( '.primaryButton' ) ;
220234 expect ( css ) . not . toContain ( '%button-base' ) ;
221235 } ) ;
222236
223237 it ( 'generates .d.ts with class names and :export values for @extend file' , async ( ) => {
224- const { processor, dtsOutputFolder } = createProcessor ( false ) ;
238+ const { processor } = createProcessor ( terminalProvider , false ) ;
225239 await compileFixtureAsync ( processor , 'extend-with-exports.module.scss' ) ;
226- const dts : string = await readDtsOutputAsync ( dtsOutputFolder , 'extend-with-exports.module.scss' ) ;
227- expect ( dts ) . toMatchSnapshot ( ) ;
240+ const dts : string = getDtsOutput ( 'extend-with-exports.module.scss' ) ;
228241 expect ( dts ) . toContain ( 'primaryButton' ) ;
229242 expect ( dts ) . toContain ( 'dangerButton' ) ;
230243 expect ( dts ) . toContain ( 'colorPrimary' ) ;
@@ -234,17 +247,8 @@ describe(SassProcessor.name, () => {
234247
235248 describe ( 'error reporting' , ( ) => {
236249 it ( 'emits an error for invalid SCSS syntax' , async ( ) => {
237- // Write a temporary invalid fixture to disk, compile it, then clean up.
238- const invalidFixturePath : string = `${ fixturesFolder } /invalid.module.scss` ;
239- await FileSystem . writeFileAsync ( invalidFixturePath , '.broken { color: ; }' ) ;
240-
241- const { processor, logger } = createProcessor ( false ) ;
242- try {
243- await processor . compileFilesAsync ( new Set ( [ invalidFixturePath ] ) ) ;
244- } finally {
245- await FileSystem . deleteFileAsync ( invalidFixturePath ) ;
246- }
247-
250+ const { processor, logger } = createProcessor ( terminalProvider , false ) ;
251+ await compileFixtureAsync ( processor , 'invalid.module.scss' ) ;
248252 expect ( logger . errors . length ) . toBeGreaterThan ( 0 ) ;
249253 } ) ;
250254 } ) ;
0 commit comments