fix(testing): support custom workspaceRoot for angular CT (#15485)
This commit is contained in:
parent
81e1dcd195
commit
26fbd1daa0
@ -9,8 +9,10 @@ import {
|
|||||||
updateFile,
|
updateFile,
|
||||||
updateProjectConfig,
|
updateProjectConfig,
|
||||||
removeFile,
|
removeFile,
|
||||||
|
checkFilesExist,
|
||||||
} from '../../utils';
|
} from '../../utils';
|
||||||
import { names } from '@nrwl/devkit';
|
import { names } from '@nrwl/devkit';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
describe('Angular Cypress Component Tests', () => {
|
describe('Angular Cypress Component Tests', () => {
|
||||||
let projectName: string;
|
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) {
|
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,
|
readCachedProjectGraph,
|
||||||
readTargetOptions,
|
readTargetOptions,
|
||||||
stripIndents,
|
stripIndents,
|
||||||
workspaceRoot,
|
|
||||||
} from '@nrwl/devkit';
|
} from '@nrwl/devkit';
|
||||||
import { existsSync, lstatSync, mkdirSync, writeFileSync } from 'fs';
|
import { existsSync, lstatSync, mkdirSync, writeFileSync } from 'fs';
|
||||||
import { dirname, join, relative, sep } from 'path';
|
import { dirname, join, relative, sep } from 'path';
|
||||||
import type { BrowserBuilderSchema } from '../src/builders/webpack-browser/schema';
|
import type { BrowserBuilderSchema } from '../src/builders/webpack-browser/schema';
|
||||||
|
import { gte } from 'semver';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Angular nx preset for Cypress Component Testing
|
* Angular nx preset for Cypress Component Testing
|
||||||
@ -78,11 +78,14 @@ ${e.stack ? e.stack : e}`
|
|||||||
Has project config? ${!!graph.nodes?.[buildTarget.project]?.data}`);
|
Has project config? ${!!graph.nodes?.[buildTarget.project]?.data}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fromWorkspaceRoot = relative(workspaceRoot, pathToConfig);
|
const fromWorkspaceRoot = relative(ctContext.root, pathToConfig);
|
||||||
const normalizedFromWorkspaceRootPath = lstatSync(pathToConfig).isFile()
|
const normalizedFromWorkspaceRootPath = lstatSync(pathToConfig).isFile()
|
||||||
? dirname(fromWorkspaceRoot)
|
? dirname(fromWorkspaceRoot)
|
||||||
: fromWorkspaceRoot;
|
: fromWorkspaceRoot;
|
||||||
const offset = offsetFromRoot(normalizedFromWorkspaceRootPath);
|
const offset = isOffsetNeeded(ctContext, ctProjectConfig)
|
||||||
|
? offsetFromRoot(normalizedFromWorkspaceRootPath)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const buildContext = createExecutorContext(
|
const buildContext = createExecutorContext(
|
||||||
graph,
|
graph,
|
||||||
graph.nodes[buildTarget.project]?.data.targets,
|
graph.nodes[buildTarget.project]?.data.targets,
|
||||||
@ -101,6 +104,15 @@ ${e.stack ? e.stack : e}`
|
|||||||
...nxBaseCypressPreset(pathToConfig),
|
...nxBaseCypressPreset(pathToConfig),
|
||||||
// NOTE: cannot use a glob pattern since it will break cypress generated tsconfig.
|
// NOTE: cannot use a glob pattern since it will break cypress generated tsconfig.
|
||||||
specPattern: ['src/**/*.cy.ts', 'src/**/*.cy.js'],
|
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: {
|
devServer: {
|
||||||
// cypress uses string union type,
|
// cypress uses string union type,
|
||||||
// need to use const to prevent typing to string
|
// need to use const to prevent typing to string
|
||||||
@ -156,8 +168,12 @@ function getBuildableTarget(ctContext: ExecutorContext) {
|
|||||||
function normalizeBuildTargetOptions(
|
function normalizeBuildTargetOptions(
|
||||||
buildContext: ExecutorContext,
|
buildContext: ExecutorContext,
|
||||||
ctContext: ExecutorContext,
|
ctContext: ExecutorContext,
|
||||||
offset: string
|
offset?: string
|
||||||
): { root: string; sourceRoot: string; buildOptions: BrowserBuilderSchema } {
|
): {
|
||||||
|
root: string;
|
||||||
|
sourceRoot: string;
|
||||||
|
buildOptions: BrowserBuilderSchema & { workspaceRoot: string };
|
||||||
|
} {
|
||||||
const options = readTargetOptions<BrowserBuilderSchema>(
|
const options = readTargetOptions<BrowserBuilderSchema>(
|
||||||
{
|
{
|
||||||
project: buildContext.projectName,
|
project: buildContext.projectName,
|
||||||
@ -168,39 +184,40 @@ function normalizeBuildTargetOptions(
|
|||||||
);
|
);
|
||||||
const buildOptions = withSchemaDefaults(options);
|
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
|
// cypress creates a tsconfig if one isn't preset
|
||||||
// that contains all the support required for angular and component tests
|
// that contains all the support required for angular and component tests
|
||||||
delete buildOptions.tsConfig;
|
delete buildOptions.tsConfig;
|
||||||
|
|
||||||
buildOptions.fileReplacements = buildOptions.fileReplacements.map((fr) => {
|
if (offset) {
|
||||||
fr.replace = joinPathFragments(offset, fr.replace);
|
// polyfill entries might be local files or files that are resolved from node_modules
|
||||||
fr.with = joinPathFragments(offset, fr.with);
|
// like zone.js.
|
||||||
return fr;
|
// 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
|
// 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
|
// then we don't want to have the assets/scripts/styles be included to
|
||||||
@ -213,29 +230,31 @@ function normalizeBuildTargetOptions(
|
|||||||
ctContext.projectName
|
ctContext.projectName
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
buildOptions.assets = buildOptions.assets.map((asset) => {
|
if (offset) {
|
||||||
return typeof asset === 'string'
|
buildOptions.assets = buildOptions.assets.map((asset) => {
|
||||||
? joinPathFragments(offset, asset)
|
return typeof asset === 'string'
|
||||||
: { ...asset, input: joinPathFragments(offset, asset.input) };
|
? joinPathFragments(offset, asset)
|
||||||
});
|
: { ...asset, input: joinPathFragments(offset, asset.input) };
|
||||||
buildOptions.styles = buildOptions.styles.map((style) => {
|
});
|
||||||
return typeof style === 'string'
|
buildOptions.styles = buildOptions.styles.map((style) => {
|
||||||
? joinPathFragments(offset, style)
|
return typeof style === 'string'
|
||||||
: { ...style, input: joinPathFragments(offset, style.input) };
|
? joinPathFragments(offset, style)
|
||||||
});
|
: { ...style, input: joinPathFragments(offset, style.input) };
|
||||||
buildOptions.scripts = buildOptions.scripts.map((script) => {
|
});
|
||||||
return typeof script === 'string'
|
buildOptions.scripts = buildOptions.scripts.map((script) => {
|
||||||
? joinPathFragments(offset, script)
|
return typeof script === 'string'
|
||||||
: { ...script, input: joinPathFragments(offset, script.input) };
|
? joinPathFragments(offset, script)
|
||||||
});
|
: { ...script, input: joinPathFragments(offset, script.input) };
|
||||||
if (buildOptions.stylePreprocessorOptions?.includePaths.length > 0) {
|
});
|
||||||
buildOptions.stylePreprocessorOptions = {
|
if (buildOptions.stylePreprocessorOptions?.includePaths.length > 0) {
|
||||||
includePaths: buildOptions.stylePreprocessorOptions.includePaths.map(
|
buildOptions.stylePreprocessorOptions = {
|
||||||
(path) => {
|
includePaths: buildOptions.stylePreprocessorOptions.includePaths.map(
|
||||||
return joinPathFragments(offset, path);
|
(path) => {
|
||||||
}
|
return joinPathFragments(offset, path);
|
||||||
),
|
}
|
||||||
};
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const stylePath = getTempStylesForTailwind(ctContext);
|
const stylePath = getTempStylesForTailwind(ctContext);
|
||||||
@ -256,9 +275,15 @@ Note: this may fail, setting the correct 'sourceRoot' for ${buildContext.project
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
root: joinPathFragments(offset, config.root),
|
root: offset ? joinPathFragments(offset, config.root) : config.root,
|
||||||
sourceRoot: joinPathFragments(offset, config.sourceRoot),
|
sourceRoot: offset
|
||||||
buildOptions,
|
? 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,
|
ctProjectConfig.root,
|
||||||
'tailwind.config'
|
'tailwind.config'
|
||||||
);
|
);
|
||||||
const isTailWindInCtProject =
|
const exts = ['js', 'cjs'];
|
||||||
existsSync(ctProjectTailwindConfig + '.js') ||
|
const isTailWindInCtProject = exts.some((ext) =>
|
||||||
existsSync(ctProjectTailwindConfig + '.cjs');
|
existsSync(`${ctProjectTailwindConfig}.${ext}`)
|
||||||
|
);
|
||||||
const rootTailwindPath = join(ctExecutorContext.root, 'tailwind.config');
|
const rootTailwindPath = join(ctExecutorContext.root, 'tailwind.config');
|
||||||
const isTailWindInRoot =
|
const isTailWindInRoot = exts.some((ext) =>
|
||||||
existsSync(rootTailwindPath + '.js') ||
|
existsSync(`${rootTailwindPath}.${ext}`)
|
||||||
existsSync(rootTailwindPath + '.cjs');
|
);
|
||||||
|
|
||||||
if (isTailWindInRoot || isTailWindInCtProject) {
|
if (isTailWindInRoot || isTailWindInCtProject) {
|
||||||
const pathToStyle = getTempTailwindPath(ctExecutorContext);
|
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
|
* in the project graph
|
||||||
**/
|
**/
|
||||||
export function isCtProjectUsingBuildProject(
|
export function isCtProjectUsingBuildProject(
|
||||||
|
|||||||
@ -51,6 +51,8 @@ const IGNORE_MATCHES_IN_PACKAGE = {
|
|||||||
'sass',
|
'sass',
|
||||||
'stylus',
|
'stylus',
|
||||||
'tailwindcss',
|
'tailwindcss',
|
||||||
|
// used in the CT angular plugin where Cy is already installed to use it.
|
||||||
|
'cypress',
|
||||||
],
|
],
|
||||||
cli: ['nx'],
|
cli: ['nx'],
|
||||||
cypress: ['cypress', '@angular-devkit/schematics', '@nrwl/cypress', 'vite'],
|
cypress: ['cypress', '@angular-devkit/schematics', '@nrwl/cypress', 'vite'],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user