fix(testing): support custom workspaceRoot for angular CT (#15485)
This commit is contained in:
parent
81e1dcd195
commit
26fbd1daa0
@ -9,8 +9,10 @@ import {
|
||||
updateFile,
|
||||
updateProjectConfig,
|
||||
removeFile,
|
||||
checkFilesExist,
|
||||
} from '../../utils';
|
||||
import { names } from '@nrwl/devkit';
|
||||
import { join } from 'path';
|
||||
|
||||
describe('Angular Cypress Component Tests', () => {
|
||||
let projectName: string;
|
||||
@ -114,6 +116,20 @@ describe('Angular Cypress Component Tests', () => {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should use root level tailwinds config', () => {
|
||||
useRootLevelTailwindConfig(
|
||||
join('libs', buildableLibName, 'tailwind.config.js')
|
||||
);
|
||||
checkFilesExist('tailwind.config.js');
|
||||
checkFilesDoNotExist(`libs/${buildableLibName}/tailwind.config.js`);
|
||||
|
||||
if (runCypressTests()) {
|
||||
expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain(
|
||||
'All specs passed!'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function createApp(appName: string) {
|
||||
@ -386,3 +402,21 @@ function updateBuilableLibTestsToAssertAppStyles(
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function useRootLevelTailwindConfig(existingConfigPath: string) {
|
||||
createFile(
|
||||
'tailwind.config.js',
|
||||
`const { join } = require('path');
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [join(__dirname, '**/*.{html,js,ts}')],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
`
|
||||
);
|
||||
removeFile(existingConfigPath);
|
||||
}
|
||||
|
||||
@ -19,11 +19,11 @@ import {
|
||||
readCachedProjectGraph,
|
||||
readTargetOptions,
|
||||
stripIndents,
|
||||
workspaceRoot,
|
||||
} from '@nrwl/devkit';
|
||||
import { existsSync, lstatSync, mkdirSync, writeFileSync } from 'fs';
|
||||
import { dirname, join, relative, sep } from 'path';
|
||||
import type { BrowserBuilderSchema } from '../src/builders/webpack-browser/schema';
|
||||
import { gte } from 'semver';
|
||||
|
||||
/**
|
||||
* Angular nx preset for Cypress Component Testing
|
||||
@ -78,11 +78,14 @@ ${e.stack ? e.stack : e}`
|
||||
Has project config? ${!!graph.nodes?.[buildTarget.project]?.data}`);
|
||||
}
|
||||
|
||||
const fromWorkspaceRoot = relative(workspaceRoot, pathToConfig);
|
||||
const fromWorkspaceRoot = relative(ctContext.root, pathToConfig);
|
||||
const normalizedFromWorkspaceRootPath = lstatSync(pathToConfig).isFile()
|
||||
? dirname(fromWorkspaceRoot)
|
||||
: fromWorkspaceRoot;
|
||||
const offset = offsetFromRoot(normalizedFromWorkspaceRootPath);
|
||||
const offset = isOffsetNeeded(ctContext, ctProjectConfig)
|
||||
? offsetFromRoot(normalizedFromWorkspaceRootPath)
|
||||
: undefined;
|
||||
|
||||
const buildContext = createExecutorContext(
|
||||
graph,
|
||||
graph.nodes[buildTarget.project]?.data.targets,
|
||||
@ -101,6 +104,15 @@ ${e.stack ? e.stack : e}`
|
||||
...nxBaseCypressPreset(pathToConfig),
|
||||
// NOTE: cannot use a glob pattern since it will break cypress generated tsconfig.
|
||||
specPattern: ['src/**/*.cy.ts', 'src/**/*.cy.js'],
|
||||
// cypress defaults to a relative path from the workspaceRoot instead of projectRoot
|
||||
// set as absolute path in case this changes internally to cypress, this path isn't OS dependent
|
||||
indexHtmlFile: joinPathFragments(
|
||||
ctContext.root,
|
||||
ctProjectConfig.root,
|
||||
'cypress',
|
||||
'support',
|
||||
'component-index.html'
|
||||
),
|
||||
devServer: {
|
||||
// cypress uses string union type,
|
||||
// need to use const to prevent typing to string
|
||||
@ -156,8 +168,12 @@ function getBuildableTarget(ctContext: ExecutorContext) {
|
||||
function normalizeBuildTargetOptions(
|
||||
buildContext: ExecutorContext,
|
||||
ctContext: ExecutorContext,
|
||||
offset: string
|
||||
): { root: string; sourceRoot: string; buildOptions: BrowserBuilderSchema } {
|
||||
offset?: string
|
||||
): {
|
||||
root: string;
|
||||
sourceRoot: string;
|
||||
buildOptions: BrowserBuilderSchema & { workspaceRoot: string };
|
||||
} {
|
||||
const options = readTargetOptions<BrowserBuilderSchema>(
|
||||
{
|
||||
project: buildContext.projectName,
|
||||
@ -168,39 +184,40 @@ function normalizeBuildTargetOptions(
|
||||
);
|
||||
const buildOptions = withSchemaDefaults(options);
|
||||
|
||||
// polyfill entries might be local files or files that are resolved from node_modules
|
||||
// like zone.js.
|
||||
// prevents error from webpack saying can't find <offset>/zone.js.
|
||||
const handlePolyfillPath = (polyfill: string) => {
|
||||
const maybeFullPath = join(workspaceRoot, polyfill.split('/').join(sep));
|
||||
if (existsSync(maybeFullPath)) {
|
||||
return joinPathFragments(offset, polyfill);
|
||||
}
|
||||
return polyfill;
|
||||
};
|
||||
// paths need to be unix paths for angular devkit
|
||||
buildOptions.polyfills =
|
||||
Array.isArray(buildOptions.polyfills) && buildOptions.polyfills.length > 0
|
||||
? (buildOptions.polyfills as string[]).map((p) => handlePolyfillPath(p))
|
||||
: handlePolyfillPath(buildOptions.polyfills as string);
|
||||
|
||||
buildOptions.main = joinPathFragments(offset, buildOptions.main);
|
||||
buildOptions.index =
|
||||
typeof buildOptions.index === 'string'
|
||||
? joinPathFragments(offset, buildOptions.index)
|
||||
: {
|
||||
...buildOptions.index,
|
||||
input: joinPathFragments(offset, buildOptions.index.input),
|
||||
};
|
||||
// cypress creates a tsconfig if one isn't preset
|
||||
// that contains all the support required for angular and component tests
|
||||
delete buildOptions.tsConfig;
|
||||
|
||||
buildOptions.fileReplacements = buildOptions.fileReplacements.map((fr) => {
|
||||
fr.replace = joinPathFragments(offset, fr.replace);
|
||||
fr.with = joinPathFragments(offset, fr.with);
|
||||
return fr;
|
||||
});
|
||||
if (offset) {
|
||||
// polyfill entries might be local files or files that are resolved from node_modules
|
||||
// like zone.js.
|
||||
// prevents error from webpack saying can't find <offset>/zone.js.
|
||||
const handlePolyfillPath = (polyfill: string) => {
|
||||
const maybeFullPath = join(ctContext.root, polyfill.split('/').join(sep));
|
||||
if (existsSync(maybeFullPath)) {
|
||||
return joinPathFragments(offset, polyfill);
|
||||
}
|
||||
return polyfill;
|
||||
};
|
||||
// paths need to be unix paths for angular devkit
|
||||
buildOptions.polyfills =
|
||||
Array.isArray(buildOptions.polyfills) && buildOptions.polyfills.length > 0
|
||||
? (buildOptions.polyfills as string[]).map((p) => handlePolyfillPath(p))
|
||||
: handlePolyfillPath(buildOptions.polyfills as string);
|
||||
buildOptions.main = joinPathFragments(offset, buildOptions.main);
|
||||
buildOptions.index =
|
||||
typeof buildOptions.index === 'string'
|
||||
? joinPathFragments(offset, buildOptions.index)
|
||||
: {
|
||||
...buildOptions.index,
|
||||
input: joinPathFragments(offset, buildOptions.index.input),
|
||||
};
|
||||
buildOptions.fileReplacements = buildOptions.fileReplacements.map((fr) => {
|
||||
fr.replace = joinPathFragments(offset, fr.replace);
|
||||
fr.with = joinPathFragments(offset, fr.with);
|
||||
return fr;
|
||||
});
|
||||
}
|
||||
|
||||
// if the ct project isn't being used in the build project
|
||||
// then we don't want to have the assets/scripts/styles be included to
|
||||
@ -213,29 +230,31 @@ function normalizeBuildTargetOptions(
|
||||
ctContext.projectName
|
||||
)
|
||||
) {
|
||||
buildOptions.assets = buildOptions.assets.map((asset) => {
|
||||
return typeof asset === 'string'
|
||||
? joinPathFragments(offset, asset)
|
||||
: { ...asset, input: joinPathFragments(offset, asset.input) };
|
||||
});
|
||||
buildOptions.styles = buildOptions.styles.map((style) => {
|
||||
return typeof style === 'string'
|
||||
? joinPathFragments(offset, style)
|
||||
: { ...style, input: joinPathFragments(offset, style.input) };
|
||||
});
|
||||
buildOptions.scripts = buildOptions.scripts.map((script) => {
|
||||
return typeof script === 'string'
|
||||
? joinPathFragments(offset, script)
|
||||
: { ...script, input: joinPathFragments(offset, script.input) };
|
||||
});
|
||||
if (buildOptions.stylePreprocessorOptions?.includePaths.length > 0) {
|
||||
buildOptions.stylePreprocessorOptions = {
|
||||
includePaths: buildOptions.stylePreprocessorOptions.includePaths.map(
|
||||
(path) => {
|
||||
return joinPathFragments(offset, path);
|
||||
}
|
||||
),
|
||||
};
|
||||
if (offset) {
|
||||
buildOptions.assets = buildOptions.assets.map((asset) => {
|
||||
return typeof asset === 'string'
|
||||
? joinPathFragments(offset, asset)
|
||||
: { ...asset, input: joinPathFragments(offset, asset.input) };
|
||||
});
|
||||
buildOptions.styles = buildOptions.styles.map((style) => {
|
||||
return typeof style === 'string'
|
||||
? joinPathFragments(offset, style)
|
||||
: { ...style, input: joinPathFragments(offset, style.input) };
|
||||
});
|
||||
buildOptions.scripts = buildOptions.scripts.map((script) => {
|
||||
return typeof script === 'string'
|
||||
? joinPathFragments(offset, script)
|
||||
: { ...script, input: joinPathFragments(offset, script.input) };
|
||||
});
|
||||
if (buildOptions.stylePreprocessorOptions?.includePaths.length > 0) {
|
||||
buildOptions.stylePreprocessorOptions = {
|
||||
includePaths: buildOptions.stylePreprocessorOptions.includePaths.map(
|
||||
(path) => {
|
||||
return joinPathFragments(offset, path);
|
||||
}
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const stylePath = getTempStylesForTailwind(ctContext);
|
||||
@ -256,9 +275,15 @@ Note: this may fail, setting the correct 'sourceRoot' for ${buildContext.project
|
||||
}
|
||||
|
||||
return {
|
||||
root: joinPathFragments(offset, config.root),
|
||||
sourceRoot: joinPathFragments(offset, config.sourceRoot),
|
||||
buildOptions,
|
||||
root: offset ? joinPathFragments(offset, config.root) : config.root,
|
||||
sourceRoot: offset
|
||||
? joinPathFragments(offset, config.sourceRoot)
|
||||
: config.sourceRoot,
|
||||
buildOptions: {
|
||||
...buildOptions,
|
||||
// this property is only valid for cy v12.9.0+
|
||||
workspaceRoot: offset ? undefined : ctContext.root,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -309,13 +334,14 @@ function getTempStylesForTailwind(ctExecutorContext: ExecutorContext) {
|
||||
ctProjectConfig.root,
|
||||
'tailwind.config'
|
||||
);
|
||||
const isTailWindInCtProject =
|
||||
existsSync(ctProjectTailwindConfig + '.js') ||
|
||||
existsSync(ctProjectTailwindConfig + '.cjs');
|
||||
const exts = ['js', 'cjs'];
|
||||
const isTailWindInCtProject = exts.some((ext) =>
|
||||
existsSync(`${ctProjectTailwindConfig}.${ext}`)
|
||||
);
|
||||
const rootTailwindPath = join(ctExecutorContext.root, 'tailwind.config');
|
||||
const isTailWindInRoot =
|
||||
existsSync(rootTailwindPath + '.js') ||
|
||||
existsSync(rootTailwindPath + '.cjs');
|
||||
const isTailWindInRoot = exts.some((ext) =>
|
||||
existsSync(`${rootTailwindPath}.${ext}`)
|
||||
);
|
||||
|
||||
if (isTailWindInRoot || isTailWindInCtProject) {
|
||||
const pathToStyle = getTempTailwindPath(ctExecutorContext);
|
||||
@ -339,3 +365,46 @@ function getTempStylesForTailwind(ctExecutorContext: ExecutorContext) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isOffsetNeeded(
|
||||
ctExecutorContext: ExecutorContext,
|
||||
ctProjectConfig: ProjectConfiguration
|
||||
) {
|
||||
try {
|
||||
const { version = null } = require('cypress/package.json');
|
||||
|
||||
const supportsWorkspaceRoot = !!version && gte(version, '12.9.0');
|
||||
|
||||
// if using cypress <v12.9.0 then we require the offset
|
||||
if (!supportsWorkspaceRoot) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
ctProjectConfig.projectType === 'library' &&
|
||||
// angular will only see this config if the library root is the build project config root
|
||||
// otherwise it will be set to the buildTarget root which is the app root where this config doesn't exist
|
||||
// causing tailwind styles from the libs project root to not work
|
||||
['js', 'cjs'].some((ext) =>
|
||||
existsSync(
|
||||
join(
|
||||
ctExecutorContext.root,
|
||||
ctProjectConfig.root,
|
||||
`tailwind.config.${ext}`
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
||||
logger.error(e);
|
||||
}
|
||||
// unable to determine if we don't require an offset
|
||||
// safest to assume we do
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ export function getTempTailwindPath(context: ExecutorContext) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the childProjectName is a decendent of the parentProjectName
|
||||
* Checks if the childProjectName is a descendent of the parentProjectName
|
||||
* in the project graph
|
||||
**/
|
||||
export function isCtProjectUsingBuildProject(
|
||||
|
||||
@ -51,6 +51,8 @@ const IGNORE_MATCHES_IN_PACKAGE = {
|
||||
'sass',
|
||||
'stylus',
|
||||
'tailwindcss',
|
||||
// used in the CT angular plugin where Cy is already installed to use it.
|
||||
'cypress',
|
||||
],
|
||||
cli: ['nx'],
|
||||
cypress: ['cypress', '@angular-devkit/schematics', '@nrwl/cypress', 'vite'],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user