Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for CSS Modules with React and Preact #1343

Merged
merged 2 commits into from
Sep 20, 2024
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
13 changes: 13 additions & 0 deletions fixtures/preact-css-modules/components/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { h } from 'preact';
import './styles.css';
import './styles.less';
import './styles.scss';
import './styles.stylus';
import stylesCss from './styles.module.css?module';
import stylesLess from './styles.module.less?module';
import stylesScss from './styles.module.scss?module';
import stylesStylus from './styles.module.stylus?module';

export default function App() {
return <div className={`red large justified lowercase ${stylesCss.italic} ${stylesLess.underline} ${stylesScss.bold} ${stylesStylus.rtl}`}></div>
}
3 changes: 3 additions & 0 deletions fixtures/preact-css-modules/components/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.red {
color: red;
}
3 changes: 3 additions & 0 deletions fixtures/preact-css-modules/components/styles.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.justified {
text-align: justify;
}
3 changes: 3 additions & 0 deletions fixtures/preact-css-modules/components/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.italic {
font-style: italic;
}
3 changes: 3 additions & 0 deletions fixtures/preact-css-modules/components/styles.module.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.underline {
text-decoration: underline;
}
3 changes: 3 additions & 0 deletions fixtures/preact-css-modules/components/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.bold {
font-weight: bold;
}
2 changes: 2 additions & 0 deletions fixtures/preact-css-modules/components/styles.module.stylus
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.rtl
direction: rtl;
3 changes: 3 additions & 0 deletions fixtures/preact-css-modules/components/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.large {
font-size: 50px;
}
2 changes: 2 additions & 0 deletions fixtures/preact-css-modules/components/styles.stylus
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.lowercase
text-transform: lowercase
5 changes: 5 additions & 0 deletions fixtures/preact-css-modules/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { h, render } from 'preact';

import App from './components/App';

render(<App />, document.getElementById('app'));
12 changes: 12 additions & 0 deletions fixtures/react-css-modules/components/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import './styles.css';
import './styles.less';
import './styles.scss';
import './styles.stylus';
import stylesCss from './styles.module.css?module';
import stylesLess from './styles.module.less?module';
import stylesScss from './styles.module.scss?module';
import stylesStylus from './styles.module.stylus?module';

export default function App() {
return <div className={`red large justified lowercase ${stylesCss.italic} ${stylesLess.underline} ${stylesScss.bold} ${stylesStylus.rtl}`}></div>
}
3 changes: 3 additions & 0 deletions fixtures/react-css-modules/components/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.red {
color: red;
}
3 changes: 3 additions & 0 deletions fixtures/react-css-modules/components/styles.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.justified {
text-align: justify;
}
3 changes: 3 additions & 0 deletions fixtures/react-css-modules/components/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.italic {
font-style: italic;
}
3 changes: 3 additions & 0 deletions fixtures/react-css-modules/components/styles.module.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.underline {
text-decoration: underline;
}
3 changes: 3 additions & 0 deletions fixtures/react-css-modules/components/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.bold {
font-weight: bold;
}
2 changes: 2 additions & 0 deletions fixtures/react-css-modules/components/styles.module.stylus
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.rtl
direction: rtl;
3 changes: 3 additions & 0 deletions fixtures/react-css-modules/components/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.large {
font-size: 50px;
}
2 changes: 2 additions & 0 deletions fixtures/react-css-modules/components/styles.stylus
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.lowercase
text-transform: lowercase
6 changes: 6 additions & 0 deletions fixtures/react-css-modules/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {createRoot} from 'react-dom/client';
import App from './components/App';

const root = createRoot(document.getElementById('app'));

