nx/packages/angular/src/generators/setup-tailwind/setup-tailwind.application.spec.ts

452 lines
15 KiB
TypeScript

import * as devkit from '@nx/devkit';
import {
addProjectConfiguration,
readJson,
readProjectConfiguration,
Tree,
updateProjectConfiguration,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { setupTailwindGenerator } from './setup-tailwind';
import {
autoprefixerVersion,
postcssVersion,
tailwindVersion,
} from '../../utils/versions';
describe('setupTailwind generator', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
jest.clearAllMocks();
});
it('should fail when the project does not exist', async () => {
await expect(
setupTailwindGenerator(tree, { project: 'not-found' })
).rejects.toThrow();
});
describe('application', () => {
const project = 'app1';
beforeEach(() => {
addProjectConfiguration(tree, project, {
name: project,
projectType: 'application',
root: `apps/${project}`,
sourceRoot: `apps/${project}/src`,
});
});
it('should throw when tailwind is installed as a dependency with a version lower than 2.0.0', async () => {
tree.write(
'package.json',
JSON.stringify({ dependencies: { tailwindcss: '^1.99.99' } })
);
await expect(setupTailwindGenerator(tree, { project })).rejects.toThrow(
`Tailwind CSS version "^1.99.99" is not supported. Please upgrade to v2.0.0 or higher.`
);
});
it('should throw when tailwind is installed as a devDependency with a version lower than 2.0.0', async () => {
tree.write(
'package.json',
JSON.stringify({ devDependencies: { tailwindcss: '^1.99.99' } })
);
await expect(setupTailwindGenerator(tree, { project })).rejects.toThrow(
`Tailwind CSS version "^1.99.99" is not supported. Please upgrade to v2.0.0 or higher.`
);
});
it('should throw when there is a tailwind.config.js file in the project', async () => {
tree.write(`apps/${project}/tailwind.config.js`, '');
await expect(setupTailwindGenerator(tree, { project })).rejects.toThrow(
expect.objectContaining({
message: expect.stringContaining(
`The "tailwind.config.js" file already exists in the project "${project}". Are you sure this is the right project to set up Tailwind?`
),
})
);
});
it('should throw when the provided styles entry point is not found', async () => {
const stylesEntryPoint = `apps/${project}/src/foo.scss`;
await expect(
setupTailwindGenerator(tree, { project, stylesEntryPoint })
).rejects.toThrow(
`The provided styles entry point "${stylesEntryPoint}" could not be found.`
);
});
it('should throw when the styles entry point is not provided and it is not found', async () => {
await expect(setupTailwindGenerator(tree, { project })).rejects.toThrow(
expect.objectContaining({
message: expect.stringContaining(
`Could not find a styles entry point for project "${project}"`
),
})
);
});
it('should throw when styles is not configured in the build config', async () => {
const projectConfig = readProjectConfiguration(tree, project);
projectConfig.targets = {
build: {
executor: '@nx/angular:webpack-browser',
options: {},
},
};
await expect(setupTailwindGenerator(tree, { project })).rejects.toThrow(
expect.objectContaining({
message: expect.stringContaining(
`Could not find a styles entry point for project "${project}"`
),
})
);
});
it('should throw when the styles configured in the build config do not exist', async () => {
const stylesEntryPoint = `apps/${project}/src/custom-styles-entry-point.scss`;
const projectConfig = readProjectConfiguration(tree, project);
projectConfig.targets = {
build: {
executor: '@nx/angular:webpack-browser',
options: {
styles: ['node_modules/awesome-ds/styles.css', stylesEntryPoint],
},
},
};
await expect(setupTailwindGenerator(tree, { project })).rejects.toThrow(
expect.objectContaining({
message: expect.stringContaining(
`Could not find a styles entry point for project "${project}"`
),
})
);
});
it('should throw when no styles within the project root are configured in the build config', async () => {
const projectConfig = readProjectConfiguration(tree, project);
projectConfig.targets = {
build: {
executor: '@nx/angular:webpack-browser',
options: {
styles: ['node_modules/awesome-ds/styles.css'],
},
},
};
await expect(setupTailwindGenerator(tree, { project })).rejects.toThrow(
expect.objectContaining({
message: expect.stringContaining(
`Could not find a styles entry point for project "${project}"`
),
})
);
});
it('should throw when the style inside the project root specified in the build config as an object has "inject: false"', async () => {
const stylesEntryPoint = `apps/${project}/src/custom-styles-entry-point.scss`;
tree.write(stylesEntryPoint, 'p { margin: 0; }');
const projectConfig = readProjectConfiguration(tree, project);
projectConfig.targets = {
build: {
executor: '@nx/angular:webpack-browser',
options: {
styles: [
'node_modules/awesome-ds/styles.css',
{
bundleName: 'styles.css',
input: stylesEntryPoint,
inject: false,
},
],
},
},
};
updateProjectConfiguration(tree, project, projectConfig);
await expect(setupTailwindGenerator(tree, { project })).rejects.toThrow(
expect.objectContaining({
message: expect.stringContaining(
`Could not find a styles entry point for project "${project}"`
),
})
);
});
it('should add tailwind styles to provided styles entry point', async () => {
const stylesEntryPoint = `apps/${project}/src/custom-styles-entry-point.scss`;
tree.write(stylesEntryPoint, 'p { margin: 0; }');
await setupTailwindGenerator(tree, {
project,
stylesEntryPoint,
skipFormat: true,
});
expect(tree.read(stylesEntryPoint, 'utf-8')).toMatchInlineSnapshot(`
"@tailwind base;
@tailwind components;
@tailwind utilities;
p { margin: 0; }"
`);
});
it.each([
`apps/${project}/src/styles.css`,
`apps/${project}/src/styles.scss`,
`apps/${project}/src/styles.sass`,
`apps/${project}/src/styles.less`,
])(
'should add tailwind styles to "%s" when not provided',
async (stylesEntryPoint) => {
tree.write(stylesEntryPoint, 'p { margin: 0; }');
await setupTailwindGenerator(tree, { project, skipFormat: true });
expect(tree.read(stylesEntryPoint, 'utf-8')).toMatchSnapshot();
}
);
it('should add tailwind styles to the first style inside the project root specified in the build config as a string', async () => {
const stylesEntryPoint = `apps/${project}/src/custom-styles-entry-point.scss`;
tree.write(stylesEntryPoint, 'p { margin: 0; }');
const projectConfig = readProjectConfiguration(tree, project);
projectConfig.targets = {
build: {
executor: '@nx/angular:webpack-browser',
options: {
styles: ['node_modules/awesome-ds/styles.css', stylesEntryPoint],
},
},
};
updateProjectConfiguration(tree, project, projectConfig);
await setupTailwindGenerator(tree, { project, skipFormat: true });
expect(tree.read(stylesEntryPoint, 'utf-8')).toMatchInlineSnapshot(`
"@tailwind base;
@tailwind components;
@tailwind utilities;
p { margin: 0; }"
`);
});
it('should add tailwind styles to the first style inside the project root specified in the build config as an object when inject is not specified', async () => {
const stylesEntryPoint = `apps/${project}/src/custom-styles-entry-point.scss`;
tree.write(stylesEntryPoint, 'p { margin: 0; }');
const projectConfig = readProjectConfiguration(tree, project);
projectConfig.targets = {
build: {
executor: '@nx/angular:webpack-browser',
options: {
styles: [
'node_modules/awesome-ds/styles.css',
{
bundleName: 'styles.css',
input: stylesEntryPoint,
},
],
},
},
};
updateProjectConfiguration(tree, project, projectConfig);
await setupTailwindGenerator(tree, { project, skipFormat: true });
expect(tree.read(stylesEntryPoint, 'utf-8')).toMatchInlineSnapshot(`
"@tailwind base;
@tailwind components;
@tailwind utilities;
p { margin: 0; }"
`);
});
it('should add tailwind styles to the first style inside the project root specified in the build config as an object when "inject: true"', async () => {
const stylesEntryPoint = `apps/${project}/src/custom-styles-entry-point.scss`;
tree.write(stylesEntryPoint, 'p { margin: 0; }');
const projectConfig = readProjectConfiguration(tree, project);
projectConfig.targets = {
build: {
executor: '@nx/angular:webpack-browser',
options: {
styles: [
'node_modules/awesome-ds/styles.css',
{
bundleName: 'styles.css',
input: stylesEntryPoint,
inject: true,
},
],
},
},
};
updateProjectConfiguration(tree, project, projectConfig);
await setupTailwindGenerator(tree, { project, skipFormat: true });
expect(tree.read(stylesEntryPoint, 'utf-8')).toMatchInlineSnapshot(`
"@tailwind base;
@tailwind components;
@tailwind utilities;
p { margin: 0; }"
`);
});
it('should add required packages', async () => {
const stylesEntryPoint = `apps/${project}/src/styles.scss`;
tree.write(stylesEntryPoint, 'p { margin: 0; }');
await setupTailwindGenerator(tree, {
project,
stylesEntryPoint,
skipFormat: true,
});
const { devDependencies } = readJson(tree, 'package.json');
expect(devDependencies.tailwindcss).toBe(tailwindVersion);
expect(devDependencies.autoprefixer).toBe(autoprefixerVersion);
expect(devDependencies.postcss).toBe(postcssVersion);
});
it('should generate the tailwind.config.js file in the project root with the config for v3 by default', async () => {
const stylesEntryPoint = `apps/${project}/src/styles.scss`;
tree.write(stylesEntryPoint, 'p { margin: 0; }');
await setupTailwindGenerator(tree, {
project,
stylesEntryPoint,
skipFormat: true,
});
expect(tree.read(`apps/${project}/tailwind.config.js`, 'utf-8'))
.toMatchInlineSnapshot(`
"const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
const { join } = require('path');
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
...createGlobPatternsForDependencies(__dirname),
],
theme: {
extend: {},
},
plugins: [],
};
"
`);
});
it('should generate the tailwind.config.js file in the project root with the config for v3 when a version greater than 3 is installed', async () => {
const stylesEntryPoint = `apps/${project}/src/styles.scss`;
tree.write(stylesEntryPoint, 'p { margin: 0; }');
tree.write(
'package.json',
JSON.stringify({ devDependencies: { tailwindcss: '^3.0.1' } })
);
await setupTailwindGenerator(tree, {
project,
stylesEntryPoint,
skipFormat: true,
});
expect(tree.read(`apps/${project}/tailwind.config.js`, 'utf-8'))
.toMatchInlineSnapshot(`
"const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
const { join } = require('path');
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
...createGlobPatternsForDependencies(__dirname),
],
theme: {
extend: {},
},
plugins: [],
};
"
`);
});
it('should generate the tailwind.config.js file in the project root with the config for v2 when a version greater than 2 and lower than 3 is installed', async () => {
const stylesEntryPoint = `apps/${project}/src/styles.scss`;
tree.write(stylesEntryPoint, 'p { margin: 0; }');
tree.write(
'package.json',
JSON.stringify({ devDependencies: { tailwindcss: '~2.0.0' } })
);
await setupTailwindGenerator(tree, {
project,
stylesEntryPoint,
skipFormat: true,
});
expect(tree.read(`apps/${project}/tailwind.config.js`, 'utf-8'))
.toMatchInlineSnapshot(`
"const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
const { join } = require('path');
module.exports = {
mode: 'jit',
purge: [
join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
...createGlobPatternsForDependencies(__dirname),
],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
};
"
`);
});
it('should format files', async () => {
const stylesEntryPoint = `apps/${project}/src/styles.scss`;
tree.write(stylesEntryPoint, 'p { margin: 0; }');
jest.spyOn(devkit, 'formatFiles');
await setupTailwindGenerator(tree, { project, stylesEntryPoint });
expect(devkit.formatFiles).toHaveBeenCalled();
});
it('should not format files when "skipFormat: true"', async () => {
const stylesEntryPoint = `apps/${project}/src/styles.scss`;
tree.write(stylesEntryPoint, 'p { margin: 0; }');
jest.spyOn(devkit, 'formatFiles');
await setupTailwindGenerator(tree, {
project,
stylesEntryPoint,
skipFormat: true,
});
expect(devkit.formatFiles).not.toHaveBeenCalled();
});
});
});