mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-15 01:03:03 +08:00
chore: adds angular test app for v17 (#28513)
Issue number: Internal --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> There is no Angular v17 test app. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> There is an Angular v17 test app. New overrides: - angular.json: `browserTarget` was changed to `buildTarget` and to replace `@nguniversal` with `@angular/ssr` and `@angular-devkit/build-angular` - server.ts: Replace uses of `@nguniversal` with `@angular/ssr` New change to base: - polyfills.ts: Import [changed](https://github.com/angular/angular/blob/main/CHANGELOG.md#zonejs) from `import 'zone.js/dist/zone';` (this change is supported for all versions of Angular that we support, so should be changed in the base file) All other files were duplicated from their v16 counterparts. ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->
This commit is contained in:
@ -8,7 +8,7 @@ runs:
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
name: ionic-core
|
||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -140,7 +140,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
apps: [ng14, ng15, ng16]
|
||||
apps: [ng14, ng15, ng16, ng17]
|
||||
needs: [build-angular, build-angular-server]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
156
packages/angular/test/apps/ng17/angular.json
Normal file
156
packages/angular/test/apps/ng17/angular.json
Normal file
@ -0,0 +1,156 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"test-app": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"schematics": {},
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/test-app/browser",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"buildOptimizer": true,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "src/assets",
|
||||
"output": "assets"
|
||||
},
|
||||
{
|
||||
"glob": "**/*.svg",
|
||||
"input": "node_modules/ionicons/dist/ionicons/svg",
|
||||
"output": "./svg"
|
||||
}
|
||||
],
|
||||
"styles": ["src/styles.css"],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"progress": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb"
|
||||
}
|
||||
]
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"buildTarget": "test-app:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "test-app:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "test-app:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"buildTarget": "test-app:build"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-eslint/builder:lint",
|
||||
"options": {
|
||||
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
"builder": "@angular-devkit/build-angular:server",
|
||||
"options": {
|
||||
"outputPath": "dist/test-app/server",
|
||||
"main": "server.ts",
|
||||
"tsConfig": "tsconfig.server.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"outputHashing": "media",
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"sourceMap": false,
|
||||
"optimization": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-ssr": {
|
||||
"builder": "@angular-devkit/build-angular:ssr-dev-server",
|
||||
"options": {
|
||||
"browserTarget": "test-app:build",
|
||||
"serverTarget": "test-app:server"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "test-app:build:production",
|
||||
"serverTarget": "test-app:server:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"prerender": {
|
||||
"builder": "@angular-devkit/build-angular:prerender",
|
||||
"options": {
|
||||
"browserTarget": "test-app:build:production",
|
||||
"serverTarget": "test-app:server:production",
|
||||
"routes": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"schematicCollections": ["@angular-eslint/schematics"],
|
||||
"cache": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
0
packages/angular/test/apps/ng17/e2e/src/.gitkeep
Normal file
0
packages/angular/test/apps/ng17/e2e/src/.gitkeep
Normal file
@ -0,0 +1,5 @@
|
||||
it("should be on Angular 17", () => {
|
||||
cy.visit('/lazy');
|
||||
|
||||
cy.get('ion-title').contains('Angular 17');
|
||||
});
|
@ -0,0 +1,8 @@
|
||||
it("binding route data to inputs should work", () => {
|
||||
cy.visit('/lazy/version-test/bind-route/test?query=test');
|
||||
|
||||
cy.get('#route-params').contains('test');
|
||||
cy.get('#query-params').contains('test');
|
||||
cy.get('#data').contains('data:bindToComponentInputs');
|
||||
cy.get('#resolve').contains('resolve:bindToComponentInputs');
|
||||
});
|
@ -0,0 +1,20 @@
|
||||
describe('Modal Nav Params', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit('/lazy/version-test/modal-nav-params');
|
||||
});
|
||||
|
||||
it('should assign the rootParams when presented in a modal multiple times', () => {
|
||||
cy.contains('Open Modal').click();
|
||||
cy.get('ion-modal').should('exist').should('be.visible');
|
||||
cy.get('ion-modal').contains('OK');
|
||||
|
||||
cy.contains("Close").click();
|
||||
cy.get('ion-modal').should('not.be.visible');
|
||||
|
||||
cy.contains('Open Modal').click();
|
||||
cy.get('ion-modal').should('exist').should('be.visible');
|
||||
cy.get('ion-modal').contains('OK').should('exist');
|
||||
});
|
||||
|
||||
});
|
16223
packages/angular/test/apps/ng17/package-lock.json
generated
Normal file
16223
packages/angular/test/apps/ng17/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
69
packages/angular/test/apps/ng17/package.json
Normal file
69
packages/angular/test/apps/ng17/package.json
Normal file
@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "ionic-angular-test-app",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"sync:build": "sh scripts/build-ionic.sh",
|
||||
"sync": "sh scripts/sync.sh",
|
||||
"build": "ng build --configuration production --no-progress",
|
||||
"lint": "ng lint",
|
||||
"serve:ssr": "node dist/test-app/server/main.js",
|
||||
"build:ssr": "ng build --prod && ng run test-app:server:production",
|
||||
"dev:ssr": "ng run test-app:serve-ssr",
|
||||
"prerender": "ng run test-app:prerender",
|
||||
"cy.open": "cypress open",
|
||||
"cy.run": "cypress run",
|
||||
"test": "concurrently \"npm run start -- --configuration production\" \"wait-on http-get://localhost:4200 && npm run cy.run\" --kill-others --success first",
|
||||
"test.watch": "concurrently \"npm run start\" \"wait-on http-get://localhost:4200 && npm run cy.open\" --kill-others --success first"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.0.2",
|
||||
"@angular/common": "^17.0.2",
|
||||
"@angular/compiler": "^17.0.2",
|
||||
"@angular/core": "^17.0.2",
|
||||
"@angular/forms": "^17.0.2",
|
||||
"@angular/platform-browser": "^17.0.2",
|
||||
"@angular/platform-browser-dynamic": "^17.0.2",
|
||||
"@angular/platform-server": "^17.0.2",
|
||||
"@angular/router": "^17.0.2",
|
||||
"@angular/ssr": "^17.0.0",
|
||||
"@ionic/angular": "^7.0.0",
|
||||
"@ionic/angular-server": "^7.0.0",
|
||||
"core-js": "^3.33.2",
|
||||
"express": "^4.15.2",
|
||||
"ionicons": "^7.2.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"typescript-eslint-language-service": "^4.1.5",
|
||||
"zone.js": "~0.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.0.0",
|
||||
"@angular-eslint/builder": "^17.0.0",
|
||||
"@angular-eslint/eslint-plugin": "^17.0.0",
|
||||
"@angular-eslint/eslint-plugin-template": "^17.0.0",
|
||||
"@angular-eslint/schematics": "^17.0.0",
|
||||
"@angular-eslint/template-parser": "^17.0.0",
|
||||
"@angular/cli": "^17.0.0",
|
||||
"@angular/compiler-cli": "^17.0.2",
|
||||
"@angular/language-service": "^17.0.2",
|
||||
"@types/express": "^4.17.7",
|
||||
"@types/ws": "8.5.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"concurrently": "^6.0.0",
|
||||
"cypress": "^13.2.0",
|
||||
"eslint": "^7.26.0",
|
||||
"ts-loader": "^6.2.2",
|
||||
"ts-node": "^8.3.0",
|
||||
"typescript": "~5.2.2",
|
||||
"wait-on": "^5.2.1",
|
||||
"webpack": "^5.61.0",
|
||||
"webpack-cli": "^4.9.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
}
|
71
packages/angular/test/apps/ng17/server.ts
Normal file
71
packages/angular/test/apps/ng17/server.ts
Normal file
@ -0,0 +1,71 @@
|
||||
|
||||
import 'zone.js/node';
|
||||
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import { CommonEngine } from '@angular/ssr';
|
||||
import * as express from 'express';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import bootstrap from './src/main.server';
|
||||
|
||||
// The Express app is exported so that it can be used by serverless Functions.
|
||||
export function app(): express.Express {
|
||||
const server = express();
|
||||
const distFolder = join(process.cwd(), 'dist/test-app/browser');
|
||||
const indexHtml = existsSync(join(distFolder, 'index.original.html'))
|
||||
? join(distFolder, 'index.original.html')
|
||||
: join(distFolder, 'index.html');
|
||||
|
||||
const commonEngine = new CommonEngine();
|
||||
|
||||
server.set('view engine', 'html');
|
||||
server.set('views', distFolder);
|
||||
|
||||
// Example Express Rest API endpoints
|
||||
// server.get('/api/**', (req, res) => { });
|
||||
// Serve static files from /browser
|
||||
server.get('*.*', express.static(distFolder, {
|
||||
maxAge: '1y'
|
||||
}));
|
||||
|
||||
// All regular routes use the Angular engine
|
||||
server.get('*', (req, res, next) => {
|
||||
const { protocol, originalUrl, baseUrl, headers } = req;
|
||||
|
||||
commonEngine
|
||||
.render({
|
||||
bootstrap,
|
||||
documentFilePath: indexHtml,
|
||||
url: `${protocol}://${headers.host}${originalUrl}`,
|
||||
publicPath: distFolder,
|
||||
providers: [
|
||||
{ provide: APP_BASE_HREF, useValue: baseUrl },],
|
||||
})
|
||||
.then((html) => res.send(html))
|
||||
.catch((err) => next(err));
|
||||
});
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
function run(): void {
|
||||
const port = process.env['PORT'] || 4000;
|
||||
|
||||
// Start up the Node server
|
||||
const server = app();
|
||||
server.listen(port, () => {
|
||||
console.log(`Node Express server listening on http://localhost:${port}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Webpack will replace 'require' with '__webpack_require__'
|
||||
// '__non_webpack_require__' is a proxy to Node 'require'
|
||||
// The below code is to ensure that the server is run only when not requiring the bundle.
|
||||
declare const __non_webpack_require__: NodeRequire;
|
||||
const mainModule = __non_webpack_require__.main;
|
||||
const moduleFilename = mainModule && mainModule.filename || '';
|
||||
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
|
||||
run();
|
||||
}
|
||||
|
||||
export default bootstrap;
|
@ -0,0 +1,10 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes, { bindToComponentInputs: true })],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
@ -0,0 +1,41 @@
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bind-route',
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>bindToComponentInputs</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
<div>
|
||||
<h3>Bind Route</h3>
|
||||
<p id="route-params">Route params: id: {{id}}</p>
|
||||
<p id="query-params">Query params: query: {{query}}</p>
|
||||
<p id="data">Data: title: {{title}}</p>
|
||||
<p id="resolve">Resolve: name: {{name}}</p>
|
||||
</div>
|
||||
</ion-content>
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [IonicModule]
|
||||
})
|
||||
export class BindComponentInputsComponent implements OnInit {
|
||||
|
||||
@Input() id?: string; // path parameter
|
||||
@Input() query?: string; // query parameter
|
||||
@Input() title?: string; // data property
|
||||
@Input() name?: string; // resolve property
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log('BindComponentInputsComponent.ngOnInit', {
|
||||
id: this.id,
|
||||
query: this.query,
|
||||
title: this.title,
|
||||
name: this.name
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { IonicModule } from "@ionic/angular";
|
||||
|
||||
import { NavRootComponent } from "./nav-root.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-modal-nav-params',
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Modal Nav Params</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-button id="open">Open Modal</ion-button>
|
||||
<ion-modal #modal trigger="open">
|
||||
<ng-template>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button (click)="modal.dismiss()">Close</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-nav [root]="root" [rootParams]="rootParams"></ion-nav>
|
||||
</ion-content>
|
||||
</ng-template>
|
||||
</ion-modal>
|
||||
</ion-content>
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [IonicModule, NavRootComponent]
|
||||
})
|
||||
export class ModalNavParamsComponent {
|
||||
|
||||
root = NavRootComponent;
|
||||
rootParams = {
|
||||
params: {
|
||||
id: 123
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import { JsonPipe } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { IonicModule } from "@ionic/angular";
|
||||
|
||||
/**
|
||||
* This is used to track if any occurences of
|
||||
* the ion-nav root component being attached to
|
||||
* the DOM result in the rootParams not being
|
||||
* assigned to the component instance.
|
||||
*
|
||||
* https://github.com/ionic-team/ionic-framework/issues/27146
|
||||
*/
|
||||
let rootParamsException = false;
|
||||
|
||||
@Component({
|
||||
selector: 'app-modal-content',
|
||||
template: `
|
||||
{{ hasException ? 'ERROR' : 'OK' }}
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [IonicModule, JsonPipe]
|
||||
})
|
||||
export class NavRootComponent {
|
||||
|
||||
params: any;
|
||||
|
||||
ngOnInit() {
|
||||
if (this.params === undefined) {
|
||||
rootParamsException = true;
|
||||
}
|
||||
}
|
||||
|
||||
get hasException() {
|
||||
return rootParamsException;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: 'modal-nav-params',
|
||||
loadComponent: () => import('./modal-nav-params/modal-nav-params.component').then(m => m.ModalNavParamsComponent),
|
||||
},
|
||||
{
|
||||
path: 'bind-route/:id',
|
||||
data: {
|
||||
title: 'data:bindToComponentInputs'
|
||||
},
|
||||
resolve: {
|
||||
name: () => 'resolve:bindToComponentInputs'
|
||||
},
|
||||
loadComponent: () => import('./bind-component-inputs/bind-component-inputs.component').then(c => c.BindComponentInputsComponent)
|
||||
}
|
||||
])
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class VersionTestRoutingModule { }
|
35
packages/angular/test/apps/ng17/tsconfig.json
Normal file
35
packages/angular/test/apps/ng17/tsconfig.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/out-tsc",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"sourceMap": false,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"emitDecoratorMetadata": true,
|
||||
"typeRoots": ["node_modules/@types"],
|
||||
"lib": ["ES2022", "dom"],
|
||||
"plugins": [
|
||||
{
|
||||
"name": "typescript-eslint-language-service"
|
||||
}
|
||||
],
|
||||
"useDefineForClassFields": false
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@ import './zone-flags';
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
import 'zone.js'; // Included with Angular CLI.
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
|
Reference in New Issue
Block a user