diff --git a/.eslintignore b/.eslintignore index 40b0fcc4a9fce1..5043b4bb97f494 100644 --- a/.eslintignore +++ b/.eslintignore @@ -23,7 +23,7 @@ /packages/pigment-react/processors/ /packages/pigment-react/exports/ /packages/pigment-react/theme/ -/packages/pigment-react/tests/fixtures/ +/packages/pigment-react/tests/**/fixtures /packages/pigment-nextjs-plugin/loader.js # Ignore fixtures /packages-internal/scripts/typescript-to-proptypes/test/*/* diff --git a/packages/pigment-react/package.json b/packages/pigment-react/package.json index 21780ca073c0f6..f82fe99d7c1855 100644 --- a/packages/pigment-react/package.json +++ b/packages/pigment-react/package.json @@ -26,6 +26,7 @@ "copy-license": "node ../../scripts/pigment-license.mjs", "build": "tsup", "test": "cd ../../ && cross-env NODE_ENV=test mocha 'packages/pigment-react/**/*.test.{js,ts,tsx}'", + "test:update": "cd ../../ && cross-env NODE_ENV=test UPDATE_FIXTURES=true mocha 'packages/pigment-react/**/*.test.{js,ts,tsx}'", "test:ci": "cd ../../ && cross-env NODE_ENV=test BABEL_ENV=coverage nyc --reporter=lcov --report-dir=./coverage/pigment-react mocha 'packages/pigment-react/**/*.test.{js,ts,tsx}'", "typecheck": "tsc --noEmit -p ." }, @@ -53,12 +54,14 @@ "@types/babel__core": "^7.20.5", "@types/babel__helper-module-imports": "^7.18.3", "@types/babel__helper-plugin-utils": "^7.10.3", + "@types/chai": "^4.3.12", "@types/cssesc": "^3.0.2", "@types/lodash": "^4.14.202", "@types/node": "^18.19.21", "@types/react": "^18.2.55", "@types/stylis": "^4.2.5", "chai": "^4.4.1", + "prettier": "^3.2.5", "react": "^18.2.0" }, "peerDependencies": { diff --git a/packages/pigment-react/tests/README.md b/packages/pigment-react/tests/README.md index 3508b94a7ac824..5da4275ebf7d59 100644 --- a/packages/pigment-react/tests/README.md +++ b/packages/pigment-react/tests/README.md @@ -1,3 +1,35 @@ -# Adding new fixtures +# Pigment CSS testing -Create a new file name with `[name].input.js` and add `styled`, `css` or other zero-runtime calls into the file. Also add equivalent `[name].output.js` and `[name].output.css` and run the test. After the new test fails, get the results from the received output and add it to the equivalent js and css files. This is equivalent to snapshot testing and will make sure any change in internal css generation logic does not fail any other existing tests. +## Folder structure + +- `tests` is the root folder for all tests + - `fixtures` contains all the fixtures for the tests + - `*.input.js` are the input files created manually + - `*.output.*` are the expected output files created by running the tests + - `*.test.js` are the test files that run the fixtures + +## Running tests + +At the root project terminal: + +```bash +pnpm nx run @pigment-css/react:test +``` + +To update the output fixtures: + +```bash +pnpm nx run @pigment-css/react:test:update +``` + +## Adding new tests + +Each folder inside `tests` is a Pigment CSS feature. To add a new test, create a new folder with the feature name and add a new test file with the `.test.js` extension. Inside the test file, import the fixtures and run the tests. + +## Adding new fixtures + +Create a new file name with `[name].input.js` and add `styled`, `css` or other Pigment CSS calls into the file. + +The first time you run the tests, the output files will be created automatically. Then check the output files to make sure they are correct. + +For any implementation changes after that, if you want to update the output files, run the tests with the `test:update` script. diff --git a/packages/pigment-react/tests/css/css.test.ts b/packages/pigment-react/tests/css/css.test.ts new file mode 100644 index 00000000000000..d926f6de9a4162 --- /dev/null +++ b/packages/pigment-react/tests/css/css.test.ts @@ -0,0 +1,40 @@ +import path from 'node:path'; +import { runTransformation, expect } from '../testUtils'; + +const theme = { + palette: { + primary: { + main: 'red', + }, + }, + size: { + font: { + h1: '3rem', + }, + }, + components: { + MuiSlider: { + styleOverrides: { + rail: { + fontSize: '3rem', + }, + }, + }, + }, +}; + +describe('Pigment CSS - css', () => { + it('basics', async () => { + const { output, fixture } = await runTransformation( + path.join(__dirname, 'fixtures/css.input.js'), + { + themeArgs: { + theme, + }, + }, + ); + + expect(output.js).to.equal(fixture.js); + expect(output.css).to.equal(fixture.css); + }); +}); diff --git a/packages/pigment-react/tests/css/fixtures/css.input.js b/packages/pigment-react/tests/css/fixtures/css.input.js new file mode 100644 index 00000000000000..24519d991b09ac --- /dev/null +++ b/packages/pigment-react/tests/css/fixtures/css.input.js @@ -0,0 +1,6 @@ +import { css } from '@pigment-css/react'; + +const cls1 = css` + color: ${({ theme }) => theme.palette.primary.main}; + font-size: ${({ theme }) => theme.size.font.h1}; +`; diff --git a/packages/pigment-react/tests/css/fixtures/css.output.css b/packages/pigment-react/tests/css/fixtures/css.output.css new file mode 100644 index 00000000000000..e34d404861da6d --- /dev/null +++ b/packages/pigment-react/tests/css/fixtures/css.output.css @@ -0,0 +1,4 @@ +.c1wr0t7p { + color: red; + font-size: 3rem; +} diff --git a/packages/pigment-react/tests/css/fixtures/css.output.js b/packages/pigment-react/tests/css/fixtures/css.output.js new file mode 100644 index 00000000000000..afbe5dcc772db6 --- /dev/null +++ b/packages/pigment-react/tests/css/fixtures/css.output.js @@ -0,0 +1 @@ +const cls1 = 'c1wr0t7p'; diff --git a/packages/pigment-react/tests/fixtures/styled.output.css b/packages/pigment-react/tests/fixtures/styled.output.css deleted file mode 100644 index 64f8bb93f42fc4..00000000000000 --- a/packages/pigment-react/tests/fixtures/styled.output.css +++ /dev/null @@ -1,6 +0,0 @@ -@keyframes r1ub6j9g{from{transform:rotate(360deg);}to{transform:rotate(0deg);}} -.c1y26wbb{color:red;animation:r1ub6j9g 2s ease-out 0s infinite;} -.ct00dwm{color:red;font-size:3rem;} -.soujkwr{display:block;position:absolute;border-radius:inherit;background-color:currentColor;opacity:0.38;font-size:3rem;} -.soujkwr-1{font-size:3rem;} -.s14dtw5g{display:block;opacity:0.38;font-size:3rem;} diff --git a/packages/pigment-react/tests/fixtures/styled.output.js b/packages/pigment-react/tests/fixtures/styled.output.js deleted file mode 100644 index bfc47724a88307..00000000000000 --- a/packages/pigment-react/tests/fixtures/styled.output.js +++ /dev/null @@ -1,17 +0,0 @@ -import { styled as _styled3 } from "@pigment-css/react"; -import { styled as _styled2 } from "@pigment-css/react"; -import { styled as _styled } from "@pigment-css/react"; -import _theme from "@pigment-css/react/theme"; -const Component = /*#__PURE__*/_styled("div")({ - classes: ["c1y26wbb"] -}); -const cls1 = "ct00dwm"; -const SliderRail = /*#__PURE__*/_styled2("span", { - name: 'MuiSlider', - slot: 'Rail' -})({ - classes: ["soujkwr", "soujkwr-1"] -}); -const SliderRail2 = /*#__PURE__*/_styled3("span")({ - classes: ["s14dtw5g"] -}); diff --git a/packages/pigment-react/tests/keyframes/fixtures/keyframes.input.js b/packages/pigment-react/tests/keyframes/fixtures/keyframes.input.js new file mode 100644 index 00000000000000..8ff26f9d247b2b --- /dev/null +++ b/packages/pigment-react/tests/keyframes/fixtures/keyframes.input.js @@ -0,0 +1,10 @@ +import { keyframes } from '@pigment-css/react'; + +const rotateKeyframe = keyframes({ + from: { + transform: 'rotate(360deg)', + }, + to: { + transform: 'rotate(0deg)', + }, +}); diff --git a/packages/pigment-react/tests/keyframes/fixtures/keyframes.output.css b/packages/pigment-react/tests/keyframes/fixtures/keyframes.output.css new file mode 100644 index 00000000000000..43ac2b3341e9fb --- /dev/null +++ b/packages/pigment-react/tests/keyframes/fixtures/keyframes.output.css @@ -0,0 +1,8 @@ +@keyframes r14c1bqo { + from { + transform: rotate(360deg); + } + to { + transform: rotate(0deg); + } +} diff --git a/packages/pigment-react/tests/keyframes/fixtures/keyframes.output.js b/packages/pigment-react/tests/keyframes/fixtures/keyframes.output.js new file mode 100644 index 00000000000000..2df1df78892cc8 --- /dev/null +++ b/packages/pigment-react/tests/keyframes/fixtures/keyframes.output.js @@ -0,0 +1 @@ +const rotateKeyframe = 'r14c1bqo'; diff --git a/packages/pigment-react/tests/keyframes/keyframes.test.ts b/packages/pigment-react/tests/keyframes/keyframes.test.ts new file mode 100644 index 00000000000000..337dedbaf76fdb --- /dev/null +++ b/packages/pigment-react/tests/keyframes/keyframes.test.ts @@ -0,0 +1,13 @@ +import path from 'node:path'; +import { runTransformation, expect } from '../testUtils'; + +describe('Pigment CSS - keyframes', () => { + it('basics', async () => { + const { output, fixture } = await runTransformation( + path.join(__dirname, 'fixtures/keyframes.input.js'), + ); + + expect(output.js).to.equal(fixture.js); + expect(output.css).to.equal(fixture.css); + }); +}); diff --git a/packages/pigment-react/tests/pigment.test.ts b/packages/pigment-react/tests/pigment.test.ts deleted file mode 100644 index 34addd06ead91f..00000000000000 --- a/packages/pigment-react/tests/pigment.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import * as fs from 'node:fs'; -import * as path from 'node:path'; -import { expect } from 'chai'; -import { asyncResolveFallback } from '@wyw-in-js/shared'; -import { TransformCacheCollection, transform, createFileReporter } from '@wyw-in-js/transform'; -import { preprocessor } from '@pigment-css/react/utils'; - -const files = fs.readdirSync(path.join(__dirname, 'fixtures')); - -const theme = { - palette: { - primary: { - main: 'red', - }, - }, - size: { - font: { - h1: '3rem', - }, - }, - components: { - MuiSlider: { - styleOverrides: { - rail: { - fontSize: '3rem', - }, - }, - }, - }, -}; - -describe('zero-runtime', () => { - files.forEach((file) => { - it(`test input file ${file}`, async () => { - if (file.includes('.output.')) { - return; - } - const cache = new TransformCacheCollection(); - const { emitter: eventEmitter } = createFileReporter(false); - const inputFilePath = path.join(__dirname, 'fixtures', file); - const outputFilePath = path.join(__dirname, 'fixtures', file.replace('.input.', '.output.')); - const outputCssFilePath = path.join( - __dirname, - 'fixtures', - file.replace('.input.js', '.output.css'), - ); - const inputContent = fs.readFileSync(inputFilePath, 'utf8'); - const outputContent = fs.readFileSync(outputFilePath, 'utf8'); - const outputCssContent = fs.readFileSync(outputCssFilePath, 'utf8'); - - const pluginOptions = { - themeArgs: { - theme, - }, - babelOptions: { - configFile: false, - babelrc: false, - }, - tagResolver(_source: string, tag: string) { - return require.resolve(`../exports/${tag}`); - }, - }; - const result = await transform( - { - options: { - filename: inputFilePath, - preprocessor, - pluginOptions, - }, - cache, - eventEmitter, - }, - inputContent, - asyncResolveFallback, - ); - - expect(result.cssText).to.equal(outputCssContent); - expect(result.code.trim()).to.equal(outputContent.trim()); - }); - }); -}); diff --git a/packages/pigment-react/tests/fixtures/styled.input.js b/packages/pigment-react/tests/styled/fixtures/styled.input.js similarity index 75% rename from packages/pigment-react/tests/fixtures/styled.input.js rename to packages/pigment-react/tests/styled/fixtures/styled.input.js index 0d08d32f0886b6..8e2c133a04dacd 100644 --- a/packages/pigment-react/tests/fixtures/styled.input.js +++ b/packages/pigment-react/tests/styled/fixtures/styled.input.js @@ -1,4 +1,4 @@ -import { styled, keyframes, css } from '@pigment-css/react'; +import { styled, keyframes } from '@pigment-css/react'; const rotateKeyframe = keyframes({ from: { @@ -14,16 +14,11 @@ const Component = styled.div(({ theme }) => ({ animation: `${rotateKeyframe} 2s ease-out 0s infinite`, })); -const cls1 = css` - color: ${({ theme }) => theme.palette.primary.main}; - font-size: ${({ theme }) => theme.size.font.h1}; -`; - const SliderRail = styled('span', { name: 'MuiSlider', slot: 'Rail', })` - display: block; + display: none; position: absolute; border-radius: inherit; background-color: currentColor; diff --git a/packages/pigment-react/tests/styled/fixtures/styled.output.css b/packages/pigment-react/tests/styled/fixtures/styled.output.css new file mode 100644 index 00000000000000..79471464008abb --- /dev/null +++ b/packages/pigment-react/tests/styled/fixtures/styled.output.css @@ -0,0 +1,28 @@ +@keyframes r1419f2q { + from { + transform: rotate(360deg); + } + to { + transform: rotate(0deg); + } +} +.c1vtarpi { + color: red; + animation: r1419f2q 2s ease-out 0s infinite; +} +.s1sjy0ja { + display: none; + position: absolute; + border-radius: inherit; + background-color: currentColor; + opacity: 0.38; + font-size: 3rem; +} +.s1sjy0ja-1 { + font-size: 3rem; +} +.s6hrafw { + display: block; + opacity: 0.38; + font-size: 3rem; +} diff --git a/packages/pigment-react/tests/styled/fixtures/styled.output.js b/packages/pigment-react/tests/styled/fixtures/styled.output.js new file mode 100644 index 00000000000000..d5519e6a7a196b --- /dev/null +++ b/packages/pigment-react/tests/styled/fixtures/styled.output.js @@ -0,0 +1,16 @@ +import { styled as _styled3 } from '@pigment-css/react'; +import { styled as _styled2 } from '@pigment-css/react'; +import { styled as _styled } from '@pigment-css/react'; +import _theme from '@pigment-css/react/theme'; +const Component = /*#__PURE__*/ _styled('div')({ + classes: ['c1vtarpi'], +}); +const SliderRail = /*#__PURE__*/ _styled2('span', { + name: 'MuiSlider', + slot: 'Rail', +})({ + classes: ['s1sjy0ja', 's1sjy0ja-1'], +}); +const SliderRail2 = /*#__PURE__*/ _styled3('span')({ + classes: ['s6hrafw'], +}); diff --git a/packages/pigment-react/tests/styled/styled.test.ts b/packages/pigment-react/tests/styled/styled.test.ts new file mode 100644 index 00000000000000..4827244fa29ca9 --- /dev/null +++ b/packages/pigment-react/tests/styled/styled.test.ts @@ -0,0 +1,40 @@ +import path from 'node:path'; +import { runTransformation, expect } from '../testUtils'; + +const theme = { + palette: { + primary: { + main: 'red', + }, + }, + size: { + font: { + h1: '3rem', + }, + }, + components: { + MuiSlider: { + styleOverrides: { + rail: { + fontSize: '3rem', + }, + }, + }, + }, +}; + +describe('Pigment CSS - styled', () => { + it('basics', async () => { + const { output, fixture } = await runTransformation( + path.join(__dirname, 'fixtures/styled.input.js'), + { + themeArgs: { + theme, + }, + }, + ); + + expect(output.js).to.equal(fixture.js); + expect(output.css).to.equal(fixture.css); + }); +}); diff --git a/packages/pigment-react/tests/testUtils.ts b/packages/pigment-react/tests/testUtils.ts new file mode 100644 index 00000000000000..88ff6ab98a3c38 --- /dev/null +++ b/packages/pigment-react/tests/testUtils.ts @@ -0,0 +1,91 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { expect as chaiExpect } from 'chai'; +import { asyncResolveFallback } from '@wyw-in-js/shared'; +import { TransformCacheCollection, transform, createFileReporter } from '@wyw-in-js/transform'; +import { preprocessor } from '@pigment-css/react/utils'; +import * as prettier from 'prettier'; + +const shouldUpdateOutput = process.env.UPDATE_FIXTURES === 'true'; + +export async function runTransformation( + absolutePath: string, + options?: { themeArgs?: { theme?: any } }, +) { + const cache = new TransformCacheCollection(); + const { emitter: eventEmitter } = createFileReporter(false); + const inputFilePath = absolutePath; + const outputFilePath = absolutePath.replace('.input.', '.output.'); + const outputCssFilePath = absolutePath.replace('.input.js', '.output.css'); + + const inputContent = fs.readFileSync(inputFilePath, 'utf8'); + let outputContent = fs.existsSync(outputFilePath) ? fs.readFileSync(outputFilePath, 'utf8') : ''; + let outputCssContent = fs.existsSync(outputCssFilePath) + ? fs.readFileSync(outputCssFilePath, 'utf8') + : ''; + + const pluginOptions = { + themeArgs: { + theme: options?.themeArgs?.theme, + }, + babelOptions: { + configFile: false, + babelrc: false, + }, + tagResolver(_source: string, tag: string) { + return require.resolve(`../exports/${tag}`); + }, + }; + const result = await transform( + { + options: { + filename: inputFilePath, + preprocessor, + pluginOptions, + }, + cache, + eventEmitter, + }, + inputContent, + asyncResolveFallback, + ); + + const prettierConfig = await prettier.resolveConfig( + path.join(process.cwd(), 'prettier.config.js'), + ); + const formattedJs = await prettier.format(result.code, { + ...prettierConfig, + parser: 'babel', + }); + const formattedCss = await prettier.format(result.cssText ?? '', { + ...prettierConfig, + parser: 'css', + }); + + if (!outputContent || shouldUpdateOutput) { + fs.writeFileSync(outputFilePath, formattedJs, 'utf-8'); + outputContent = formattedJs; + } + + if (!outputCssContent || shouldUpdateOutput) { + fs.writeFileSync(outputCssFilePath, formattedCss, 'utf-8'); + outputCssContent = formattedCss; + } + + return { + output: { + js: formattedJs, + css: formattedCss, + }, + fixture: { + js: outputContent, + css: outputCssContent, + }, + }; +} + +export function expect(val: any): ReturnType { + const CUSTOM_ERROR = + 'The file contents have changed. Run "test:update" command to update the file if this is expected.'; + return chaiExpect(val, CUSTOM_ERROR); +} diff --git a/packages/pigment-react/tsconfig.json b/packages/pigment-react/tsconfig.json index 1e4e9fefe33535..55ad292e876743 100644 --- a/packages/pigment-react/tsconfig.json +++ b/packages/pigment-react/tsconfig.json @@ -12,7 +12,8 @@ "@mui/system/*": ["./packages/mui-system/src/*"], "@mui/utils": ["./packages/mui-utils/src"], "@mui/utils/*": ["./packages/mui-utils/src/*"] - } + }, + "types": ["node", "mocha"] }, "include": ["src/**/*.ts"], "exclude": ["./tsup.config.ts"] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa2fd7b0363311..a12a5fec79085a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2161,6 +2161,9 @@ importers: '@types/babel__helper-plugin-utils': specifier: ^7.10.3 version: 7.10.3 + '@types/chai': + specifier: ^4.3.12 + version: 4.3.12 '@types/cssesc': specifier: ^3.0.2 version: 3.0.2 @@ -2179,6 +2182,9 @@ importers: chai: specifier: ^4.4.1 version: 4.4.1 + prettier: + specifier: ^3.2.5 + version: 3.2.5 react: specifier: ^18.2.0 version: 18.2.0