root.render(<App />);
5 changes: 4 additions & 1 deletion lib/loaders/babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ module.exports = {
if (webpackConfig.useReact) {
loaderFeatures.ensurePackagesExistAndAreCorrectVersion('react');

babelConfig.presets.push(require.resolve('@babel/preset-react'));
babelConfig.presets.push([require.resolve('@babel/preset-react'), {
// TODO: To remove when Babel 8, "automatic" will become the default value
runtime: 'automatic',
}]);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick-win, let's bump @babel/preset-react to ^7.9.0 to use runtime: 'automatic'

}

if (webpackConfig.usePreact) {
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"@babel/eslint-parser": "^7.17.0",
"@babel/plugin-transform-react-jsx": "^7.12.11",
"@babel/preset-env": "^7.16.0",
"@babel/preset-react": "^7.0.0",
"@babel/preset-react": "^7.9.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bumping the dev dependency alone is not enough. For our users, what matters for the package managers is the peerDependencies section.

btw, we should update our features utility to make it enforce versions based on peerDependencies rather than devDependencies now that we properly define peer dependencies. Or we should drop that runtime check entirely in favor of letting yarn/npm/pnpm show the warning at installation time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, forgot about peerDeps, thanks

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, we should update our features utility to make it enforce versions based on peerDependencies rather than devDependencies now that we properly define peer dependencies. Or we should drop that runtime check entirely in favor of letting yarn/npm/pnpm show the warning at installation time.

Totally! Maybe we can do 1) for v5, and 2) for v6?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the first change can even be done in a minor version (as that's about changing the internal implementation only). Our peerDependencies is already what package managers will report.

"@babel/preset-typescript": "^7.0.0",
"@hotwired/stimulus": "^3.0.0",
"@symfony/mock-module": "file:fixtures/stimulus/mock-module",
Expand Down Expand Up @@ -81,6 +81,8 @@
"preact": "^10.5.0",
"preact-compat": "^3.17.0",
"puppeteer": "^23.2.2",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"sass": "^1.17.0",
"sass-loader": "^16.0.1",
"sinon": "^14.0.0",
Expand All @@ -102,7 +104,7 @@
"@babel/core": "^7.17.0",
"@babel/plugin-transform-react-jsx": "^7.12.11",
"@babel/preset-env": "^7.16.0",
"@babel/preset-react": "^7.0.0",
"@babel/preset-react": "^7.9.0",
"@babel/preset-typescript": "^7.0.0",
"@symfony/stimulus-bridge": "^3.0.0",
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
Expand Down
172 changes: 168 additions & 4 deletions test/functional.js
Original file line number Diff line number Diff line change
Expand Up @@ -1684,13 +1684,179 @@ module.exports = {
expectClassDeclaration('large'); // Standard SCSS
expectClassDeclaration('justified'); // Standard Less
expectClassDeclaration('lowercase'); // Standard Stylus
expectClassDeclaration('block'); // Standard Postcss
expectClassDeclaration('block'); // Standard PostCSS

expectClassDeclaration('italic_foo'); // CSS Module
expectClassDeclaration('bold_foo'); // SCSS Module
expectClassDeclaration('underline_foo'); // Less Module
expectClassDeclaration('rtl_foo'); // Stylus Module
expectClassDeclaration('hidden_foo'); // PostCSS Module

testSetup.requestTestPage(
browser,
path.join(config.getContext(), 'www'),
[
'build/runtime.js',
'build/main.js'
],
async({ page }) => {
const divClassArray = await page.evaluate(() => Array.from(document.body.querySelector('#app > div').classList.values()));

expect(divClassArray.includes('red')).to.be.true; // Standard CSS
expect(divClassArray.includes('large')).to.be.true; // Standard SCSS
expect(divClassArray.includes('justified')).to.be.true; // Standard Less
expect(divClassArray.includes('lowercase')).to.be.true; // Standard Stylus
expect(divClassArray.includes('block')).to.be.true; // Standard PostCSS

expect(divClassArray.includes('italic_foo')).to.be.true; // CSS module
expect(divClassArray.includes('bold_foo')).to.be.true; // SCSS module
expect(divClassArray.includes('underline_foo')).to.be.true; // Less module
expect(divClassArray.includes('rtl_foo')).to.be.true; // Stylus module
expect(divClassArray.includes('hidden_foo')).to.be.true; // PostCSS module

done();
}
);
});
});

it('React supports CSS/Sass/Less/Stylus modules', (done) => {
const appDir = testSetup.createTestAppDir();
const config = testSetup.createWebpackConfig(appDir, 'www/build', 'dev');
config.enableSingleRuntimeChunk();
config.setPublicPath('/build');
config.addEntry('main', './react-css-modules/main.js');
config.enableReactPreset();
config.enableSassLoader();
config.enableLessLoader();
config.enableStylusLoader();
config.configureCssLoader(options => {
// Remove hashes from local ident names
// since they are not always the same.
if (options.modules) {
options.modules.localIdentName = '[local]_foo';
}
});

// Enable the PostCSS loader so we can use `lang="postcss"`
config.enablePostCssLoader();
fs.writeFileSync(
path.join(appDir, 'postcss.config.js'),
`
module.exports = {
plugins: [
require('autoprefixer')()
]
} `
);

testSetup.runWebpack(config, (webpackAssert) => {
expect(config.outputPath).to.be.a.directory().with.deep.files([
'main.js',
'main.css',
'manifest.json',
'entrypoints.json',
'runtime.js',
]);

const expectClassDeclaration = (className) => {
webpackAssert.assertOutputFileContains(
'main.css',
`.${className} {`
);
};

expectClassDeclaration('red'); // Standard CSS
expectClassDeclaration('large'); // Standard SCSS
expectClassDeclaration('justified'); // Standard Less
expectClassDeclaration('lowercase'); // Standard Stylus

expectClassDeclaration('italic_foo'); // CSS Module
expectClassDeclaration('bold_foo'); // SCSS Module
expectClassDeclaration('underline_foo'); // Less Module
expectClassDeclaration('rtl_foo'); // Stylus Module

testSetup.requestTestPage(
browser,
path.join(config.getContext(), 'www'),
[
'build/runtime.js',
'build/main.js'
],
async({ page }) => {
const divClassArray = await page.evaluate(() => Array.from(document.body.querySelector('#app > div').classList.values()));

expect(divClassArray.includes('red')).to.be.true; // Standard CSS
expect(divClassArray.includes('large')).to.be.true; // Standard SCSS
expect(divClassArray.includes('justified')).to.be.true; // Standard Less
expect(divClassArray.includes('lowercase')).to.be.true; // Standard Stylus

expect(divClassArray.includes('italic_foo')).to.be.true; // CSS module
expect(divClassArray.includes('bold_foo')).to.be.true; // SCSS module
expect(divClassArray.includes('underline_foo')).to.be.true; // Less module
expect(divClassArray.includes('rtl_foo')).to.be.true; // Stylus module

done();
}
);
});
});

