|
| 1 | +import * as fs from 'fs'; |
| 2 | +import * as path from 'path'; |
| 3 | +import executor from './executor'; |
| 4 | +import { ScssBuildExecutorSchema } from './schema'; |
| 5 | +import { createMockContext, createTempDir, cleanupTempDir } from '../../utils/test-utils'; |
| 6 | +import { writeFileText, writeJson, readFileText } from '../../utils'; |
| 7 | + |
| 8 | +function createMockModules(workspaceRoot: string, projectRoot: string): void { |
| 9 | + const projectNodeModules = path.join(projectRoot, 'node_modules', 'sass-embedded'); |
| 10 | + fs.mkdirSync(projectNodeModules, { recursive: true }); |
| 11 | + fs.writeFileSync( |
| 12 | + path.join(projectNodeModules, 'index.js'), |
| 13 | + [ |
| 14 | + 'class SassString {', |
| 15 | + ' constructor(value) { this.value = value; }', |
| 16 | + '}', |
| 17 | + 'module.exports = {', |
| 18 | + ' SassString,', |
| 19 | + " compile: () => ({ css: '@charset \"UTF-8\"; .a{display:flex}' })", |
| 20 | + '};', |
| 21 | + '', |
| 22 | + ].join('\n'), |
| 23 | + 'utf8', |
| 24 | + ); |
| 25 | + |
| 26 | + const workspaceNodeModules = path.join(workspaceRoot, 'node_modules'); |
| 27 | + fs.mkdirSync(workspaceNodeModules, { recursive: true }); |
| 28 | + |
| 29 | + const postcssDir = path.join(workspaceNodeModules, 'postcss'); |
| 30 | + fs.mkdirSync(postcssDir, { recursive: true }); |
| 31 | + fs.writeFileSync( |
| 32 | + path.join(postcssDir, 'index.js'), |
| 33 | + [ |
| 34 | + 'module.exports = function postcss() {', |
| 35 | + ' return {', |
| 36 | + ' process: async (css) => ({ css: css + "/*prefixed*/" })', |
| 37 | + ' };', |
| 38 | + '};', |
| 39 | + '', |
| 40 | + ].join('\n'), |
| 41 | + 'utf8', |
| 42 | + ); |
| 43 | + |
| 44 | + const autoprefixerDir = path.join(workspaceNodeModules, 'autoprefixer'); |
| 45 | + fs.mkdirSync(autoprefixerDir, { recursive: true }); |
| 46 | + fs.writeFileSync( |
| 47 | + path.join(autoprefixerDir, 'index.js'), |
| 48 | + 'module.exports = function autoprefixer() { return { postcssPlugin: "autoprefixer" }; };', |
| 49 | + 'utf8', |
| 50 | + ); |
| 51 | + |
| 52 | + const cleanCssDir = path.join(workspaceNodeModules, 'clean-css'); |
| 53 | + fs.mkdirSync(cleanCssDir, { recursive: true }); |
| 54 | + fs.writeFileSync( |
| 55 | + path.join(cleanCssDir, 'index.js'), |
| 56 | + [ |
| 57 | + 'module.exports = class CleanCss {', |
| 58 | + ' constructor(options) { this.options = options || {}; }', |
| 59 | + ' minify(css) {', |
| 60 | + ' return { styles: css + "/*min:" + (this.options.profile || "none") + "*/" };', |
| 61 | + ' }', |
| 62 | + '};', |
| 63 | + '', |
| 64 | + ].join('\n'), |
| 65 | + 'utf8', |
| 66 | + ); |
| 67 | +} |
| 68 | + |
| 69 | +async function setupProjectStructure(workspaceRoot: string): Promise<string> { |
| 70 | + const projectRoot = path.join(workspaceRoot, 'packages', 'devextreme-scss'); |
| 71 | + const buildDir = path.join(projectRoot, 'build'); |
| 72 | + fs.mkdirSync(buildDir, { recursive: true }); |
| 73 | + |
| 74 | + await writeJson(path.join(workspaceRoot, 'package.json'), { name: 'workspace' }); |
| 75 | + await writeJson(path.join(projectRoot, 'package.json'), { name: 'devextreme-scss' }); |
| 76 | + |
| 77 | + await writeJson(path.join(projectRoot, 'build', 'clean-css-options.json'), { profile: 'all' }); |
| 78 | + |
| 79 | + const themebuilderDataDir = path.join( |
| 80 | + workspaceRoot, |
| 81 | + 'packages', |
| 82 | + 'devextreme-themebuilder', |
| 83 | + 'src', |
| 84 | + 'data', |
| 85 | + ); |
| 86 | + fs.mkdirSync(themebuilderDataDir, { recursive: true }); |
| 87 | + await writeJson(path.join(themebuilderDataDir, 'clean-css-options.json'), { profile: 'ci' }); |
| 88 | + |
| 89 | + const devextremeDir = path.join(workspaceRoot, 'packages', 'devextreme'); |
| 90 | + fs.mkdirSync(devextremeDir, { recursive: true }); |
| 91 | + await writeJson(path.join(devextremeDir, 'package.json'), { version: '26.1.0-test' }); |
| 92 | + |
| 93 | + await writeFileText( |
| 94 | + path.join(buildDir, 'theme-options.cjs'), |
| 95 | + [ |
| 96 | + 'module.exports = {', |
| 97 | + ' getThemes: () => [', |
| 98 | + " ['generic', 'default', 'light'],", |
| 99 | + ' ],', |
| 100 | + '};', |
| 101 | + '', |
| 102 | + ].join('\n'), |
| 103 | + ); |
| 104 | + |
| 105 | + await writeFileText(path.join(buildDir, 'bundle-template.common.scss'), '.common { color: red; }'); |
| 106 | + await writeFileText(path.join(buildDir, 'bundle-template.generic.scss'), '.generic-$COLOR { color: red; }'); |
| 107 | + |
| 108 | + createMockModules(workspaceRoot, projectRoot); |
| 109 | + return projectRoot; |
| 110 | +} |
| 111 | + |
1 | 112 | describe('ScssBuildExecutor E2E', () => { |
2 | | - it('has test placeholder for native pipeline', () => { |
3 | | - expect(true).toBe(true); |
| 113 | + let tempDir: string; |
| 114 | + |
| 115 | + beforeEach(() => { |
| 116 | + tempDir = createTempDir('nx-scss-build-e2e-'); |
| 117 | + }); |
| 118 | + |
| 119 | + afterEach(() => { |
| 120 | + cleanupTempDir(tempDir); |
| 121 | + }); |
| 122 | + |
| 123 | + it('builds all mode bundles and applies license/minification profile', async () => { |
| 124 | + const projectRoot = await setupProjectStructure(tempDir); |
| 125 | + const context = createMockContext({ |
| 126 | + root: tempDir, |
| 127 | + projectName: 'devextreme-scss', |
| 128 | + projectRoot: 'packages/devextreme-scss', |
| 129 | + }); |
| 130 | + |
| 131 | + const options: ScssBuildExecutorSchema = { mode: 'all', cssOutputDir: './artifacts/css' }; |
| 132 | + const result = await executor(options, context); |
| 133 | + |
| 134 | + expect(result.success).toBe(true); |
| 135 | + expect(fs.existsSync(path.join(projectRoot, 'scss', 'bundles', 'dx.light.scss'))).toBe(true); |
| 136 | + expect(fs.existsSync(path.join(projectRoot, 'scss', 'bundles', 'dx.common.scss'))).toBe(true); |
| 137 | + |
| 138 | + const cssDir = path.join(projectRoot, 'artifacts', 'css'); |
| 139 | + const generatedCssFiles = fs |
| 140 | + .readdirSync(cssDir) |
| 141 | + .filter((name) => name.endsWith('.css')) |
| 142 | + .sort(); |
| 143 | + expect(generatedCssFiles.length).toBeGreaterThan(0); |
| 144 | + expect(generatedCssFiles).toContain('dx.common.css'); |
| 145 | + |
| 146 | + const commonCss = await readFileText(path.join(cssDir, 'dx.common.css')); |
| 147 | + |
| 148 | + expect(commonCss).toContain('Version: 26.1.0-test'); |
| 149 | + expect(commonCss).toContain('/*min:all*/'); |
| 150 | + expect(commonCss).toContain('DevExtreme (dx.common.css)'); |
| 151 | + }); |
| 152 | + |
| 153 | + it('builds ci mode only for selected dev bundles and uses ci profile', async () => { |
| 154 | + const projectRoot = await setupProjectStructure(tempDir); |
| 155 | + const context = createMockContext({ |
| 156 | + root: tempDir, |
| 157 | + projectName: 'devextreme-scss', |
| 158 | + projectRoot: 'packages/devextreme-scss', |
| 159 | + }); |
| 160 | + |
| 161 | + const options: ScssBuildExecutorSchema = { |
| 162 | + mode: 'ci', |
| 163 | + devBundles: ['light'], |
| 164 | + cssOutputDir: './artifacts/css', |
| 165 | + }; |
| 166 | + const result = await executor(options, context); |
| 167 | + |
| 168 | + expect(result.success).toBe(true); |
| 169 | + |
| 170 | + const cssDir = path.join(projectRoot, 'artifacts', 'css'); |
| 171 | + const generatedCssFiles = fs |
| 172 | + .readdirSync(cssDir) |
| 173 | + .filter((name) => name.endsWith('.css')) |
| 174 | + .sort(); |
| 175 | + |
| 176 | + expect(generatedCssFiles).toEqual(['dx.light.css']); |
| 177 | + const lightCss = await readFileText(path.join(cssDir, 'dx.light.css')); |
| 178 | + expect(lightCss).toContain('/*min:ci*/'); |
| 179 | + |
| 180 | + expect(fs.existsSync(path.join(projectRoot, 'scss', 'bundles', 'dx.common.scss'))).toBe(true); |
4 | 181 | }); |
5 | 182 | }); |
0 commit comments