diff --git a/README.md b/README.md index 8c9266c3f1d..8a54e1dea4e 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,6 @@ These GitHub repositories provide supplementary resources for Rush Stack: | [/build-tests/heft-parameter-plugin](./build-tests/heft-parameter-plugin/) | This project contains a Heft plugin that adds a custom parameter to built-in actions | | [/build-tests/heft-parameter-plugin-test](./build-tests/heft-parameter-plugin-test/) | This project exercises a built-in Heft action with a custom parameter | | [/build-tests/heft-rspack-everything-test](./build-tests/heft-rspack-everything-test/) | Building this project tests every task and config file for Heft when targeting the web browser runtime using Rspack | -| [/build-tests/heft-sass-doNotTrimOriginalFileExtension-test](./build-tests/heft-sass-doNotTrimOriginalFileExtension-test/) | Tests the doNotTrimOriginalFileExtension option for heft-sass-plugin | | [/build-tests/heft-sass-test](./build-tests/heft-sass-test/) | This project illustrates a minimal tutorial Heft project targeting the web browser runtime | | [/build-tests/heft-swc-test](./build-tests/heft-swc-test/) | Building this project tests building with SWC | | [/build-tests/heft-typescript-composite-test](./build-tests/heft-typescript-composite-test/) | Building this project tests behavior of Heft when the tsconfig.json file uses project references. | diff --git a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/.gitignore b/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/.gitignore deleted file mode 100644 index a214a6e4904..00000000000 --- a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -lib-css diff --git a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/config/heft.json b/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/config/heft.json deleted file mode 100644 index 922235cb167..00000000000 --- a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/config/heft.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft.schema.json", - - "phasesByName": { - "build": { - "cleanFiles": [{ "includeGlobs": ["lib-commonjs", "lib-css", "temp"] }], - - "tasksByName": { - "sass": { - "taskPlugin": { - "pluginPackage": "@rushstack/heft-sass-plugin" - } - }, - "typescript": { - "taskDependencies": ["sass"], - "taskPlugin": { - "pluginPackage": "@rushstack/heft-typescript-plugin" - } - } - } - }, - - "test": { - "phaseDependencies": ["build"], - "tasksByName": { - "jest": { - "taskPlugin": { - "pluginPackage": "@rushstack/heft-jest-plugin" - } - } - } - } - } -} diff --git a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/config/jest.config.json b/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/config/jest.config.json deleted file mode 100644 index 0ba0c340faa..00000000000 --- a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/config/jest.config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "@rushstack/heft-jest-plugin/includes/jest-shared.config.json", - - "roots": ["/lib-commonjs"], - "testMatch": ["/lib-commonjs/**/*.test.{cjs,js}"] -} diff --git a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/config/rush-project.json b/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/config/rush-project.json deleted file mode 100644 index 3136f4e81a8..00000000000 --- a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/config/rush-project.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush-project.schema.json", - - "operationSettings": [ - { - "operationName": "_phase:build", - "outputFolderNames": ["lib-commonjs", "lib-css", "temp/sass-ts"] - }, - { - "operationName": "_phase:test", - "outputFolderNames": ["coverage"] - } - ] -} diff --git a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/config/sass.json b/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/config/sass.json deleted file mode 100644 index ce8eb11a29b..00000000000 --- a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/config/sass.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft-sass-plugin.schema.json", - - "cssOutputFolders": ["lib-css"], - "fileExtensions": [".module.scss"], - "nonModuleFileExtensions": [".global.scss"], - "doNotTrimOriginalFileExtension": true, - "silenceDeprecations": ["mixed-decls", "import", "global-builtin", "color-functions"] -} diff --git a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/package.json b/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/package.json deleted file mode 100644 index 0953af9d6dc..00000000000 --- a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "heft-sass-doNotTrimOriginalFileExtension-test", - "description": "Tests the doNotTrimOriginalFileExtension option for heft-sass-plugin", - "version": "1.0.0", - "private": true, - "scripts": { - "build": "heft build --clean", - "_phase:build": "heft run --only build -- --clean", - "_phase:test": "heft run --only test -- --clean" - }, - "devDependencies": { - "@rushstack/heft": "workspace:*", - "@rushstack/heft-jest-plugin": "workspace:*", - "@rushstack/heft-sass-plugin": "workspace:*", - "@rushstack/heft-typescript-plugin": "workspace:*", - "@types/heft-jest": "1.0.1", - "@types/node": "20.17.19", - "typescript": "~5.8.2" - } -} diff --git a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/styles.module.scss b/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/styles.module.scss deleted file mode 100644 index 300ef97e35a..00000000000 --- a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/styles.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Test SCSS module for verifying doNotTrimOriginalFileExtension output. - * Expected output: styles.module.scss.css (not styles.module.css) - */ - -.label { - color: royalblue; -} - -.container { - padding: 16px; -} diff --git a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/stylesGlobal.global.scss b/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/stylesGlobal.global.scss deleted file mode 100644 index 11cbf01cdd2..00000000000 --- a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/stylesGlobal.global.scss +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Test non-module SCSS for verifying doNotTrimOriginalFileExtension output. - * Expected output: stylesGlobal.global.scss.css (not stylesGlobal.global.css) - */ - -.globalWrapper { - font-size: 16px; -} diff --git a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/test/__snapshots__/lib-css.test.ts.snap b/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/test/__snapshots__/lib-css.test.ts.snap deleted file mode 100644 index 30fd7e2089d..00000000000 --- a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/test/__snapshots__/lib-css.test.ts.snap +++ /dev/null @@ -1,37 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SASS No Shims (doNotTrimOriginalFileExtension) styles.module.scss: files 1`] = ` -Array [ - "styles.module.scss.css", -] -`; - -exports[`SASS No Shims (doNotTrimOriginalFileExtension) styles.module.scss: styles.module.scss.css 1`] = ` -"/** - * Test SCSS module for verifying doNotTrimOriginalFileExtension output. - * Expected output: styles.module.scss.css (not styles.module.css) - */ -.label { - color: royalblue; -} - -.container { - padding: 16px; -}" -`; - -exports[`SASS No Shims (doNotTrimOriginalFileExtension) stylesGlobal.global.scss: files 1`] = ` -Array [ - "stylesGlobal.global.scss.css", -] -`; - -exports[`SASS No Shims (doNotTrimOriginalFileExtension) stylesGlobal.global.scss: stylesGlobal.global.scss.css 1`] = ` -"/** - * Test non-module SCSS for verifying doNotTrimOriginalFileExtension output. - * Expected output: stylesGlobal.global.scss.css (not stylesGlobal.global.css) - */ -.globalWrapper { - font-size: 16px; -}" -`; diff --git a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/test/lib-css.test.ts b/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/test/lib-css.test.ts deleted file mode 100644 index dc065a1196f..00000000000 --- a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/test/lib-css.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import * as path from 'node:path'; -import { validateSnapshots, getScssFiles } from './validateSnapshots'; - -describe('SASS No Shims (doNotTrimOriginalFileExtension)', () => { - const libFolder: string = path.join(__dirname, '../../lib-css'); - getScssFiles().forEach((fileName: string) => { - it(fileName, () => { - validateSnapshots(libFolder, fileName); - }); - }); -}); diff --git a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/test/validateSnapshots.ts b/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/test/validateSnapshots.ts deleted file mode 100644 index f7b5542c881..00000000000 --- a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/src/test/validateSnapshots.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -/// -import * as fs from 'node:fs'; -import * as path from 'node:path'; - -export function getScssFiles(): string[] { - const srcFolder: string = path.join(__dirname, '../../src'); - const sourceFiles: string[] = fs - .readdirSync(srcFolder, { withFileTypes: true }) - .filter((file: fs.Dirent) => { - const { name } = file; - return file.isFile() && !name.startsWith('_') && (name.endsWith('.sass') || name.endsWith('.scss')); - }) - .map((dirent) => dirent.name); - return sourceFiles; -} - -export function validateSnapshots(dir: string, fileName: string): void { - const originalExt: string = path.extname(fileName); - const basename: string = path.basename(fileName, originalExt) + '.'; - const files: fs.Dirent[] = fs.readdirSync(dir, { withFileTypes: true }); - const filteredFiles: fs.Dirent[] = files.filter((file: fs.Dirent) => { - return file.isFile() && file.name.startsWith(basename); - }); - expect(filteredFiles.map((x) => x.name)).toMatchSnapshot(`files`); - filteredFiles.forEach((file: fs.Dirent) => { - if (!file.isFile() || !file.name.startsWith(basename)) { - return; - } - const filePath: string = path.join(dir, file.name); - const fileContents: string = fs.readFileSync(filePath, 'utf8'); - const normalizedFileContents: string = fileContents.replace(/\r/gm, ''); - expect(normalizedFileContents).toMatchSnapshot(`${file.name}`); - }); -} diff --git a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/tsconfig.json b/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/tsconfig.json deleted file mode 100644 index 9f9b216bd5a..00000000000 --- a/build-tests/heft-sass-doNotTrimOriginalFileExtension-test/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/tsconfig", - - "compilerOptions": { - "outDir": "lib-commonjs", - "rootDir": "src", - "rootDirs": ["src", "temp/sass-ts"], - - "forceConsistentCasingInFileNames": true, - "declaration": true, - "sourceMap": true, - "declarationMap": true, - "inlineSources": true, - "strictNullChecks": true, - "noUnusedLocals": true, - "types": ["heft-jest", "node"], - - "module": "commonjs", - "target": "es2017", - "lib": ["es2017"] - }, - "include": ["src/**/*.ts"] -} diff --git a/common/changes/@rushstack/heft-sass-plugin/clean-up-tests_2026-04-11-00-05.json b/common/changes/@rushstack/heft-sass-plugin/clean-up-tests_2026-04-11-00-05.json new file mode 100644 index 00000000000..9b91da609ca --- /dev/null +++ b/common/changes/@rushstack/heft-sass-plugin/clean-up-tests_2026-04-11-00-05.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "", + "type": "none", + "packageName": "@rushstack/heft-sass-plugin" + } + ], + "packageName": "@rushstack/heft-sass-plugin", + "email": "iclanton@users.noreply.github.com" +} \ No newline at end of file diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index 7ea165996ca..bf5b6374240 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -782,7 +782,7 @@ importers: version: 6.4.22(@types/react@17.0.74)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@storybook/cli': specifier: ~6.4.18 - version: 6.4.22(eslint@9.37.0)(jest@29.3.1(@types/node@20.17.19)(babel-plugin-macros@3.1.0))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(typescript@5.8.2) + version: 6.4.22(eslint@9.37.0)(jest@29.3.1(@types/node@20.17.19))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(typescript@5.8.2) '@storybook/components': specifier: ~6.4.18 version: 6.4.22(@types/react@17.0.74)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -818,7 +818,7 @@ importers: version: 5.2.7(webpack@4.47.0) jest: specifier: ~29.3.1 - version: 29.3.1(@types/node@20.17.19)(babel-plugin-macros@3.1.0) + version: 29.3.1(@types/node@20.17.19) react: specifier: ~17.0.2 version: 17.0.2 @@ -1004,7 +1004,7 @@ importers: version: 5.2.7(webpack@5.105.4) jest: specifier: ~29.3.1 - version: 29.3.1(@types/node@20.17.19)(babel-plugin-macros@3.1.0) + version: 29.3.1(@types/node@20.17.19) react: specifier: ~19.2.3 version: 19.2.4 @@ -2136,30 +2136,6 @@ importers: specifier: ~5.8.2 version: 5.8.2 - ../../../build-tests/heft-sass-doNotTrimOriginalFileExtension-test: - devDependencies: - '@rushstack/heft': - specifier: workspace:* - version: link:../../apps/heft - '@rushstack/heft-jest-plugin': - specifier: workspace:* - version: link:../../heft-plugins/heft-jest-plugin - '@rushstack/heft-sass-plugin': - specifier: workspace:* - version: link:../../heft-plugins/heft-sass-plugin - '@rushstack/heft-typescript-plugin': - specifier: workspace:* - version: link:../../heft-plugins/heft-typescript-plugin - '@types/heft-jest': - specifier: 1.0.1 - version: 1.0.1 - '@types/node': - specifier: 20.17.19 - version: 20.17.19 - typescript: - specifier: ~5.8.2 - version: 5.8.2 - ../../../build-tests/heft-sass-test: dependencies: buttono: @@ -22438,7 +22414,7 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(babel-plugin-macros@3.1.0)': + '@jest/core@29.7.0': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -22452,7 +22428,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.9.3)(babel-plugin-macros@3.1.0) + jest-config: 29.7.0(@types/node@22.9.3) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -25160,7 +25136,7 @@ snapshots: ts-dedent: 2.2.0 util-deprecate: 1.0.2 - '@storybook/cli@6.4.22(eslint@9.37.0)(jest@29.3.1(@types/node@20.17.19)(babel-plugin-macros@3.1.0))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(typescript@5.8.2)': + '@storybook/cli@6.4.22(eslint@9.37.0)(jest@29.3.1(@types/node@20.17.19))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(typescript@5.8.2)': dependencies: '@babel/core': 7.20.12 '@babel/preset-env': 7.29.2(@babel/core@7.20.12) @@ -25180,7 +25156,7 @@ snapshots: fs-extra: 9.1.0 get-port: 5.1.1 globby: 11.1.0 - jest: 29.3.1(@types/node@20.17.19)(babel-plugin-macros@3.1.0) + jest: 29.3.1(@types/node@20.17.19) jscodeshift: 0.13.1(@babel/preset-env@7.29.2(@babel/core@7.20.12)) json5: 2.2.3 leven: 3.1.0 @@ -28767,13 +28743,13 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.12 - create-jest@29.7.0(@types/node@20.17.19)(babel-plugin-macros@3.1.0): + create-jest@29.7.0(@types/node@20.17.19): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.17.19)(babel-plugin-macros@3.1.0) + jest-config: 29.7.0(@types/node@20.17.19) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -32142,16 +32118,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.17.19)(babel-plugin-macros@3.1.0): + jest-cli@29.7.0(@types/node@20.17.19): dependencies: - '@jest/core': 29.7.0(babel-plugin-macros@3.1.0) + '@jest/core': 29.7.0 '@jest/test-result': 29.7.0(@types/node@20.17.19) '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.17.19)(babel-plugin-macros@3.1.0) + create-jest: 29.7.0(@types/node@20.17.19) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.17.19)(babel-plugin-macros@3.1.0) + jest-config: 29.7.0(@types/node@20.17.19) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -32221,7 +32197,7 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.17.19)(babel-plugin-macros@3.1.0): + jest-config@29.7.0(@types/node@20.17.19): dependencies: '@babel/core': 7.20.12 '@jest/test-sequencer': 29.7.0(@types/node@20.17.19) @@ -32251,7 +32227,7 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@22.9.3)(babel-plugin-macros@3.1.0): + jest-config@29.7.0(@types/node@22.9.3): dependencies: '@babel/core': 7.20.12 '@jest/test-sequencer': 29.7.0(@types/node@22.9.3) @@ -32639,12 +32615,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.3.1(@types/node@20.17.19)(babel-plugin-macros@3.1.0): + jest@29.3.1(@types/node@20.17.19): dependencies: '@jest/core': 29.5.0(babel-plugin-macros@3.1.0) '@jest/types': 29.5.0 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.17.19)(babel-plugin-macros@3.1.0) + jest-cli: 29.7.0(@types/node@20.17.19) transitivePeerDependencies: - '@types/node' - babel-plugin-macros diff --git a/common/config/subspaces/default/repo-state.json b/common/config/subspaces/default/repo-state.json index 468d5af5047..db90c045a90 100644 --- a/common/config/subspaces/default/repo-state.json +++ b/common/config/subspaces/default/repo-state.json @@ -1,5 +1,5 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "6140dce85765ff55fd0f8cf9e4469496cf6b4896", + "pnpmShrinkwrapHash": "d80d7c51d2136ac728c22f9c7b55d60fc8aadb0a", "preferredVersionsHash": "029c99bd6e65c5e1f25e2848340509811ff9753c" } diff --git a/heft-plugins/heft-sass-plugin/src/test/SassProcessor.test.ts b/heft-plugins/heft-sass-plugin/src/test/SassProcessor.test.ts index c6c86ffc1b6..f548d4c3859 100644 --- a/heft-plugins/heft-sass-plugin/src/test/SassProcessor.test.ts +++ b/heft-plugins/heft-sass-plugin/src/test/SassProcessor.test.ts @@ -32,6 +32,7 @@ type ICreateProcessorOptions = Partial< | 'nonModuleFileExtensions' | 'postProcessCssAsync' | 'preserveIcssExports' + | 'silenceDeprecations' | 'srcFolder' > >; @@ -405,6 +406,126 @@ describe(SassProcessor.name, () => { }); }); + describe('simple.module.sass (indented Sass syntax)', () => { + it('compiles indented Sass syntax to CSS correctly', async () => { + const { processor } = createProcessor(terminalProvider); + await compileFixtureAsync(processor, 'simple.module.sass'); + const css: string = getCssOutput('simple.module.sass'); + expect(css).toContain('.exampleClass'); + expect(css).toContain('color: red'); + expect(css).toContain('.exampleHeading'); + expect(css).toContain('font-weight: bold'); + }); + + it('generates .d.ts with class names from indented Sass', async () => { + const { processor } = createProcessor(terminalProvider); + await compileFixtureAsync(processor, 'simple.module.sass'); + const dts: string = getDtsOutput('simple.module.sass'); + expect(dts).toContain('exampleClass'); + expect(dts).toContain('exampleHeading'); + expect(dts).toContain('export default styles'); + }); + + it('emits an ESM shim for an indented Sass module file', async () => { + const { processor } = createProcessor(terminalProvider, { + cssOutputFolders: [{ folder: CSS_OUTPUT_FOLDER, shimModuleFormat: 'esnext' }] + }); + await compileFixtureAsync(processor, 'simple.module.sass'); + const shim: string = getJsShimOutput('simple.module.sass'); + expect(shim).toBe(`export { default } from "./simple.module.css";`); + }); + }); + + describe('use-with-partial.module.scss (@use with local partial)', () => { + it('resolves variables from a @use partial in CSS output', async () => { + const { processor } = createProcessor(terminalProvider); + await compileFixtureAsync(processor, 'use-with-partial.module.scss'); + const css: string = getCssOutput('use-with-partial.module.scss'); + // $brand-color from the partial should be resolved to its value + expect(css).toContain('#0078d4'); + // $spacing-unit * 2 = 8px + expect(css).toContain('8px'); + expect(css).toContain('.container'); + expect(css).toContain('.header'); + }); + + it('generates .d.ts with class names from a file using @use', async () => { + const { processor } = createProcessor(terminalProvider); + await compileFixtureAsync(processor, 'use-with-partial.module.scss'); + const dts: string = getDtsOutput('use-with-partial.module.scss'); + expect(dts).toContain('container'); + expect(dts).toContain('header'); + expect(dts).toContain('export default styles'); + }); + }); + + describe('global-styles.global.sass (.global.sass non-module file)', () => { + it('compiles indented Sass syntax to plain CSS for a .global.sass file', async () => { + const { processor } = createProcessor(terminalProvider); + await compileFixtureAsync(processor, 'global-styles.global.sass'); + const css: string = getCssOutput('global-styles.global.sass'); + expect(css).toContain('body'); + expect(css).toContain('h1'); + // $heading-size should be resolved to its value + expect(css).toContain('24px'); + expect(css).toContain('#333'); + }); + + it('emits export {}; in the .d.ts for a .global.sass file', async () => { + const { processor } = createProcessor(terminalProvider); + await compileFixtureAsync(processor, 'global-styles.global.sass'); + const dts: string = getDtsOutput('global-styles.global.sass'); + expect(dts).toBe('export {};'); + }); + + it('emits a side-effect ESM shim for a .global.sass file', async () => { + const { processor } = createProcessor(terminalProvider, { + cssOutputFolders: [{ folder: CSS_OUTPUT_FOLDER, shimModuleFormat: 'esnext' }] + }); + await compileFixtureAsync(processor, 'global-styles.global.sass'); + const shim: string = getJsShimOutput('global-styles.global.sass'); + expect(shim).toBe(`import "./global-styles.global.css";export {};`); + }); + }); + + describe('css-module.module.css (plain CSS as CSS module input)', () => { + it('generates .d.ts with class names from a .module.css input file', async () => { + const { processor } = createProcessor(terminalProvider); + await compileFixtureAsync(processor, 'css-module.module.css'); + const dts: string = getDtsOutput('css-module.module.css'); + expect(dts).toContain('root'); + expect(dts).toContain('header'); + expect(dts).toContain('export default styles'); + }); + + it('passes CSS through the pipeline unchanged for a .module.css input file', async () => { + const { processor } = createProcessor(terminalProvider); + await compileFixtureAsync(processor, 'css-module.module.css'); + const css: string = getCssOutput('css-module.module.css'); + expect(css).toContain('.root'); + expect(css).toContain('.header'); + expect(css).toContain('display: flex'); + }); + + it('does not emit a JS shim for a .module.css input file', async () => { + const { processor } = createProcessor(terminalProvider, { + cssOutputFolders: [{ folder: CSS_OUTPUT_FOLDER, shimModuleFormat: 'esnext' }] + }); + await compileFixtureAsync(processor, 'css-module.module.css'); + // .css inputs cannot have a shim (the shim would reference itself) + const shimPaths: string[] = getAllWrittenPathsMatching('.module.css.js'); + expect(shimPaths).toHaveLength(0); + }); + }); + + describe('silenceDeprecations option', () => { + it('throws at construction time when given an unknown deprecation code', () => { + expect(() => + createProcessor(terminalProvider, { silenceDeprecations: ['not-a-real-deprecation-code'] }) + ).toThrow('Unknown deprecation code: not-a-real-deprecation-code'); + }); + }); + describe('non-module (global) files', () => { it('emits plain compiled CSS for a .global.scss file', async () => { const { processor } = createProcessor(terminalProvider, { diff --git a/heft-plugins/heft-sass-plugin/src/test/__snapshots__/SassProcessor.test.ts.snap b/heft-plugins/heft-sass-plugin/src/test/__snapshots__/SassProcessor.test.ts.snap index 0c9223d5aa0..2fd0616ad01 100644 --- a/heft-plugins/heft-sass-plugin/src/test/__snapshots__/SassProcessor.test.ts.snap +++ b/heft-plugins/heft-sass-plugin/src/test/__snapshots__/SassProcessor.test.ts.snap @@ -279,6 +279,93 @@ export default styles;", } `; +exports[`SassProcessor css-module.module.css (plain CSS as CSS module input) does not emit a JS shim for a .module.css input file: terminal-output 1`] = ` +Array [ + "[verbose] Checking for changes to 1 files...[n]", + "[ log] Compiling 1 files...[n]", +] +`; + +exports[`SassProcessor css-module.module.css (plain CSS as CSS module input) does not emit a JS shim for a .module.css input file: written-files 1`] = ` +Map { + "/fake/output/dts/css-module.module.css.d.ts" => "declare interface IStyles { + root: string; + header: string; +} +declare const styles: IStyles; +export default styles;", + "/fake/output/css/css-module.module.css" => "/* Plain CSS file treated as a CSS module via fileExtensions: ['.module.css']. + Verifies that CSS input (not SCSS/Sass) is processed correctly. */ +.root { + display: flex; + flex-direction: column; +} + +.header { + font-size: 16px; + font-weight: bold; +}", +} +`; + +exports[`SassProcessor css-module.module.css (plain CSS as CSS module input) generates .d.ts with class names from a .module.css input file: terminal-output 1`] = ` +Array [ + "[verbose] Checking for changes to 1 files...[n]", + "[ log] Compiling 1 files...[n]", +] +`; + +exports[`SassProcessor css-module.module.css (plain CSS as CSS module input) generates .d.ts with class names from a .module.css input file: written-files 1`] = ` +Map { + "/fake/output/dts/css-module.module.css.d.ts" => "declare interface IStyles { + root: string; + header: string; +} +declare const styles: IStyles; +export default styles;", + "/fake/output/css/css-module.module.css" => "/* Plain CSS file treated as a CSS module via fileExtensions: ['.module.css']. + Verifies that CSS input (not SCSS/Sass) is processed correctly. */ +.root { + display: flex; + flex-direction: column; +} + +.header { + font-size: 16px; + font-weight: bold; +}", +} +`; + +exports[`SassProcessor css-module.module.css (plain CSS as CSS module input) passes CSS through the pipeline unchanged for a .module.css input file: terminal-output 1`] = ` +Array [ + "[verbose] Checking for changes to 1 files...[n]", + "[ log] Compiling 1 files...[n]", +] +`; + +exports[`SassProcessor css-module.module.css (plain CSS as CSS module input) passes CSS through the pipeline unchanged for a .module.css input file: written-files 1`] = ` +Map { + "/fake/output/dts/css-module.module.css.d.ts" => "declare interface IStyles { + root: string; + header: string; +} +declare const styles: IStyles; +export default styles;", + "/fake/output/css/css-module.module.css" => "/* Plain CSS file treated as a CSS module via fileExtensions: ['.module.css']. + Verifies that CSS input (not SCSS/Sass) is processed correctly. */ +.root { + display: flex; + flex-direction: column; +} + +.header { + font-size: 16px; + font-weight: bold; +}", +} +`; + exports[`SassProcessor doNotTrimOriginalFileExtension preserves the source extension when doNotTrimOriginalFileExtension is true: terminal-output 1`] = ` Array [ "[verbose] Checking for changes to 1 files...[n]", @@ -663,6 +750,73 @@ Map { } `; +exports[`SassProcessor global-styles.global.sass (.global.sass non-module file) compiles indented Sass syntax to plain CSS for a .global.sass file: terminal-output 1`] = ` +Array [ + "[verbose] Checking for changes to 1 files...[n]", + "[ log] Compiling 1 files...[n]", +] +`; + +exports[`SassProcessor global-styles.global.sass (.global.sass non-module file) compiles indented Sass syntax to plain CSS for a .global.sass file: written-files 1`] = ` +Map { + "/fake/output/dts/global-styles.global.sass.d.ts" => "export {};", + "/fake/output/css/global-styles.global.css" => "body { + margin: 0; + padding: 0; +} + +h1 { + font-size: 24px; + color: #333; +}", +} +`; + +exports[`SassProcessor global-styles.global.sass (.global.sass non-module file) emits a side-effect ESM shim for a .global.sass file: terminal-output 1`] = ` +Array [ + "[verbose] Checking for changes to 1 files...[n]", + "[ log] Compiling 1 files...[n]", +] +`; + +exports[`SassProcessor global-styles.global.sass (.global.sass non-module file) emits a side-effect ESM shim for a .global.sass file: written-files 1`] = ` +Map { + "/fake/output/dts/global-styles.global.sass.d.ts" => "export {};", + "/fake/output/css/global-styles.global.css" => "body { + margin: 0; + padding: 0; +} + +h1 { + font-size: 24px; + color: #333; +}", + "/fake/output/css/global-styles.global.sass.js" => "import \\"./global-styles.global.css\\";export {};", +} +`; + +exports[`SassProcessor global-styles.global.sass (.global.sass non-module file) emits export {}; in the .d.ts for a .global.sass file: terminal-output 1`] = ` +Array [ + "[verbose] Checking for changes to 1 files...[n]", + "[ log] Compiling 1 files...[n]", +] +`; + +exports[`SassProcessor global-styles.global.sass (.global.sass non-module file) emits export {}; in the .d.ts for a .global.sass file: written-files 1`] = ` +Map { + "/fake/output/dts/global-styles.global.sass.d.ts" => "export {};", + "/fake/output/css/global-styles.global.css" => "body { + margin: 0; + padding: 0; +} + +h1 { + font-size: 24px; + color: #333; +}", +} +`; + exports[`SassProcessor mixin-with-exports.module.scss (Sass @mixin) expands @mixin calls in CSS output: terminal-output 1`] = ` Array [ "[verbose] Checking for changes to 1 files...[n]", @@ -1031,3 +1185,141 @@ export default styles;", }", } `; + +exports[`SassProcessor silenceDeprecations option throws at construction time when given an unknown deprecation code: terminal-output 1`] = `Array []`; + +exports[`SassProcessor silenceDeprecations option throws at construction time when given an unknown deprecation code: written-files 1`] = `Map {}`; + +exports[`SassProcessor simple.module.sass (indented Sass syntax) compiles indented Sass syntax to CSS correctly: terminal-output 1`] = ` +Array [ + "[verbose] Checking for changes to 1 files...[n]", + "[ log] Compiling 1 files...[n]", +] +`; + +exports[`SassProcessor simple.module.sass (indented Sass syntax) compiles indented Sass syntax to CSS correctly: written-files 1`] = ` +Map { + "/fake/output/dts/simple.module.sass.d.ts" => "declare interface IStyles { + exampleClass: string; + exampleHeading: string; +} +declare const styles: IStyles; +export default styles;", + "/fake/output/css/simple.module.css" => ".exampleClass { + color: red; + font-size: 14px; +} + +.exampleHeading { + font-weight: bold; + color: navy; +}", +} +`; + +exports[`SassProcessor simple.module.sass (indented Sass syntax) emits an ESM shim for an indented Sass module file: terminal-output 1`] = ` +Array [ + "[verbose] Checking for changes to 1 files...[n]", + "[ log] Compiling 1 files...[n]", +] +`; + +exports[`SassProcessor simple.module.sass (indented Sass syntax) emits an ESM shim for an indented Sass module file: written-files 1`] = ` +Map { + "/fake/output/dts/simple.module.sass.d.ts" => "declare interface IStyles { + exampleClass: string; + exampleHeading: string; +} +declare const styles: IStyles; +export default styles;", + "/fake/output/css/simple.module.css" => ".exampleClass { + color: red; + font-size: 14px; +} + +.exampleHeading { + font-weight: bold; + color: navy; +}", + "/fake/output/css/simple.module.sass.js" => "export { default } from \\"./simple.module.css\\";", +} +`; + +exports[`SassProcessor simple.module.sass (indented Sass syntax) generates .d.ts with class names from indented Sass: terminal-output 1`] = ` +Array [ + "[verbose] Checking for changes to 1 files...[n]", + "[ log] Compiling 1 files...[n]", +] +`; + +exports[`SassProcessor simple.module.sass (indented Sass syntax) generates .d.ts with class names from indented Sass: written-files 1`] = ` +Map { + "/fake/output/dts/simple.module.sass.d.ts" => "declare interface IStyles { + exampleClass: string; + exampleHeading: string; +} +declare const styles: IStyles; +export default styles;", + "/fake/output/css/simple.module.css" => ".exampleClass { + color: red; + font-size: 14px; +} + +.exampleHeading { + font-weight: bold; + color: navy; +}", +} +`; + +exports[`SassProcessor use-with-partial.module.scss (@use with local partial) generates .d.ts with class names from a file using @use: terminal-output 1`] = ` +Array [ + "[verbose] Checking for changes to 1 files...[n]", + "[ log] Compiling 1 files...[n]", +] +`; + +exports[`SassProcessor use-with-partial.module.scss (@use with local partial) generates .d.ts with class names from a file using @use: written-files 1`] = ` +Map { + "/fake/output/dts/use-with-partial.module.scss.d.ts" => "declare interface IStyles { + container: string; + header: string; +} +declare const styles: IStyles; +export default styles;", + "/fake/output/css/use-with-partial.module.css" => ".container { + color: #0078d4; + padding: 8px; +} + +.header { + border-bottom: 1px solid #0078d4; +}", +} +`; + +exports[`SassProcessor use-with-partial.module.scss (@use with local partial) resolves variables from a @use partial in CSS output: terminal-output 1`] = ` +Array [ + "[verbose] Checking for changes to 1 files...[n]", + "[ log] Compiling 1 files...[n]", +] +`; + +exports[`SassProcessor use-with-partial.module.scss (@use with local partial) resolves variables from a @use partial in CSS output: written-files 1`] = ` +Map { + "/fake/output/dts/use-with-partial.module.scss.d.ts" => "declare interface IStyles { + container: string; + header: string; +} +declare const styles: IStyles; +export default styles;", + "/fake/output/css/use-with-partial.module.css" => ".container { + color: #0078d4; + padding: 8px; +} + +.header { + border-bottom: 1px solid #0078d4; +}", +} +`; diff --git a/heft-plugins/heft-sass-plugin/src/test/fixtures/_partial.scss b/heft-plugins/heft-sass-plugin/src/test/fixtures/_partial.scss new file mode 100644 index 00000000000..8fad83f62a2 --- /dev/null +++ b/heft-plugins/heft-sass-plugin/src/test/fixtures/_partial.scss @@ -0,0 +1,4 @@ +// Sass partial exposing shared design tokens. +// Imported via @use 'partial' in use-with-partial.module.scss. +$brand-color: #0078d4; +$spacing-unit: 4px; diff --git a/heft-plugins/heft-sass-plugin/src/test/fixtures/css-module.module.css b/heft-plugins/heft-sass-plugin/src/test/fixtures/css-module.module.css new file mode 100644 index 00000000000..5967c09e336 --- /dev/null +++ b/heft-plugins/heft-sass-plugin/src/test/fixtures/css-module.module.css @@ -0,0 +1,11 @@ +/* Plain CSS file treated as a CSS module via fileExtensions: ['.module.css']. + Verifies that CSS input (not SCSS/Sass) is processed correctly. */ +.root { + display: flex; + flex-direction: column; +} + +.header { + font-size: 16px; + font-weight: bold; +} diff --git a/heft-plugins/heft-sass-plugin/src/test/fixtures/global-styles.global.sass b/heft-plugins/heft-sass-plugin/src/test/fixtures/global-styles.global.sass new file mode 100644 index 00000000000..ea0f6d07b8a --- /dev/null +++ b/heft-plugins/heft-sass-plugin/src/test/fixtures/global-styles.global.sass @@ -0,0 +1,12 @@ +// Non-module global stylesheet in indented Sass syntax (.global.sass). +// Used to verify that .global.sass files are treated as non-modules. +$heading-size: 24px +$body-color: #333 + +body + margin: 0 + padding: 0 + +h1 + font-size: $heading-size + color: $body-color diff --git a/heft-plugins/heft-sass-plugin/src/test/fixtures/simple.module.sass b/heft-plugins/heft-sass-plugin/src/test/fixtures/simple.module.sass new file mode 100644 index 00000000000..e9194c59387 --- /dev/null +++ b/heft-plugins/heft-sass-plugin/src/test/fixtures/simple.module.sass @@ -0,0 +1,9 @@ +// Indented Sass syntax (whitespace-significant, no braces). +// Used to verify that .sass files are parsed with the indented syntax. +.exampleClass + color: red + font-size: 14px + +.exampleHeading + font-weight: bold + color: navy diff --git a/heft-plugins/heft-sass-plugin/src/test/fixtures/use-with-partial.module.scss b/heft-plugins/heft-sass-plugin/src/test/fixtures/use-with-partial.module.scss new file mode 100644 index 00000000000..9f8b9e74ea0 --- /dev/null +++ b/heft-plugins/heft-sass-plugin/src/test/fixtures/use-with-partial.module.scss @@ -0,0 +1,12 @@ +// Uses the modern @use syntax to import variables from a local partial. +// Verifies that SassProcessor resolves _partial.scss when @use 'partial' is written. +@use 'partial' as tokens; + +.container { + color: tokens.$brand-color; + padding: tokens.$spacing-unit * 2; +} + +.header { + border-bottom: 1px solid tokens.$brand-color; +} diff --git a/rush.json b/rush.json index 36c55a432c6..eddb61d39f6 100644 --- a/rush.json +++ b/rush.json @@ -935,12 +935,6 @@ "reviewCategory": "tests", "shouldPublish": false }, - { - "packageName": "heft-sass-doNotTrimOriginalFileExtension-test", - "projectFolder": "build-tests/heft-sass-doNotTrimOriginalFileExtension-test", - "reviewCategory": "tests", - "shouldPublish": false - }, { "packageName": "heft-sass-test", "projectFolder": "build-tests/heft-sass-test",