it('Preact supports CSS/Sass/Less/Stylus modules', (done) => {
const appDir = testSetup.createTestAppDir();
const config = testSetup.createWebpackConfig(appDir, 'www/build', 'dev');
config.enableSingleRuntimeChunk();
config.setPublicPath('/build');
config.addEntry('main', './preact-css-modules/main.js');
config.enablePreactPreset();
config.enableSassLoader();
config.enableLessLoader();
config.enableStylusLoader();
config.configureCssLoader(options => {
// Remove hashes from local ident names
// since they are not always the same.
if (options.modules) {
options.modules.localIdentName = '[local]_foo';
}
});

// Enable the PostCSS loader so we can use `lang="postcss"`
config.enablePostCssLoader();
fs.writeFileSync(
path.join(appDir, 'postcss.config.js'),
`
module.exports = {
plugins: [
require('autoprefixer')()
]
} `
);

testSetup.runWebpack(config, (webpackAssert) => {
expect(config.outputPath).to.be.a.directory().with.deep.files([
'main.js',
'main.css',
'manifest.json',
'entrypoints.json',
'runtime.js',
]);

const expectClassDeclaration = (className) => {
webpackAssert.assertOutputFileContains(
'main.css',
`.${className} {`
);
};

expectClassDeclaration('red'); // Standard CSS
expectClassDeclaration('large'); // Standard SCSS
expectClassDeclaration('justified'); // Standard Less
expectClassDeclaration('lowercase'); // Standard Stylus

expectClassDeclaration('italic_foo'); // CSS Module
expectClassDeclaration('bold_foo'); // SCSS Module
expectClassDeclaration('underline_foo'); // Less Module
expectClassDeclaration('rtl_foo'); // Stylus Module
expectClassDeclaration('hidden_foo'); // Stylus Module

testSetup.requestTestPage(
browser,
Expand All @@ -1706,13 +1872,11 @@ module.exports = {
expect(divClassArray.includes('large')).to.be.true; // Standard SCSS
expect(divClassArray.includes('justified')).to.be.true; // Standard Less
expect(divClassArray.includes('lowercase')).to.be.true; // Standard Stylus
expect(divClassArray.includes('block')).to.be.true; // Standard Stylus

expect(divClassArray.includes('italic_foo')).to.be.true; // CSS module
expect(divClassArray.includes('bold_foo')).to.be.true; // SCSS module
expect(divClassArray.includes('underline_foo')).to.be.true; // Less module
expect(divClassArray.includes('rtl_foo')).to.be.true; // Stylus module
expect(divClassArray.includes('hidden_foo')).to.be.true; // Stylus module

done();
}
Expand Down
18 changes: 16 additions & 2 deletions test/loaders/babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,23 @@ describe('loaders/babel', () => {

// env, react & foo
expect(actualLoaders[0].options.presets).to.have.lengthOf(3);
expect(actualLoaders[0].options.presets).to.include(require.resolve('@babel/preset-react'));
expect(actualLoaders[0].options.presets[0]).to.deep.equal([
require.resolve('@babel/preset-env'),
{
corejs: null,
modules: false,
targets: {},
useBuiltIns: false,
},
]);
expect(actualLoaders[0].options.presets[1]).to.deep.equal([
require.resolve('@babel/preset-react'),
{
runtime: 'automatic',
}
]);
// foo is also still there, not overridden
expect(actualLoaders[0].options.presets).to.include('foo');
expect(actualLoaders[0].options.presets[2]).to.equal('foo');
});

it('getLoaders() with preact', () => {
Expand Down
Loading