Leosvel Pérez Espinosa 37f02f7e6b
feat(angular): support angular 18.0.0 (#22509)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #25284
2024-05-23 17:50:04 +02:00

712 lines
18 KiB
TypeScript

import 'nx/src/internal-testing-utils/mock-project-graph';
import {
readJson,
readProjectConfiguration,
Tree,
updateJson,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { generateTestApplication } from '../utils/testing';
import { setupMf } from './setup-mf';
import { E2eTestRunner } from '../../utils/test-runners';
describe('Init MF', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await generateTestApplication(tree, {
name: 'app1',
routing: true,
standalone: false,
skipFormat: true,
});
await generateTestApplication(tree, {
name: 'remote1',
routing: true,
standalone: false,
skipFormat: true,
});
});
test.each([
['app1', 'host'],
['remote1', 'remote'],
])(
'should create webpack and mf configs correctly',
async (app, type: 'host' | 'remote') => {
// ACT
await setupMf(tree, {
appName: app,
mfType: type,
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
// ASSERT
expect(tree.exists(`${app}/module-federation.config.js`)).toBeTruthy();
expect(tree.exists(`${app}/webpack.config.js`)).toBeTruthy();
expect(tree.exists(`${app}/webpack.prod.config.js`)).toBeTruthy();
const webpackContents = tree.read(`${app}/webpack.config.js`, 'utf-8');
expect(webpackContents).toMatchSnapshot();
const mfConfigContents = tree.read(
`${app}/module-federation.config.js`,
'utf-8'
);
expect(mfConfigContents).toMatchSnapshot();
}
);
test.each([
['app1', 'host'],
['remote1', 'remote'],
])(
'should create webpack and mf configs correctly when --typescriptConfiguration=true',
async (app, type: 'host' | 'remote') => {
// ACT
await setupMf(tree, {
appName: app,
mfType: type,
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
// ASSERT
expect(tree.exists(`${app}/module-federation.config.ts`)).toBeTruthy();
expect(tree.exists(`${app}/webpack.config.ts`)).toBeTruthy();
expect(tree.exists(`${app}/webpack.prod.config.ts`)).toBeTruthy();
const webpackContents = tree.read(`${app}/webpack.config.ts`, 'utf-8');
expect(webpackContents).toMatchSnapshot();
const mfConfigContents = tree.read(
`${app}/module-federation.config.ts`,
'utf-8'
);
expect(mfConfigContents).toMatchSnapshot();
}
);
test.each([
['app1', 'host'],
['remote1', 'remote'],
])(
'create bootstrap file with the contents of main.ts',
async (app, type: 'host' | 'remote') => {
// ARRANGE
const mainContents = tree.read(`${app}/src/main.ts`, 'utf-8');
// ACT
await setupMf(tree, {
appName: app,
mfType: type,
standalone: false,
skipFormat: true,
});
// ASSERT
const bootstrapContents = tree.read(`${app}/src/bootstrap.ts`, 'utf-8');
const updatedMainContents = tree.read(`${app}/src/main.ts`, 'utf-8');
expect(bootstrapContents).toEqual(mainContents);
expect(updatedMainContents).not.toEqual(mainContents);
}
);
test.each([
['app1', 'host'],
['remote1', 'remote'],
])(
'should alter main.ts to import the bootstrap file dynamically',
async (app, type: 'host' | 'remote') => {
// ARRANGE
const mainContents = tree.read(`${app}/src/main.ts`, 'utf-8');
// ACT
await setupMf(tree, {
appName: app,
mfType: type,
standalone: false,
skipFormat: true,
});
// ASSERT
const updatedMainContents = tree.read(`${app}/src/main.ts`, 'utf-8');
expect(updatedMainContents).toEqual(
`import('./bootstrap').catch(err => console.error(err));`
);
expect(updatedMainContents).not.toEqual(mainContents);
}
);
test.each([
['app1', 'host'],
['remote1', 'remote'],
])(
'should change the build and serve target and set correct path to webpack config',
async (app, type: 'host' | 'remote') => {
// ACT
await setupMf(tree, {
appName: app,
mfType: type,
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
// ASSERT
const { build, serve } = readProjectConfiguration(tree, app).targets;
expect(serve.executor).toEqual(
type === 'host'
? '@nx/angular:module-federation-dev-server'
: '@nx/angular:dev-server'
);
expect(build.executor).toEqual('@nx/angular:webpack-browser');
expect(build.options.customWebpackConfig.path).toEqual(
`${app}/webpack.config.js`
);
}
);
test.each([
['app1', 'host'],
['remote1', 'remote'],
])(
'should change the build and serve target and set correct path to webpack config when --typescriptConfiguration=true',
async (app, type: 'host' | 'remote') => {
// ACT
await setupMf(tree, {
appName: app,
mfType: type,
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
// ASSERT
const { build, serve } = readProjectConfiguration(tree, app).targets;
expect(serve.executor).toEqual(
type === 'host'
? '@nx/angular:module-federation-dev-server'
: '@nx/angular:dev-server'
);
expect(build.executor).toEqual('@nx/angular:webpack-browser');
expect(build.options.customWebpackConfig.path).toEqual(
`${app}/webpack.config.ts`
);
}
);
it('should not generate a webpack prod file for dynamic host', async () => {
// ACT
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
federationType: 'dynamic',
standalone: false,
skipFormat: true,
});
// ASSERT
const { build } = readProjectConfiguration(tree, 'app1').targets;
expect(tree.exists('app1/webpack.prod.config.ts')).toBeFalsy();
expect(build.configurations.production.customWebpackConfig).toBeUndefined();
});
it('should generate the remote entry module and component correctly', async () => {
// ACT
await setupMf(tree, {
appName: 'remote1',
mfType: 'remote',
prefix: 'my-org',
standalone: false,
});
// ASSERT
expect(
tree.read('remote1/src/app/remote-entry/entry.component.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('remote1/src/app/remote-entry/entry.module.ts', 'utf-8')
).toMatchSnapshot();
});
it('should generate the remote entry component correctly when prefix is not provided', async () => {
// ACT
await setupMf(tree, {
appName: 'remote1',
mfType: 'remote',
standalone: false,
skipFormat: true,
});
// ASSERT
expect(
tree.read('remote1/src/app/remote-entry/entry.component.ts', 'utf-8')
).toMatchSnapshot();
});
it('should add the remote config to the host when --remotes flag supplied', async () => {
// ACT
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
remotes: ['remote1'],
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
// ASSERT
const mfConfigContents = tree.read(
`app1/module-federation.config.js`,
'utf-8'
);
expect(mfConfigContents).toContain(`'remote1'`);
});
it('should add the remote config to the host when --remotes flag supplied when --typescriptConfiguration=true', async () => {
// ACT
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
remotes: ['remote1'],
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
// ASSERT
const mfConfigContents = tree.read(
`app1/module-federation.config.ts`,
'utf-8'
);
expect(mfConfigContents).toContain(`'remote1'`);
});
it('should add a remote application and add it to a specified host applications webpack config when no other remote has been added to it', async () => {
// ARRANGE
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
// ACT
await setupMf(tree, {
appName: 'remote1',
mfType: 'remote',
host: 'app1',
typescriptConfiguration: false,
standalone: false,
});
// ASSERT
const hostMfConfig = tree.read('app1/module-federation.config.js', 'utf-8');
expect(hostMfConfig).toMatchSnapshot();
});
it('should add a remote application and add it to a specified host applications webpack config when no other remote has been added to it when --typescriptConfiguration=true', async () => {
// ARRANGE
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
// ACT
await setupMf(tree, {
appName: 'remote1',
mfType: 'remote',
host: 'app1',
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
// ASSERT
const hostMfConfig = tree.read('app1/module-federation.config.ts', 'utf-8');
expect(hostMfConfig).toMatchSnapshot();
});
it('should add a remote application and add it to a specified host applications webpack config that contains a remote application already', async () => {
// ARRANGE
await generateTestApplication(tree, {
name: 'remote2',
standalone: false,
skipFormat: true,
});
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
await setupMf(tree, {
appName: 'remote1',
mfType: 'remote',
host: 'app1',
port: 4201,
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
// ACT
await setupMf(tree, {
appName: 'remote2',
mfType: 'remote',
host: 'app1',
port: 4202,
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
// ASSERT
const hostMfConfig = tree.read('app1/module-federation.config.js', 'utf-8');
expect(hostMfConfig).toMatchSnapshot();
});
it('should add a remote application and add it to a specified host applications webpack config that contains a remote application already when --typescriptConfiguration=true', async () => {
// ARRANGE
await generateTestApplication(tree, {
name: 'remote2',
standalone: false,
skipFormat: true,
});
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
await setupMf(tree, {
appName: 'remote1',
mfType: 'remote',
host: 'app1',
port: 4201,
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
// ACT
await setupMf(tree, {
appName: 'remote2',
mfType: 'remote',
host: 'app1',
port: 4202,
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
// ASSERT
const hostMfConfig = tree.read('app1/module-federation.config.ts', 'utf-8');
expect(hostMfConfig).toMatchSnapshot();
});
it('should add a remote application and add it to a specified host applications router config', async () => {
// ARRANGE
await generateTestApplication(tree, {
name: 'remote2',
routing: true,
standalone: false,
skipFormat: true,
});
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
routing: true,
standalone: false,
skipFormat: true,
});
await setupMf(tree, {
appName: 'remote1',
mfType: 'remote',
host: 'app1',
port: 4201,
routing: true,
standalone: false,
skipFormat: true,
});
// ACT
await setupMf(tree, {
appName: 'remote2',
mfType: 'remote',
host: 'app1',
port: 4202,
routing: true,
standalone: false,
skipFormat: true,
});
// ASSERT
const hostAppRoutes = tree.read('app1/src/app/app.routes.ts', 'utf-8');
expect(hostAppRoutes).toMatchSnapshot();
});
it('should modify the associated cypress project to add the workaround correctly', async () => {
// ARRANGE
await generateTestApplication(tree, {
name: 'test-app',
routing: true,
standalone: false,
skipFormat: true,
e2eTestRunner: E2eTestRunner.Cypress,
});
// ACT
await setupMf(tree, {
appName: 'test-app',
mfType: 'host',
routing: true,
e2eProjectName: 'test-app-e2e',
standalone: false,
skipFormat: true,
});
// ASSERT
const cypressCommands = tree.read(
'test-app-e2e/src/support/e2e.ts',
'utf-8'
);
expect(cypressCommands).toContain(
"Cannot use 'import.meta' outside a module"
);
});
describe('--federationType=dynamic', () => {
it('should create a host with the correct configurations', async () => {
// ARRANGE & ACT
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
routing: true,
federationType: 'dynamic',
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
// ASSERT
expect(tree.read('app1/module-federation.config.js', 'utf-8')).toContain(
'remotes: []'
);
expect(
tree.exists('app1/public/module-federation.manifest.json')
).toBeTruthy();
expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchSnapshot();
});
it('should create a host with the correct configurations when --typescriptConfiguration=true', async () => {
// ARRANGE & ACT
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
routing: true,
federationType: 'dynamic',
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
// ASSERT
expect(tree.read('app1/module-federation.config.ts', 'utf-8')).toContain(
'remotes: []'
);
expect(
tree.exists('app1/public/module-federation.manifest.json')
).toBeTruthy();
expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchSnapshot();
});
it('should wire up existing remote to dynamic host correctly', async () => {
await setupMf(tree, {
appName: 'remote1',
mfType: 'remote',
port: 4201,
routing: true,
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
routing: true,
federationType: 'dynamic',
remotes: ['remote1'],
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
expect(tree.read('app1/module-federation.config.js', 'utf-8')).toContain(
'remotes: []'
);
expect(
readJson(tree, 'app1/public/module-federation.manifest.json')
).toEqual({
remote1: 'http://localhost:4201',
});
expect(
tree.read('app1/src/app/app.routes.ts', 'utf-8')
).toMatchSnapshot();
});
it('should wire up existing remote to dynamic host correctly when --typescriptConfiguration=true', async () => {
await setupMf(tree, {
appName: 'remote1',
mfType: 'remote',
port: 4201,
routing: true,
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
routing: true,
federationType: 'dynamic',
remotes: ['remote1'],
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
expect(tree.read('app1/module-federation.config.ts', 'utf-8')).toContain(
'remotes: []'
);
expect(
readJson(tree, 'app1/public/module-federation.manifest.json')
).toEqual({
remote1: 'http://localhost:4201',
});
expect(
tree.read('app1/src/app/app.routes.ts', 'utf-8')
).toMatchSnapshot();
});
});
it('should add a remote to dynamic host correctly', async () => {
// ARRANGE
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
routing: true,
federationType: 'dynamic',
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
// ACT
await setupMf(tree, {
appName: 'remote1',
mfType: 'remote',
port: 4201,
host: 'app1',
routing: true,
typescriptConfiguration: false,
standalone: false,
skipFormat: true,
});
// ASSERT
expect(tree.read('app1/module-federation.config.js', 'utf-8')).toContain(
'remotes: []'
);
expect(
readJson(tree, 'app1/public/module-federation.manifest.json')
).toEqual({
remote1: 'http://localhost:4201',
});
expect(tree.read('app1/src/app/app.routes.ts', 'utf-8')).toMatchSnapshot();
});
it('should add a remote to dynamic host correctly when --typescriptConfiguration=true', async () => {
// ARRANGE
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
routing: true,
federationType: 'dynamic',
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
// ACT
await setupMf(tree, {
appName: 'remote1',
mfType: 'remote',
port: 4201,
host: 'app1',
routing: true,
typescriptConfiguration: true,
standalone: false,
skipFormat: true,
});
// ASSERT
expect(tree.read('app1/module-federation.config.ts', 'utf-8')).toContain(
'remotes: []'
);
expect(
readJson(tree, 'app1/public/module-federation.manifest.json')
).toEqual({
remote1: 'http://localhost:4201',
});
expect(tree.read('app1/src/app/app.routes.ts', 'utf-8')).toMatchSnapshot();
});
it('should not touch the package.json when run with `--skipPackageJson`', async () => {
let initialPackageJson;
updateJson(tree, 'package.json', (json) => {
json.dependencies = {};
json.devDependencies = {};
initialPackageJson = json;
return json;
});
await setupMf(tree, {
appName: 'app1',
mfType: 'host',
skipFormat: true,
skipPackageJson: true,
});
const packageJson = readJson(tree, 'package.json');
expect(packageJson).toEqual(initialPackageJson);
});
});