Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { Observable, Subscriber, catchError, defaultIfEmpty, from, of, switchMap
import { Configuration } from 'webpack';
import { ExecutionTransformer } from '../../transforms';
import { OutputHashing } from '../browser-esbuild/schema';
import { findTests } from './find-tests';
import { findTests, getTestEntrypoints } from './find-tests';
import { Schema as KarmaBuilderOptions } from './schema';

interface BuildOptions extends ApplicationBuilderInternalOptions {
Expand Down Expand Up @@ -268,28 +268,7 @@ async function collectEntrypoints(
projectSourceRoot,
);

const seen = new Set<string>();

return new Map(
Array.from(testFiles, (testFile) => {
const relativePath = path
.relative(
testFile.startsWith(projectSourceRoot) ? projectSourceRoot : context.workspaceRoot,
testFile,
)
.replace(/^[./]+/, '_')
.replace(/\//g, '-');
let uniqueName = `spec-${path.basename(relativePath, path.extname(relativePath))}`;
let suffix = 2;
while (seen.has(uniqueName)) {
uniqueName = `${relativePath}-${suffix}`;
++suffix;
}
seen.add(uniqueName);

return [uniqueName, testFile];
}),
);
return getTestEntrypoints(testFiles, { projectSourceRoot, workspaceRoot: context.workspaceRoot });
}

async function initializeApplication(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,39 @@ export async function findTests(
return [...new Set(files.flat())];
}

interface TestEntrypointsOptions {
projectSourceRoot: string;
workspaceRoot: string;
}

/** Generate unique bundle names for a set of test files. */
export function getTestEntrypoints(
testFiles: string[],
{ projectSourceRoot, workspaceRoot }: TestEntrypointsOptions,
): Map<string, string> {
const seen = new Set<string>();

return new Map(
Array.from(testFiles, (testFile) => {
const relativePath = removeRoots(testFile, [projectSourceRoot, workspaceRoot])
// Strip leading dots and path separators.
.replace(/^[./\\]+/, '')
// Replace any path separators with dashes.
.replace(/[/\\]/g, '-');
const baseName = `spec-${basename(relativePath, extname(relativePath))}`;
let uniqueName = baseName;
let suffix = 2;
while (seen.has(uniqueName)) {
uniqueName = `${baseName}-${suffix}`.replace(/([^\w](?:spec|test))-([\d]+)$/, '-$2$1');
++suffix;
}
seen.add(uniqueName);

return [uniqueName, testFile];
}),
);
}

const normalizePath = (path: string): string => path.replace(/\\/g, '/');

const removeLeadingSlash = (pattern: string): string => {
Expand All @@ -44,6 +77,16 @@ const removeRelativeRoot = (path: string, root: string): string => {
return path;
};

function removeRoots(path: string, roots: string[]): string {
for (const root of roots) {
if (path.startsWith(root)) {
return path.substring(root.length);
}
}

return basename(path);
}

async function findMatchingTests(
pattern: string,
ignore: string[],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import { getTestEntrypoints } from './find-tests';

const UNIX_ENTRYPOINTS_OPTIONS = {
pathSeparator: '/',
workspaceRoot: '/my/workspace/root',
projectSourceRoot: '/my/workspace/root/src-root',
};

const WINDOWS_ENTRYPOINTS_OPTIONS = {
pathSeparator: '\\',
workspaceRoot: 'C:\\my\\workspace\\root',
projectSourceRoot: 'C:\\my\\workspace\\root\\src-root',
};

describe('getTestEntrypoints', () => {
for (const options of [UNIX_ENTRYPOINTS_OPTIONS, WINDOWS_ENTRYPOINTS_OPTIONS]) {
describe(`with path separator "${options.pathSeparator}"`, () => {
function joinWithSeparator(base: string, rel: string) {
return `${base}${options.pathSeparator}${rel.replace(/\//g, options.pathSeparator)}`;
}

function getEntrypoints(workspaceRelative: string[], sourceRootRelative: string[] = []) {
return getTestEntrypoints(
[
...workspaceRelative.map((p) => joinWithSeparator(options.workspaceRoot, p)),
...sourceRootRelative.map((p) => joinWithSeparator(options.projectSourceRoot, p)),
],
options,
);
}

it('returns an empty map without test files', () => {
expect(getEntrypoints([])).toEqual(new Map());
});

it('strips workspace root and/or project source root', () => {
expect(getEntrypoints(['a/b.spec.js'], ['c/d.spec.js'])).toEqual(
new Map<string, string>([
['spec-a-b.spec', joinWithSeparator(options.workspaceRoot, 'a/b.spec.js')],
['spec-c-d.spec', joinWithSeparator(options.projectSourceRoot, 'c/d.spec.js')],
]),
);
});

it('adds unique prefixes to distinguish between similar names', () => {
expect(getEntrypoints(['a/b/c/d.spec.js', 'a-b/c/d.spec.js'], ['a/b-c/d.spec.js'])).toEqual(
new Map<string, string>([
['spec-a-b-c-d.spec', joinWithSeparator(options.workspaceRoot, 'a/b/c/d.spec.js')],
['spec-a-b-c-d-2.spec', joinWithSeparator(options.workspaceRoot, 'a-b/c/d.spec.js')],
[
'spec-a-b-c-d-3.spec',
joinWithSeparator(options.projectSourceRoot, 'a/b-c/d.spec.js'),
],
]),
);
});
});
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget, isApp)

// src/app/app.component.spec.ts conflicts with this one:
await harness.writeFiles({
[`src/app/a/${collidingBasename}`]: `/** Success! */`,
[`src/app/a/foo-bar/${collidingBasename}`]: `/** Success! */`,
[`src/app/a-foo/bar/${collidingBasename}`]: `/** Success! */`,
[`src/app/a-foo-bar/${collidingBasename}`]: `/** Success! */`,
[`src/app/b/${collidingBasename}`]: `/** Success! */`,
});

Expand All @@ -36,7 +38,9 @@ describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget, isApp)
const bundleLog = logs.find((log) =>
log.message.includes('Application bundle generation complete.'),
);
expect(bundleLog?.message).toContain('spec-app-a-collision.spec.js');
expect(bundleLog?.message).toContain('spec-app-a-foo-bar-collision.spec.js');
expect(bundleLog?.message).toContain('spec-app-a-foo-bar-collision-2.spec.js');
expect(bundleLog?.message).toContain('spec-app-a-foo-bar-collision-3.spec.js');
expect(bundleLog?.message).toContain('spec-app-b-collision.spec.js');
}
});
Expand Down
Loading