407 lines
12 KiB
TypeScript
407 lines
12 KiB
TypeScript
import {
|
|
cleanupProject,
|
|
listFiles,
|
|
newProject,
|
|
readFile,
|
|
removeFile,
|
|
runCLI,
|
|
uniq,
|
|
updateFile,
|
|
updateProjectConfig,
|
|
} from '@nx/e2e/utils';
|
|
|
|
// TODO(Colum or Leosvel): Investigate and fix these tests
|
|
|
|
describe('Tailwind support', () => {
|
|
let project: string;
|
|
|
|
const defaultButtonBgColor = 'bg-blue-700';
|
|
|
|
const buildLibWithTailwind = {
|
|
name: uniq('build-lib-with-tailwind'),
|
|
buttonBgColor: 'bg-green-800',
|
|
};
|
|
const pubLibWithTailwind = {
|
|
name: uniq('pub-lib-with-tailwind'),
|
|
buttonBgColor: 'bg-red-900',
|
|
};
|
|
|
|
const spacing = {
|
|
root: {
|
|
sm: '2px',
|
|
md: '4px',
|
|
lg: '8px',
|
|
},
|
|
projectVariant1: {
|
|
sm: '1px',
|
|
md: '2px',
|
|
lg: '4px',
|
|
},
|
|
projectVariant2: {
|
|
sm: '4px',
|
|
md: '8px',
|
|
lg: '16px',
|
|
},
|
|
projectVariant3: {
|
|
sm: '8px',
|
|
md: '16px',
|
|
lg: '32px',
|
|
},
|
|
};
|
|
|
|
const createWorkspaceTailwindConfigFile = () => {
|
|
const tailwindConfigFile = 'tailwind.config.js';
|
|
|
|
const tailwindConfig = `module.exports = {
|
|
content: [
|
|
'./apps/**/!(*.stories|*.spec).{ts,html}',
|
|
'./libs/**/!(*.stories|*.spec).{ts,html}',
|
|
],
|
|
theme: {
|
|
spacing: {
|
|
sm: '${spacing.root.sm}',
|
|
md: '${spacing.root.md}',
|
|
lg: '${spacing.root.lg}',
|
|
},
|
|
},
|
|
plugins: [],
|
|
};
|
|
`;
|
|
|
|
updateFile(tailwindConfigFile, tailwindConfig);
|
|
};
|
|
|
|
const createTailwindConfigFile = (
|
|
tailwindConfigFile = 'tailwind.config.js',
|
|
libSpacing: typeof spacing['projectVariant1']
|
|
) => {
|
|
const tailwindConfig = `const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
|
|
const { join } = require('path');
|
|
|
|
module.exports = {
|
|
content: [
|
|
join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
|
|
...createGlobPatternsForDependencies(__dirname),
|
|
],
|
|
theme: {
|
|
spacing: {
|
|
sm: '${libSpacing.sm}',
|
|
md: '${libSpacing.md}',
|
|
lg: '${libSpacing.lg}',
|
|
},
|
|
},
|
|
plugins: [],
|
|
};
|
|
`;
|
|
|
|
updateFile(tailwindConfigFile, tailwindConfig);
|
|
};
|
|
|
|
const updateTailwindConfig = (
|
|
tailwindConfigPath: string,
|
|
projectSpacing: typeof spacing['root']
|
|
) => {
|
|
const tailwindConfig = readFile(tailwindConfigPath);
|
|
|
|
const tailwindConfigUpdated = tailwindConfig.replace(
|
|
'theme: {',
|
|
`theme: {
|
|
spacing: {
|
|
sm: '${projectSpacing.sm}',
|
|
md: '${projectSpacing.md}',
|
|
lg: '${projectSpacing.lg}',
|
|
},`
|
|
);
|
|
|
|
updateFile(tailwindConfigPath, tailwindConfigUpdated);
|
|
};
|
|
|
|
beforeAll(() => {
|
|
project = newProject();
|
|
|
|
// Create tailwind config in the workspace root
|
|
createWorkspaceTailwindConfigFile();
|
|
});
|
|
|
|
afterAll(() => {
|
|
cleanupProject();
|
|
});
|
|
|
|
describe('Libraries', () => {
|
|
const createLibComponent = (
|
|
lib: string,
|
|
buttonBgColor: string = defaultButtonBgColor
|
|
) => {
|
|
updateFile(
|
|
`libs/${lib}/src/lib/foo.component.ts`,
|
|
`import { Component } from '@angular/core';
|
|
|
|
@Component({
|
|
selector: '${project}-foo',
|
|
template: '<button class="custom-btn text-white ${buttonBgColor}">Click me!</button>',
|
|
styles: [\`
|
|
.custom-btn {
|
|
@apply m-md p-sm;
|
|
}
|
|
\`]
|
|
})
|
|
export class FooComponent {}
|
|
`
|
|
);
|
|
|
|
updateFile(
|
|
`libs/${lib}/src/lib/${lib}.module.ts`,
|
|
`import { NgModule } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { FooComponent } from './foo.component';
|
|
|
|
@NgModule({
|
|
imports: [CommonModule],
|
|
declarations: [FooComponent],
|
|
exports: [FooComponent],
|
|
})
|
|
export class LibModule {}
|
|
`
|
|
);
|
|
|
|
updateFile(
|
|
`libs/${lib}/src/index.ts`,
|
|
`export * from './lib/foo.component';
|
|
export * from './lib/${lib}.module';
|
|
`
|
|
);
|
|
};
|
|
|
|
const assertLibComponentStyles = (
|
|
lib: string,
|
|
libSpacing: typeof spacing['root']
|
|
) => {
|
|
const builtComponentContent = readFile(
|
|
`dist/libs/${lib}/esm2022/lib/foo.component.mjs`
|
|
);
|
|
let expectedStylesRegex = new RegExp(
|
|
`styles: \\[\\"\\.custom\\-btn(\\[_ngcontent\\-%COMP%\\])?{margin:${libSpacing.md};padding:${libSpacing.sm}}(\\\\n)?\\"\\]`
|
|
);
|
|
|
|
expect(builtComponentContent).toMatch(expectedStylesRegex);
|
|
};
|
|
|
|
it('should generate a buildable library with tailwind and build correctly', () => {
|
|
runCLI(
|
|
`generate @nx/angular:lib ${buildLibWithTailwind.name} --buildable --add-tailwind --no-interactive`
|
|
);
|
|
updateTailwindConfig(
|
|
`libs/${buildLibWithTailwind.name}/tailwind.config.js`,
|
|
spacing.projectVariant1
|
|
);
|
|
createLibComponent(
|
|
buildLibWithTailwind.name,
|
|
buildLibWithTailwind.buttonBgColor
|
|
);
|
|
|
|
runCLI(`build ${buildLibWithTailwind.name}`);
|
|
|
|
assertLibComponentStyles(
|
|
buildLibWithTailwind.name,
|
|
spacing.projectVariant1
|
|
);
|
|
});
|
|
|
|
it('should set up tailwind in a previously generated buildable library and build correctly', () => {
|
|
const buildLibSetupTailwind = uniq('build-lib-setup-tailwind');
|
|
runCLI(
|
|
`generate @nx/angular:lib ${buildLibSetupTailwind} --buildable --no-interactive`
|
|
);
|
|
runCLI(
|
|
`generate @nx/angular:setup-tailwind ${buildLibSetupTailwind} --no-interactive`
|
|
);
|
|
updateTailwindConfig(
|
|
`libs/${buildLibSetupTailwind}/tailwind.config.js`,
|
|
spacing.projectVariant2
|
|
);
|
|
createLibComponent(buildLibSetupTailwind);
|
|
|
|
runCLI(`build ${buildLibSetupTailwind}`);
|
|
|
|
assertLibComponentStyles(buildLibSetupTailwind, spacing.projectVariant2);
|
|
});
|
|
|
|
it('should correctly build a buildable library with a tailwind.config.js file in the project root or workspace root', () => {
|
|
const buildLibNoProjectConfig = uniq('build-lib-no-project-config');
|
|
runCLI(
|
|
`generate @nx/angular:lib ${buildLibNoProjectConfig} --buildable --no-interactive`
|
|
);
|
|
createTailwindConfigFile(
|
|
`libs/${buildLibNoProjectConfig}/tailwind.config.js`,
|
|
spacing.projectVariant3
|
|
);
|
|
createLibComponent(buildLibNoProjectConfig);
|
|
|
|
runCLI(`build ${buildLibNoProjectConfig}`);
|
|
|
|
assertLibComponentStyles(
|
|
buildLibNoProjectConfig,
|
|
spacing.projectVariant3
|
|
);
|
|
|
|
// remove tailwind.config.js file from the project root to test the one in the workspace root
|
|
removeFile(`libs/${buildLibNoProjectConfig}/tailwind.config.js`);
|
|
|
|
runCLI(`build ${buildLibNoProjectConfig}`);
|
|
|
|
assertLibComponentStyles(buildLibNoProjectConfig, spacing.root);
|
|
});
|
|
|
|
it('should generate a publishable library with tailwind and build correctly', () => {
|
|
runCLI(
|
|
`generate @nx/angular:lib ${pubLibWithTailwind.name} --publishable --add-tailwind --importPath=@${project}/${pubLibWithTailwind.name} --no-interactive`
|
|
);
|
|
updateTailwindConfig(
|
|
`libs/${pubLibWithTailwind.name}/tailwind.config.js`,
|
|
spacing.projectVariant1
|
|
);
|
|
createLibComponent(
|
|
pubLibWithTailwind.name,
|
|
pubLibWithTailwind.buttonBgColor
|
|
);
|
|
|
|
runCLI(`build ${pubLibWithTailwind.name}`);
|
|
|
|
assertLibComponentStyles(
|
|
pubLibWithTailwind.name,
|
|
spacing.projectVariant1
|
|
);
|
|
});
|
|
|
|
it('should set up tailwind in a previously generated publishable library and build correctly', () => {
|
|
const pubLibSetupTailwind = uniq('pub-lib-setup-tailwind');
|
|
runCLI(
|
|
`generate @nx/angular:lib ${pubLibSetupTailwind} --publishable --importPath=@${project}/${pubLibSetupTailwind} --no-interactive`
|
|
);
|
|
runCLI(
|
|
`generate @nx/angular:setup-tailwind ${pubLibSetupTailwind} --no-interactive`
|
|
);
|
|
updateTailwindConfig(
|
|
`libs/${pubLibSetupTailwind}/tailwind.config.js`,
|
|
spacing.projectVariant2
|
|
);
|
|
createLibComponent(pubLibSetupTailwind);
|
|
|
|
runCLI(`build ${pubLibSetupTailwind}`);
|
|
|
|
assertLibComponentStyles(pubLibSetupTailwind, spacing.projectVariant2);
|
|
});
|
|
|
|
it('should correctly build a publishable library with a tailwind.config.js file in the project root or workspace root', () => {
|
|
const pubLibNoProjectConfig = uniq('pub-lib-no-project-config');
|
|
runCLI(
|
|
`generate @nx/angular:lib ${pubLibNoProjectConfig} --publishable --importPath=@${project}/${pubLibNoProjectConfig} --no-interactive`
|
|
);
|
|
createTailwindConfigFile(
|
|
`libs/${pubLibNoProjectConfig}/tailwind.config.js`,
|
|
spacing.projectVariant3
|
|
);
|
|
createLibComponent(pubLibNoProjectConfig);
|
|
|
|
runCLI(`build ${pubLibNoProjectConfig}`);
|
|
|
|
assertLibComponentStyles(pubLibNoProjectConfig, spacing.projectVariant3);
|
|
|
|
// remove tailwind.config.js file from the project root to test the one in the workspace root
|
|
removeFile(`libs/${pubLibNoProjectConfig}/tailwind.config.js`);
|
|
|
|
runCLI(`build ${pubLibNoProjectConfig}`);
|
|
|
|
assertLibComponentStyles(pubLibNoProjectConfig, spacing.root);
|
|
});
|
|
});
|
|
|
|
describe('Applications', () => {
|
|
const updateAppComponent = (app: string) => {
|
|
updateFile(
|
|
`apps/${app}/src/app/app.component.html`,
|
|
`<button class="custom-btn text-white">Click me!</button>`
|
|
);
|
|
|
|
updateFile(
|
|
`apps/${app}/src/app/app.component.css`,
|
|
`.custom-btn {
|
|
@apply m-md p-sm;
|
|
}`
|
|
);
|
|
};
|
|
|
|
const readAppStylesBundle = (app: string) => {
|
|
const stylesBundlePath = listFiles(`dist/apps/${app}`).find((file) =>
|
|
file.startsWith('styles.')
|
|
);
|
|
const stylesBundle = readFile(`dist/apps/${app}/${stylesBundlePath}`);
|
|
|
|
return stylesBundle;
|
|
};
|
|
|
|
const assertAppComponentStyles = (
|
|
app: string,
|
|
appSpacing: typeof spacing['root']
|
|
) => {
|
|
const mainBundlePath = listFiles(`dist/apps/${app}`).find((file) =>
|
|
file.startsWith('main.')
|
|
);
|
|
const mainBundle = readFile(`dist/apps/${app}/${mainBundlePath}`);
|
|
let expectedStylesRegex = new RegExp(
|
|
`styles:\\[\\"\\.custom\\-btn\\[_ngcontent\\-%COMP%\\]{margin:${appSpacing.md};padding:${appSpacing.sm}}\\"\\]`
|
|
);
|
|
|
|
expect(mainBundle).toMatch(expectedStylesRegex);
|
|
};
|
|
|
|
it('should build correctly and only output the tailwind utilities used', async () => {
|
|
const appWithTailwind = uniq('app-with-tailwind');
|
|
runCLI(
|
|
`generate @nx/angular:app ${appWithTailwind} --add-tailwind --no-interactive`
|
|
);
|
|
await updateProjectConfig(appWithTailwind, (config) => {
|
|
config.targets.build.executor = '@nx/angular:webpack-browser';
|
|
config.targets.build.options = {
|
|
...config.targets.build.options,
|
|
buildLibsFromSource: false,
|
|
};
|
|
return config;
|
|
});
|
|
updateTailwindConfig(
|
|
`apps/${appWithTailwind}/tailwind.config.js`,
|
|
spacing.projectVariant1
|
|
);
|
|
updateFile(
|
|
`apps/${appWithTailwind}/src/app/app.module.ts`,
|
|
`import { NgModule } from '@angular/core';
|
|
import { BrowserModule } from '@angular/platform-browser';
|
|
import { LibModule as LibModule1 } from '@${project}/${buildLibWithTailwind.name}';
|
|
import { LibModule as LibModule2 } from '@${project}/${pubLibWithTailwind.name}';
|
|
|
|
import { AppComponent } from './app.component';
|
|
|
|
@NgModule({
|
|
declarations: [AppComponent],
|
|
imports: [BrowserModule, LibModule1, LibModule2],
|
|
providers: [],
|
|
bootstrap: [AppComponent],
|
|
})
|
|
export class AppModule {}
|
|
`
|
|
);
|
|
updateAppComponent(appWithTailwind);
|
|
|
|
runCLI(`build ${appWithTailwind}`);
|
|
|
|
assertAppComponentStyles(appWithTailwind, spacing.projectVariant1);
|
|
let stylesBundle = readAppStylesBundle(appWithTailwind);
|
|
expect(stylesBundle).toContain('.text-white');
|
|
expect(stylesBundle).not.toContain('.text-black');
|
|
expect(stylesBundle).toContain(`.${buildLibWithTailwind.buttonBgColor}`);
|
|
expect(stylesBundle).toContain(`.${pubLibWithTailwind.buttonBgColor}`);
|
|
expect(stylesBundle).not.toContain(`.${defaultButtonBgColor}`);
|
|
});
|
|
});
|
|
});
|