Skip to content

Commit

Permalink
Angular 15 upgrade (#1655)
Browse files Browse the repository at this point in the history
# Pull Request

## 🀨 Rationale

Update to Angular 15.2 for nimble-angular.

Resolves #1079 
Resolves #1017 
Resolves #1570
Resolves #732
Resolves #1665 

## πŸ‘©β€πŸ’» Implementation

- Update Angular version to 15.2 using Angular's upgrade utility
- Create forks of some of Angular's CVAs and Angular's RouterLink
directive under
`angular-workspace\projects\ni\nimble-angular\src\thirdparty\directives\`
with modifications to get them to work as expected as base classes for
nimble directives
- Add explicit dependency on `source-map-loader` to `nimble-components`
to ensure the tests continue to run as they previously did

## πŸ§ͺ Testing

- Ran the Angular app and verified it worked as expected
- Existing unit tests pass
- Copied & adapted Angular tests for CVAs and RouterLink directive into
`angular-workspace\projects\ni\nimble-angular\src\thirdparty\directives\tests\`
- Added new unit tests for each nimble CVA that verifies that
`ngModelChange` is only called once when a control's value changes
- Added new unit tests for each routerLink directive that verifies that
the href was sanitized
- Used the built package to verify it works correctly in
SystemLinkShared

## βœ… Checklist

<!--- Review the list and put an x in the boxes that apply or ~~strike
through~~ around items that don't (along with an explanation). -->

- [ ] I have updated the project documentation to reflect my changes or
determined no changes are needed.

---------

Co-authored-by: Mert Akinc <7282195+m-akinc@users.noreply.github.com>
Co-authored-by: Milan Raj <rajsite@users.noreply.github.com>
  • Loading branch information
3 people committed Nov 28, 2023
1 parent 88ca90f commit bf12590
Show file tree
Hide file tree
Showing 57 changed files with 14,301 additions and 2,672 deletions.
2 changes: 1 addition & 1 deletion angular-workspace/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"@ni/nimble-angular": {
"projectType": "library",
"root": "projects/ni/nimble-angular",
"sourceRoot": "projects/ni/nimble-angular/src",
"sourceRoot": "projects/ni/nimble-angular",
"prefix": "lib",
"architect": {
"build": {
Expand Down
28 changes: 14 additions & 14 deletions angular-workspace/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,24 @@
"format": "ng lint --fix"
},
"dependencies": {
"@angular/animations": "^14.2.0",
"@angular/common": "^14.2.0",
"@angular/compiler": "^14.2.0",
"@angular/core": "^14.2.0",
"@angular/forms": "^14.2.0",
"@angular/platform-browser": "^14.2.0",
"@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0",
"@angular/animations": "^15.2.10",
"@angular/common": "^15.2.10",
"@angular/compiler": "^15.2.10",
"@angular/core": "^15.2.10",
"@angular/forms": "^15.2.10",
"@angular/platform-browser": "^15.2.10",
"@angular/platform-browser-dynamic": "^15.2.10",
"@angular/router": "^15.2.10",
"@ni/nimble-components": "*",
"rxjs": "^7.3.0",
"tslib": "^2.2.0",
"zone.js": "^0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.2.0",
"@angular/cli": "^14.2.0",
"@angular/compiler-cli": "^14.2.0",
"@angular/localize": "^14.2.0",
"@angular-devkit/build-angular": "^15.2.10",
"@angular/cli": "^15.2.10",
"@angular/compiler-cli": "^15.2.10",
"@angular/localize": "^15.2.10",
"@microsoft/fast-web-utilities": "^6.0.0",
"@ni/eslint-config-angular": "^5.0.3",
"@ni/eslint-config-javascript": "^4.2.0",
Expand All @@ -54,9 +54,9 @@
"karma-coverage": "^2.0.3",
"karma-jasmine": "^5.1.0",
"karma-jasmine-html-reporter": "^2.0.0",
"ng-packagr": "^14.1.0",
"ng-packagr": "^15.2.2",
"playwright": "^1.30.0",
"rollup": "^3.10.1",
"typescript": "~4.8.2"
}
}
}
17 changes: 0 additions & 17 deletions angular-workspace/projects/example-client-app/.browserslistrc

This file was deleted.

11 changes: 0 additions & 11 deletions angular-workspace/projects/example-client-app/src/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@ import {
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';

declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
keys(): string[],
<T>(id: string): T
}
};

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
Expand All @@ -24,10 +17,6 @@ getTestBed().initTestEnvironment(
errorOnUnknownProperties: true
}
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

// Elevate console errors to test failures
// eslint-disable-next-line no-console, @typescript-eslint/no-explicit-any
Expand Down
2 changes: 1 addition & 1 deletion angular-workspace/projects/ni/nimble-angular/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ To avoid this, call `processUpdates()` after each `fakeAsync` test. This will sy

## Angular Support Policy

`@ni/nimble-angular` supports Angular 14. To see the exact version it's tested against, view the library's `package.json`.
`@ni/nimble-angular` supports Angular 15. To see the exact version it's tested against, view the library's `package.json`.

If your application uses an older Angular version you can temporarily use an older version of `@ni/nimble-angular` (versions exist back to Angular 12) but it will not contain the latest features so it's preferable to update the application's Angular dependency.

Expand Down
10 changes: 5 additions & 5 deletions angular-workspace/projects/ni/nimble-angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
}
},
"peerDependencies": {
"@angular/common": "^14.2.0",
"@angular/core": "^14.2.0",
"@angular/forms": "^14.2.0",
"@angular/localize": "^14.2.0",
"@angular/router": "^14.2.0",
"@angular/common": "^15.2.10",
"@angular/core": "^15.2.10",
"@angular/forms": "^15.2.10",
"@angular/localize": "^15.2.10",
"@angular/router": "^15.2.10",
"@ni/nimble-components": "^20.14.10"
},
"dependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { Directive, ElementRef, Injector } from '@angular/core';
import { Directive, ElementRef, Inject, Renderer2 } from '@angular/core';
import { LocationStrategy } from '@angular/common';
import { ActivatedRoute, Router, RouterLinkWithHref } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { RouterLink } from '../../thirdparty/directives/router_link';

/**
* Base class for Nimble router link directives that go on disableable elements
*/
@Directive()
export class DisableableRouterLinkWithHrefDirective<T extends { disabled: boolean }> extends RouterLinkWithHref {
public constructor(injector: Injector, private readonly elementRef: ElementRef<T>) {
super(injector.get(Router), injector.get(ActivatedRoute), injector.get(LocationStrategy));
export class DisableableRouterLinkWithHrefDirective<T extends { disabled: boolean }> extends RouterLink {
public constructor(
@Inject(Router) router: Router,
@Inject(ActivatedRoute) route: ActivatedRoute,
renderer: Renderer2,
private readonly elementRef: ElementRef<T>,
@Inject(LocationStrategy) locationStrategy?: LocationStrategy
) {
super(router, route, undefined, renderer, elementRef, locationStrategy);
}

public override onClick(button: number, ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean): boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { Component, ElementRef, Sanitizer, SecurityContext, ViewChild } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { Router } from '@angular/router';
import { CommonModule, Location } from '@angular/common';
Expand Down Expand Up @@ -31,8 +31,12 @@ describe('Nimble anchor button RouterLinkWithHrefDirective', () => {
let innerAnchor: HTMLAnchorElement;
let routerNavigateByUrlSpy: jasmine.Spy;
let anchorClickHandlerSpy: jasmine.Spy;
let sanitizer: jasmine.SpyObj<Sanitizer>;

beforeEach(() => {
sanitizer = jasmine.createSpyObj<Sanitizer>('Sanitizer', ['sanitize']);
sanitizer.sanitize.and.callFake((_, value: string) => value);

TestBed.configureTestingModule({
declarations: [TestHostComponent, BlankComponent],
imports: [NimbleAnchorButtonModule,
Expand All @@ -42,6 +46,9 @@ describe('Nimble anchor button RouterLinkWithHrefDirective', () => {
{ path: 'page1', component: BlankComponent },
{ path: 'start', component: TestHostComponent }
], { useHash: true })
],
providers: [
{ provide: Sanitizer, useValue: sanitizer }
]
});
});
Expand Down Expand Up @@ -106,4 +113,8 @@ describe('Nimble anchor button RouterLinkWithHrefDirective', () => {
expect(routerNavigateByUrlSpy).not.toHaveBeenCalled();
}));
});

it('sanitized initial href created from nimbleRouterLink', () => {
expect(sanitizer.sanitize).toHaveBeenCalledWith(SecurityContext.URL, '/page1?param1=true');
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { Component, ElementRef, Sanitizer, SecurityContext, ViewChild } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { Router } from '@angular/router';
import { CommonModule, Location } from '@angular/common';
Expand Down Expand Up @@ -31,8 +31,12 @@ describe('Nimble anchor menu item RouterLinkWithHrefDirective', () => {
let innerAnchor: HTMLAnchorElement;
let routerNavigateByUrlSpy: jasmine.Spy;
let anchorClickHandlerSpy: jasmine.Spy;
let sanitizer: jasmine.SpyObj<Sanitizer>;

beforeEach(() => {
sanitizer = jasmine.createSpyObj<Sanitizer>('Sanitizer', ['sanitize']);
sanitizer.sanitize.and.callFake((_, value: string) => value);

TestBed.configureTestingModule({
declarations: [TestHostComponent, BlankComponent],
imports: [NimbleAnchorMenuItemModule,
Expand All @@ -42,6 +46,9 @@ describe('Nimble anchor menu item RouterLinkWithHrefDirective', () => {
{ path: 'page1', component: BlankComponent },
{ path: 'start', component: TestHostComponent }
], { useHash: true })
],
providers: [
{ provide: Sanitizer, useValue: sanitizer }
]
});
});
Expand Down Expand Up @@ -106,4 +113,8 @@ describe('Nimble anchor menu item RouterLinkWithHrefDirective', () => {
expect(routerNavigateByUrlSpy).not.toHaveBeenCalled();
}));
});

it('sanitized initial href created from nimbleRouterLink', () => {
expect(sanitizer.sanitize).toHaveBeenCalledWith(SecurityContext.URL, '/page1?param1=true');
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { Component, ElementRef, Sanitizer, SecurityContext, ViewChild } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { Router } from '@angular/router';
import { CommonModule, Location } from '@angular/common';
Expand Down Expand Up @@ -31,8 +31,12 @@ describe('Nimble anchor tab RouterLinkWithHrefDirective', () => {
let innerAnchor: HTMLAnchorElement;
let routerNavigateByUrlSpy: jasmine.Spy;
let anchorClickHandlerSpy: jasmine.Spy;
let sanitizer: jasmine.SpyObj<Sanitizer>;

beforeEach(() => {
sanitizer = jasmine.createSpyObj<Sanitizer>('Sanitizer', ['sanitize']);
sanitizer.sanitize.and.callFake((_, value: string) => value);

TestBed.configureTestingModule({
declarations: [TestHostComponent, BlankComponent],
imports: [NimbleAnchorTabModule,
Expand All @@ -42,6 +46,9 @@ describe('Nimble anchor tab RouterLinkWithHrefDirective', () => {
{ path: 'page1', component: BlankComponent },
{ path: 'start', component: TestHostComponent }
], { useHash: true })
],
providers: [
{ provide: Sanitizer, useValue: sanitizer }
]
});
});
Expand Down Expand Up @@ -106,4 +113,8 @@ describe('Nimble anchor tab RouterLinkWithHrefDirective', () => {
expect(routerNavigateByUrlSpy).not.toHaveBeenCalled();
}));
});

it('sanitized initial href created from nimbleRouterLink', () => {
expect(sanitizer.sanitize).toHaveBeenCalledWith(SecurityContext.URL, '/page1?param1=true');
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { Component, ElementRef, Sanitizer, SecurityContext, ViewChild } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { Router } from '@angular/router';
import { CommonModule, Location } from '@angular/common';
Expand Down Expand Up @@ -31,8 +31,12 @@ describe('Nimble anchor tree item RouterLinkWithHrefDirective', () => {
let innerAnchor: HTMLAnchorElement;
let routerNavigateByUrlSpy: jasmine.Spy;
let anchorClickHandlerSpy: jasmine.Spy;
let sanitizer: jasmine.SpyObj<Sanitizer>;

beforeEach(() => {
sanitizer = jasmine.createSpyObj<Sanitizer>('Sanitizer', ['sanitize']);
sanitizer.sanitize.and.callFake((_, value: string) => value);

TestBed.configureTestingModule({
declarations: [TestHostComponent, BlankComponent],
imports: [NimbleAnchorTreeItemModule,
Expand All @@ -42,6 +46,9 @@ describe('Nimble anchor tree item RouterLinkWithHrefDirective', () => {
{ path: 'page1', component: BlankComponent },
{ path: 'start', component: TestHostComponent }
], { useHash: true })
],
providers: [
{ provide: Sanitizer, useValue: sanitizer }
]
});
});
Expand Down Expand Up @@ -106,4 +113,8 @@ describe('Nimble anchor tree item RouterLinkWithHrefDirective', () => {
expect(routerNavigateByUrlSpy).not.toHaveBeenCalled();
}));
});

it('sanitized initial href created from nimbleRouterLink', () => {
expect(sanitizer.sanitize).toHaveBeenCalledWith(SecurityContext.URL, '/page1?param1=true');
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Directive, Input } from '@angular/core';
import { RouterLinkWithHref } from '@angular/router';
import { RouterLink } from '../../thirdparty/directives/router_link';

/**
* Selectors used for built-in Angular RouterLink directives:
Expand All @@ -15,7 +15,7 @@ import { RouterLinkWithHref } from '@angular/router';
* won't also be an active RouterLink directive incorrectly handling navigation.
*/
@Directive({ selector: 'nimble-anchor[nimbleRouterLink]' })
export class NimbleAnchorRouterLinkWithHrefDirective extends RouterLinkWithHref {
export class NimbleAnchorRouterLinkWithHrefDirective extends RouterLink {
@Input()
public set nimbleRouterLink(commands: never[] | string | null | undefined) {
this.routerLink = commands;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { Component, ElementRef, Sanitizer, SecurityContext, ViewChild } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { Router } from '@angular/router';
import { CommonModule, Location } from '@angular/common';
Expand Down Expand Up @@ -31,8 +31,12 @@ describe('Nimble anchor RouterLinkWithHrefDirective', () => {
let innerAnchor: HTMLAnchorElement;
let routerNavigateByUrlSpy: jasmine.Spy;
let anchorClickHandlerSpy: jasmine.Spy;
let sanitizer: jasmine.SpyObj<Sanitizer>;

beforeEach(() => {
sanitizer = jasmine.createSpyObj<Sanitizer>('Sanitizer', ['sanitize']);
sanitizer.sanitize.and.callFake((_, value: string) => value);

TestBed.configureTestingModule({
declarations: [TestHostComponent, BlankComponent],
imports: [NimbleAnchorModule,
Expand All @@ -42,6 +46,9 @@ describe('Nimble anchor RouterLinkWithHrefDirective', () => {
{ path: 'page1', component: BlankComponent },
{ path: 'start', component: TestHostComponent }
], { useHash: true })
],
providers: [
{ provide: Sanitizer, useValue: sanitizer }
]
});
});
Expand Down Expand Up @@ -98,4 +105,8 @@ describe('Nimble anchor RouterLinkWithHrefDirective', () => {
expect(routerNavigateByUrlSpy).not.toHaveBeenCalled();
}));
});

it('sanitized initial href created from nimbleRouterLink', () => {
expect(sanitizer.sanitize).toHaveBeenCalledWith(SecurityContext.URL, '/page1?param1=true');
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { LocationStrategy } from '@angular/common';
import { Directive, ElementRef, HostListener, Injector, Input } from '@angular/core';
import { ActivatedRoute, Router, RouterLinkWithHref } from '@angular/router';
import { Directive, ElementRef, HostListener, Inject, Input, Renderer2 } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import type { BreadcrumbItem } from '@ni/nimble-components/dist/esm/breadcrumb-item';
import { RouterLink } from '../../thirdparty/directives/router_link';

/**
* Selectors used for built-in Angular RouterLink directives:
Expand All @@ -17,14 +18,20 @@ import type { BreadcrumbItem } from '@ni/nimble-components/dist/esm/breadcrumb-i
* won't also be an active RouterLink directive incorrectly handling navigation.
*/
@Directive({ selector: 'nimble-breadcrumb-item[nimbleRouterLink]' })
export class NimbleBreadcrumbItemRouterLinkWithHrefDirective extends RouterLinkWithHref {
export class NimbleBreadcrumbItemRouterLinkWithHrefDirective extends RouterLink {
@Input()
public set nimbleRouterLink(commands: never[] | string | null | undefined) {
this.routerLink = commands;
}

public constructor(injector: Injector, private readonly elementRef: ElementRef<BreadcrumbItem>) {
super(injector.get(Router), injector.get(ActivatedRoute), injector.get(LocationStrategy));
public constructor(
@Inject(Router) router: Router,
@Inject(ActivatedRoute) route: ActivatedRoute,
renderer: Renderer2,
private readonly elementRef: ElementRef<BreadcrumbItem>,
@Inject(LocationStrategy) locationStrategy?: LocationStrategy
) {
super(router, route, undefined, renderer, elementRef, locationStrategy);
}

public override onClick(_button: number, _ctrlKey: boolean, _shiftKey: boolean, _altKey: boolean, _metaKey: boolean): boolean {
Expand Down

0 comments on commit bf12590

Please sign in to comment.