fix(ivy): use NgZone.onStable when bootstraped using PlatformRef (#27898)
PR Close #27898
This commit is contained in:
parent
1a7f92c423
commit
b9c6df6da7
@ -3,7 +3,7 @@
|
|||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"runtime": 1497,
|
"runtime": 1497,
|
||||||
"main": 187112,
|
"main": 187134,
|
||||||
"polyfills": 59608
|
"polyfills": 59608
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,14 @@ import {ApplicationRef} from './application_ref';
|
|||||||
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
|
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
|
||||||
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
|
import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection';
|
||||||
import {Console} from './console';
|
import {Console} from './console';
|
||||||
import {InjectionToken, Injector, StaticProvider} from './di';
|
import {Injector, StaticProvider} from './di';
|
||||||
import {Inject, Optional, SkipSelf} from './di/metadata';
|
import {Inject, Optional, SkipSelf} from './di/metadata';
|
||||||
import {ErrorHandler} from './error_handler';
|
import {ErrorHandler} from './error_handler';
|
||||||
import {LOCALE_ID} from './i18n/tokens';
|
import {LOCALE_ID} from './i18n/tokens';
|
||||||
import {ComponentFactoryResolver} from './linker';
|
import {ComponentFactoryResolver} from './linker';
|
||||||
import {Compiler} from './linker/compiler';
|
import {Compiler} from './linker/compiler';
|
||||||
import {NgModule} from './metadata';
|
import {NgModule} from './metadata';
|
||||||
|
import {SCHEDULER} from './render3/component_ref';
|
||||||
import {NgZone} from './zone';
|
import {NgZone} from './zone';
|
||||||
|
|
||||||
export function _iterableDiffersFactory() {
|
export function _iterableDiffersFactory() {
|
||||||
@ -43,6 +44,7 @@ export const APPLICATION_MODULE_PROVIDERS: StaticProvider[] = [
|
|||||||
deps:
|
deps:
|
||||||
[NgZone, Console, Injector, ErrorHandler, ComponentFactoryResolver, ApplicationInitStatus]
|
[NgZone, Console, Injector, ErrorHandler, ComponentFactoryResolver, ApplicationInitStatus]
|
||||||
},
|
},
|
||||||
|
{provide: SCHEDULER, deps: [NgZone], useFactory: zoneSchedulerFactory},
|
||||||
{
|
{
|
||||||
provide: ApplicationInitStatus,
|
provide: ApplicationInitStatus,
|
||||||
useClass: ApplicationInitStatus,
|
useClass: ApplicationInitStatus,
|
||||||
@ -59,6 +61,25 @@ export const APPLICATION_MODULE_PROVIDERS: StaticProvider[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule work at next available slot.
|
||||||
|
*
|
||||||
|
* In Ivy this is just `requestAnimationFrame`. For compatibility reasons when bootstrapped
|
||||||
|
* using `platformRef.bootstrap` we need to use `NgZone.onStable` as the scheduling mechanism.
|
||||||
|
* This overrides the scheduling mechanism in Ivy to `NgZone.onStable`.
|
||||||
|
*
|
||||||
|
* @param ngZone NgZone to use for scheduling.
|
||||||
|
*/
|
||||||
|
export function zoneSchedulerFactory(ngZone: NgZone): (fn: () => void) => void {
|
||||||
|
let queue: (() => void)[] = [];
|
||||||
|
ngZone.onStable.subscribe(() => {
|
||||||
|
while (queue.length) {
|
||||||
|
queue.pop() !();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return function(fn: () => void) { queue.push(fn); };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the root injector for an app with
|
* Configures the root injector for an app with
|
||||||
* providers of `@angular/core` dependencies that `ApplicationRef` needs
|
* providers of `@angular/core` dependencies that `ApplicationRef` needs
|
||||||
|
@ -451,21 +451,22 @@ describe('Integration', () => {
|
|||||||
expect(fixture.nativeElement).toHaveText('team 33 [ , right: ]');
|
expect(fixture.nativeElement).toHaveText('team 33 [ , right: ]');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should work when an outlet is in an ngIf',
|
fixmeIvy('unknown/maybe FW-918')
|
||||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
.it('should work when an outlet is in an ngIf',
|
||||||
const fixture = createRoot(router, RootCmp);
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
router.resetConfig([{
|
router.resetConfig([{
|
||||||
path: 'child',
|
path: 'child',
|
||||||
component: OutletInNgIf,
|
component: OutletInNgIf,
|
||||||
children: [{path: 'simple', component: SimpleCmp}]
|
children: [{path: 'simple', component: SimpleCmp}]
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
router.navigateByUrl('/child/simple');
|
router.navigateByUrl('/child/simple');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(location.path()).toEqual('/child/simple');
|
expect(location.path()).toEqual('/child/simple');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should work when an outlet is added/removed', fakeAsync(() => {
|
it('should work when an outlet is added/removed', fakeAsync(() => {
|
||||||
@Component({
|
@Component({
|
||||||
@ -634,46 +635,49 @@ describe('Integration', () => {
|
|||||||
expect(fixture.nativeElement).toHaveText('team 33 [ , right: ]');
|
expect(fixture.nativeElement).toHaveText('team 33 [ , right: ]');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should navigate back and forward',
|
fixmeIvy('unknown/maybe FW-918')
|
||||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
.it('should navigate back and forward',
|
||||||
const fixture = createRoot(router, RootCmp);
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
router.resetConfig([{
|
router.resetConfig([{
|
||||||
path: 'team/:id',
|
path: 'team/:id',
|
||||||
component: TeamCmp,
|
component: TeamCmp,
|
||||||
children:
|
children: [
|
||||||
[{path: 'simple', component: SimpleCmp}, {path: 'user/:name', component: UserCmp}]
|
{path: 'simple', component: SimpleCmp},
|
||||||
}]);
|
{path: 'user/:name', component: UserCmp}
|
||||||
|
]
|
||||||
|
}]);
|
||||||
|
|
||||||
let event: NavigationStart;
|
let event: NavigationStart;
|
||||||
router.events.subscribe(e => {
|
router.events.subscribe(e => {
|
||||||
if (e instanceof NavigationStart) {
|
if (e instanceof NavigationStart) {
|
||||||
event = e;
|
event = e;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.navigateByUrl('/team/33/simple');
|
router.navigateByUrl('/team/33/simple');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/team/33/simple');
|
expect(location.path()).toEqual('/team/33/simple');
|
||||||
const simpleNavStart = event !;
|
const simpleNavStart = event !;
|
||||||
|
|
||||||
router.navigateByUrl('/team/22/user/victor');
|
router.navigateByUrl('/team/22/user/victor');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
const userVictorNavStart = event !;
|
const userVictorNavStart = event !;
|
||||||
|
|
||||||
|
|
||||||
location.back();
|
location.back();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/team/33/simple');
|
expect(location.path()).toEqual('/team/33/simple');
|
||||||
expect(event !.navigationTrigger).toEqual('hashchange');
|
expect(event !.navigationTrigger).toEqual('hashchange');
|
||||||
expect(event !.restoredState !.navigationId).toEqual(simpleNavStart.id);
|
expect(event !.restoredState !.navigationId).toEqual(simpleNavStart.id);
|
||||||
|
|
||||||
location.forward();
|
location.forward();
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(location.path()).toEqual('/team/22/user/victor');
|
expect(location.path()).toEqual('/team/22/user/victor');
|
||||||
expect(event !.navigationTrigger).toEqual('hashchange');
|
expect(event !.navigationTrigger).toEqual('hashchange');
|
||||||
expect(event !.restoredState !.navigationId).toEqual(userVictorNavStart.id);
|
expect(event !.restoredState !.navigationId).toEqual(userVictorNavStart.id);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should navigate to the same url when config changes',
|
it('should navigate to the same url when config changes',
|
||||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
@ -1004,34 +1008,36 @@ describe('Integration', () => {
|
|||||||
]);
|
]);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should handle failed navigations gracefully', fakeAsync(inject([Router], (router: Router) => {
|
fixmeIvy('unknown/maybe FW-918')
|
||||||
const fixture = createRoot(router, RootCmp);
|
.it('should handle failed navigations gracefully',
|
||||||
|
fakeAsync(inject([Router], (router: Router) => {
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
router.resetConfig([{path: 'user/:name', component: UserCmp}]);
|
router.resetConfig([{path: 'user/:name', component: UserCmp}]);
|
||||||
|
|
||||||
const recordedEvents: any[] = [];
|
const recordedEvents: any[] = [];
|
||||||
router.events.forEach(e => recordedEvents.push(e));
|
router.events.forEach(e => recordedEvents.push(e));
|
||||||
|
|
||||||
let e: any;
|
let e: any;
|
||||||
router.navigateByUrl('/invalid') !.catch(_ => e = _);
|
router.navigateByUrl('/invalid') !.catch(_ => e = _);
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
expect(e.message).toContain('Cannot match any routes');
|
expect(e.message).toContain('Cannot match any routes');
|
||||||
|
|
||||||
router.navigateByUrl('/user/fedor');
|
router.navigateByUrl('/user/fedor');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(fixture.nativeElement).toHaveText('user fedor');
|
expect(fixture.nativeElement).toHaveText('user fedor');
|
||||||
|
|
||||||
expectEvents(recordedEvents, [
|
expectEvents(recordedEvents, [
|
||||||
[NavigationStart, '/invalid'], [NavigationError, '/invalid'],
|
[NavigationStart, '/invalid'], [NavigationError, '/invalid'],
|
||||||
|
|
||||||
[NavigationStart, '/user/fedor'], [RoutesRecognized, '/user/fedor'],
|
[NavigationStart, '/user/fedor'], [RoutesRecognized, '/user/fedor'],
|
||||||
[GuardsCheckStart, '/user/fedor'], [ChildActivationStart], [ActivationStart],
|
[GuardsCheckStart, '/user/fedor'], [ChildActivationStart], [ActivationStart],
|
||||||
[GuardsCheckEnd, '/user/fedor'], [ResolveStart, '/user/fedor'],
|
[GuardsCheckEnd, '/user/fedor'], [ResolveStart, '/user/fedor'],
|
||||||
[ResolveEnd, '/user/fedor'], [ActivationEnd], [ChildActivationEnd],
|
[ResolveEnd, '/user/fedor'], [ActivationEnd], [ChildActivationEnd],
|
||||||
[NavigationEnd, '/user/fedor']
|
[NavigationEnd, '/user/fedor']
|
||||||
]);
|
]);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
// Errors should behave the same for both deferred and eager URL update strategies
|
// Errors should behave the same for both deferred and eager URL update strategies
|
||||||
['deferred', 'eager'].forEach((strat: any) => {
|
['deferred', 'eager'].forEach((strat: any) => {
|
||||||
@ -1879,45 +1885,50 @@ describe('Integration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('redirects', () => {
|
describe('redirects', () => {
|
||||||
it('should work', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
fixmeIvy('unkwnown/maybe FW-918')
|
||||||
const fixture = createRoot(router, RootCmp);
|
.it('should work',
|
||||||
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
router.resetConfig([
|
router.resetConfig([
|
||||||
{path: 'old/team/:id', redirectTo: 'team/:id'}, {path: 'team/:id', component: TeamCmp}
|
{path: 'old/team/:id', redirectTo: 'team/:id'},
|
||||||
]);
|
{path: 'team/:id', component: TeamCmp}
|
||||||
|
]);
|
||||||
|
|
||||||
router.navigateByUrl('old/team/22');
|
router.navigateByUrl('old/team/22');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(location.path()).toEqual('/team/22');
|
expect(location.path()).toEqual('/team/22');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should update Navigation object after redirects are applied',
|
fixmeIvy('unkwnown/maybe FW-918')
|
||||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
.it('should update Navigation object after redirects are applied',
|
||||||
const fixture = createRoot(router, RootCmp);
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
let initialUrl, afterRedirectUrl;
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
let initialUrl, afterRedirectUrl;
|
||||||
|
|
||||||
router.resetConfig([
|
router.resetConfig([
|
||||||
{path: 'old/team/:id', redirectTo: 'team/:id'}, {path: 'team/:id', component: TeamCmp}
|
{path: 'old/team/:id', redirectTo: 'team/:id'},
|
||||||
]);
|
{path: 'team/:id', component: TeamCmp}
|
||||||
|
]);
|
||||||
|
|
||||||
router.events.subscribe(e => {
|
router.events.subscribe(e => {
|
||||||
if (e instanceof NavigationStart) {
|
if (e instanceof NavigationStart) {
|
||||||
const navigation = router.getCurrentNavigation();
|
const navigation = router.getCurrentNavigation();
|
||||||
initialUrl = navigation && navigation.finalUrl;
|
initialUrl = navigation && navigation.finalUrl;
|
||||||
}
|
}
|
||||||
if (e instanceof RoutesRecognized) {
|
if (e instanceof RoutesRecognized) {
|
||||||
const navigation = router.getCurrentNavigation();
|
const navigation = router.getCurrentNavigation();
|
||||||
afterRedirectUrl = navigation && navigation.finalUrl;
|
afterRedirectUrl = navigation && navigation.finalUrl;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.navigateByUrl('old/team/22');
|
router.navigateByUrl('old/team/22');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(initialUrl).toBeUndefined();
|
expect(initialUrl).toBeUndefined();
|
||||||
expect(router.serializeUrl(afterRedirectUrl as any)).toBe('/team/22');
|
expect(router.serializeUrl(afterRedirectUrl as any)).toBe('/team/22');
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should not break the back button when trigger by location change',
|
it('should not break the back button when trigger by location change',
|
||||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
@ -2022,17 +2033,18 @@ describe('Integration', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
fixmeIvy('unknown/maybe FW-918')
|
||||||
const fixture = createRoot(router, RootCmp);
|
.it('works',
|
||||||
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
router.resetConfig(
|
router.resetConfig(
|
||||||
[{path: 'team/:id', component: TeamCmp, canActivate: ['alwaysTrue']}]);
|
[{path: 'team/:id', component: TeamCmp, canActivate: ['alwaysTrue']}]);
|
||||||
|
|
||||||
router.navigateByUrl('/team/22');
|
router.navigateByUrl('/team/22');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
expect(location.path()).toEqual('/team/22');
|
||||||
expect(location.path()).toEqual('/team/22');
|
})));
|
||||||
})));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('should work when given a class', () => {
|
describe('should work when given a class', () => {
|
||||||
@ -2044,17 +2056,19 @@ describe('Integration', () => {
|
|||||||
|
|
||||||
beforeEach(() => { TestBed.configureTestingModule({providers: [AlwaysTrue]}); });
|
beforeEach(() => { TestBed.configureTestingModule({providers: [AlwaysTrue]}); });
|
||||||
|
|
||||||
it('works', fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
fixmeIvy('unknown/maybe FW-918')
|
||||||
const fixture = createRoot(router, RootCmp);
|
.it('works',
|
||||||
|
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
router.resetConfig(
|
router.resetConfig(
|
||||||
[{path: 'team/:id', component: TeamCmp, canActivate: [AlwaysTrue]}]);
|
[{path: 'team/:id', component: TeamCmp, canActivate: [AlwaysTrue]}]);
|
||||||
|
|
||||||
router.navigateByUrl('/team/22');
|
router.navigateByUrl('/team/22');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(location.path()).toEqual('/team/22');
|
expect(location.path()).toEqual('/team/22');
|
||||||
})));
|
})));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('should work when returns an observable', () => {
|
describe('should work when returns an observable', () => {
|
||||||
@ -3343,37 +3357,38 @@ describe('Integration', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('route events', () => {
|
describe('route events', () => {
|
||||||
it('should fire matching (Child)ActivationStart/End events',
|
fixmeIvy('unknown/maybe FW-918')
|
||||||
fakeAsync(inject([Router], (router: Router) => {
|
.it('should fire matching (Child)ActivationStart/End events',
|
||||||
const fixture = createRoot(router, RootCmp);
|
fakeAsync(inject([Router], (router: Router) => {
|
||||||
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
router.resetConfig([{path: 'user/:name', component: UserCmp}]);
|
router.resetConfig([{path: 'user/:name', component: UserCmp}]);
|
||||||
|
|
||||||
const recordedEvents: any[] = [];
|
const recordedEvents: any[] = [];
|
||||||
router.events.forEach(e => recordedEvents.push(e));
|
router.events.forEach(e => recordedEvents.push(e));
|
||||||
|
|
||||||
router.navigateByUrl('/user/fedor');
|
router.navigateByUrl('/user/fedor');
|
||||||
advance(fixture);
|
advance(fixture);
|
||||||
|
|
||||||
expect(fixture.nativeElement).toHaveText('user fedor');
|
expect(fixture.nativeElement).toHaveText('user fedor');
|
||||||
expect(recordedEvents[3] instanceof ChildActivationStart).toBe(true);
|
expect(recordedEvents[3] instanceof ChildActivationStart).toBe(true);
|
||||||
expect(recordedEvents[3].snapshot).toBe(recordedEvents[9].snapshot.root);
|
expect(recordedEvents[3].snapshot).toBe(recordedEvents[9].snapshot.root);
|
||||||
expect(recordedEvents[9] instanceof ChildActivationEnd).toBe(true);
|
expect(recordedEvents[9] instanceof ChildActivationEnd).toBe(true);
|
||||||
expect(recordedEvents[9].snapshot).toBe(recordedEvents[9].snapshot.root);
|
expect(recordedEvents[9].snapshot).toBe(recordedEvents[9].snapshot.root);
|
||||||
|
|
||||||
expect(recordedEvents[4] instanceof ActivationStart).toBe(true);
|
expect(recordedEvents[4] instanceof ActivationStart).toBe(true);
|
||||||
expect(recordedEvents[4].snapshot.routeConfig.path).toBe('user/:name');
|
expect(recordedEvents[4].snapshot.routeConfig.path).toBe('user/:name');
|
||||||
expect(recordedEvents[8] instanceof ActivationEnd).toBe(true);
|
expect(recordedEvents[8] instanceof ActivationEnd).toBe(true);
|
||||||
expect(recordedEvents[8].snapshot.routeConfig.path).toBe('user/:name');
|
expect(recordedEvents[8].snapshot.routeConfig.path).toBe('user/:name');
|
||||||
|
|
||||||
expectEvents(recordedEvents, [
|
expectEvents(recordedEvents, [
|
||||||
[NavigationStart, '/user/fedor'], [RoutesRecognized, '/user/fedor'],
|
[NavigationStart, '/user/fedor'], [RoutesRecognized, '/user/fedor'],
|
||||||
[GuardsCheckStart, '/user/fedor'], [ChildActivationStart], [ActivationStart],
|
[GuardsCheckStart, '/user/fedor'], [ChildActivationStart], [ActivationStart],
|
||||||
[GuardsCheckEnd, '/user/fedor'], [ResolveStart, '/user/fedor'],
|
[GuardsCheckEnd, '/user/fedor'], [ResolveStart, '/user/fedor'],
|
||||||
[ResolveEnd, '/user/fedor'], [ActivationEnd], [ChildActivationEnd],
|
[ResolveEnd, '/user/fedor'], [ActivationEnd], [ChildActivationEnd],
|
||||||
[NavigationEnd, '/user/fedor']
|
[NavigationEnd, '/user/fedor']
|
||||||
]);
|
]);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
it('should allow redirection in NavigationStart',
|
it('should allow redirection in NavigationStart',
|
||||||
fakeAsync(inject([Router], (router: Router) => {
|
fakeAsync(inject([Router], (router: Router) => {
|
||||||
|
@ -170,110 +170,104 @@ withEachNg1Version(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('scope/component change-detection', () => {
|
describe('scope/component change-detection', () => {
|
||||||
it('should interleave scope and component expressions', async(() => {
|
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
|
||||||
const ng1Module = angular.module('ng1', []);
|
|
||||||
const log: string[] = [];
|
|
||||||
const l = (value: string) => {
|
|
||||||
log.push(value);
|
|
||||||
return value + ';';
|
|
||||||
};
|
|
||||||
|
|
||||||
ng1Module.directive('ng1a', () => ({template: '{{ l(\'ng1a\') }}'}));
|
|
||||||
ng1Module.directive('ng1b', () => ({template: '{{ l(\'ng1b\') }}'}));
|
|
||||||
ng1Module.run(($rootScope: any) => {
|
|
||||||
$rootScope.l = l;
|
|
||||||
$rootScope.reset = () => log.length = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'ng2',
|
|
||||||
template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}`
|
|
||||||
})
|
|
||||||
class Ng2 {
|
|
||||||
l: any;
|
|
||||||
constructor() { this.l = l; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations:
|
|
||||||
[adapter.upgradeNg1Component('ng1a'), adapter.upgradeNg1Component('ng1b'), Ng2],
|
|
||||||
imports: [BrowserModule],
|
|
||||||
})
|
|
||||||
class Ng2Module {
|
|
||||||
}
|
|
||||||
|
|
||||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
|
||||||
|
|
||||||
const element =
|
|
||||||
html('<div>{{reset(); l(\'1A\');}}<ng2>{{l(\'1B\')}}</ng2>{{l(\'1C\')}}</div>');
|
|
||||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
|
||||||
expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;');
|
|
||||||
// https://github.com/angular/angular.js/issues/12983
|
|
||||||
expect(log).toEqual(['1A', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
|
|
||||||
ref.dispose();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
fixmeIvy(
|
fixmeIvy(
|
||||||
'FW-712: Rendering is being run on next "animation frame" rather than "Zone.microTaskEmpty" trigger')
|
'FW-918: Create API and mental model to work with Host Element; and ChangeDetections')
|
||||||
.it('should propagate changes to a downgraded component inside the ngZone', async(() => {
|
.it('should interleave scope and component expressions', async(() => {
|
||||||
let appComponent: AppComponent;
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||||
let upgradeRef: UpgradeAdapterRef;
|
const ng1Module = angular.module('ng1', []);
|
||||||
|
const log: string[] = [];
|
||||||
|
const l = (value: string) => {
|
||||||
|
log.push(value);
|
||||||
|
return value + ';';
|
||||||
|
};
|
||||||
|
|
||||||
@Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'})
|
ng1Module.directive('ng1a', () => ({template: '{{ l(\'ng1a\') }}'}));
|
||||||
class AppComponent {
|
ng1Module.directive('ng1b', () => ({template: '{{ l(\'ng1b\') }}'}));
|
||||||
value?: number;
|
ng1Module.run(($rootScope: any) => {
|
||||||
constructor() { appComponent = this; }
|
$rootScope.l = l;
|
||||||
}
|
$rootScope.reset = () => log.length = 0;
|
||||||
|
});
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-child',
|
selector: 'ng2',
|
||||||
template: '<div>{{valueFromPromise}}',
|
template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}`
|
||||||
})
|
})
|
||||||
class ChildComponent {
|
class Ng2 {
|
||||||
valueFromPromise?: number;
|
l: any;
|
||||||
@Input()
|
constructor() { this.l = l; }
|
||||||
set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); }
|
|
||||||
|
|
||||||
constructor(private zone: NgZone) {}
|
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
|
||||||
if (changes['value'].isFirstChange()) return;
|
|
||||||
|
|
||||||
// HACK(ivy): Using setTimeout allows this test to pass but hides the ivy
|
|
||||||
// renderer timing BC.
|
|
||||||
// setTimeout(() => {
|
|
||||||
// expect(element.textContent).toEqual('5');
|
|
||||||
// upgradeRef.dispose();
|
|
||||||
// }, 0);
|
|
||||||
this.zone.onMicrotaskEmpty.subscribe(() => {
|
|
||||||
expect(element.textContent).toEqual('5');
|
|
||||||
upgradeRef.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
Promise.resolve().then(
|
|
||||||
() => this.valueFromPromise = changes['value'].currentValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({declarations: [AppComponent, ChildComponent], imports: [BrowserModule]})
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
adapter.upgradeNg1Component('ng1a'), adapter.upgradeNg1Component('ng1b'), Ng2
|
||||||
|
],
|
||||||
|
imports: [BrowserModule],
|
||||||
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
const ng1Module = angular.module('ng1', []).directive(
|
|
||||||
'myApp', adapter.downgradeNg2Component(AppComponent));
|
|
||||||
|
|
||||||
const element = html('<my-app></my-app>');
|
|
||||||
|
|
||||||
|
const element =
|
||||||
|
html('<div>{{reset(); l(\'1A\');}}<ng2>{{l(\'1B\')}}</ng2>{{l(\'1C\')}}</div>');
|
||||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||||
upgradeRef = ref;
|
expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;');
|
||||||
appComponent.value = 5;
|
// https://github.com/angular/angular.js/issues/12983
|
||||||
|
expect(log).toEqual(['1A', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
|
||||||
|
ref.dispose();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should propagate changes to a downgraded component inside the ngZone', async(() => {
|
||||||
|
let appComponent: AppComponent;
|
||||||
|
let upgradeRef: UpgradeAdapterRef;
|
||||||
|
|
||||||
|
@Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'})
|
||||||
|
class AppComponent {
|
||||||
|
value?: number;
|
||||||
|
constructor() { appComponent = this; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-child',
|
||||||
|
template: '<div>{{valueFromPromise}}',
|
||||||
|
})
|
||||||
|
class ChildComponent {
|
||||||
|
valueFromPromise?: number;
|
||||||
|
@Input()
|
||||||
|
set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); }
|
||||||
|
|
||||||
|
constructor(private zone: NgZone) {}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (changes['value'].isFirstChange()) return;
|
||||||
|
|
||||||
|
this.zone.onMicrotaskEmpty.subscribe(() => {
|
||||||
|
expect(element.textContent).toEqual('5');
|
||||||
|
upgradeRef.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.resolve().then(() => this.valueFromPromise = changes['value'].currentValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations: [AppComponent, ChildComponent], imports: [BrowserModule]})
|
||||||
|
class Ng2Module {
|
||||||
|
}
|
||||||
|
|
||||||
|
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||||
|
const ng1Module = angular.module('ng1', []).directive(
|
||||||
|
'myApp', adapter.downgradeNg2Component(AppComponent));
|
||||||
|
|
||||||
|
const element = html('<my-app></my-app>');
|
||||||
|
|
||||||
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||||
|
upgradeRef = ref;
|
||||||
|
appComponent.value = 5;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
// This test demonstrates https://github.com/angular/angular/issues/6385
|
// This test demonstrates https://github.com/angular/angular/issues/6385
|
||||||
// which was invalidly fixed by https://github.com/angular/angular/pull/6386
|
// which was invalidly fixed by https://github.com/angular/angular/pull/6386
|
||||||
// it('should not trigger $digest from an async operation in a watcher', async(() => {
|
// it('should not trigger $digest from an async operation in a watcher', async(() => {
|
||||||
|
@ -21,116 +21,111 @@ withEachNg1Version(() => {
|
|||||||
beforeEach(() => destroyPlatform());
|
beforeEach(() => destroyPlatform());
|
||||||
afterEach(() => destroyPlatform());
|
afterEach(() => destroyPlatform());
|
||||||
|
|
||||||
it('should interleave scope and component expressions', async(() => {
|
fixmeIvy('FW-918: Create API and mental model to work with Host Element; and ChangeDetections')
|
||||||
const log: string[] = [];
|
.it('should interleave scope and component expressions', async(() => {
|
||||||
const l = (value: string) => {
|
const log: string[] = [];
|
||||||
log.push(value);
|
const l = (value: string) => {
|
||||||
return value + ';';
|
log.push(value);
|
||||||
};
|
return value + ';';
|
||||||
|
};
|
||||||
|
|
||||||
@Directive({selector: 'ng1a'})
|
@Directive({selector: 'ng1a'})
|
||||||
class Ng1aComponent extends UpgradeComponent {
|
class Ng1aComponent extends UpgradeComponent {
|
||||||
constructor(elementRef: ElementRef, injector: Injector) {
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
super('ng1a', elementRef, injector);
|
super('ng1a', elementRef, injector);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Directive({selector: 'ng1b'})
|
|
||||||
class Ng1bComponent extends UpgradeComponent {
|
|
||||||
constructor(elementRef: ElementRef, injector: Injector) {
|
|
||||||
super('ng1b', elementRef, injector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'ng2',
|
|
||||||
template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}`
|
|
||||||
})
|
|
||||||
class Ng2Component {
|
|
||||||
l = l;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [Ng1aComponent, Ng1bComponent, Ng2Component],
|
|
||||||
entryComponents: [Ng2Component],
|
|
||||||
imports: [BrowserModule, UpgradeModule]
|
|
||||||
})
|
|
||||||
class Ng2Module {
|
|
||||||
ngDoBootstrap() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ng1Module = angular.module('ng1', [])
|
|
||||||
.directive('ng1a', () => ({template: '{{ l(\'ng1a\') }}'}))
|
|
||||||
.directive('ng1b', () => ({template: '{{ l(\'ng1b\') }}'}))
|
|
||||||
.directive('ng2', downgradeComponent({component: Ng2Component}))
|
|
||||||
.run(($rootScope: angular.IRootScopeService) => {
|
|
||||||
$rootScope.l = l;
|
|
||||||
$rootScope.reset = () => log.length = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
const element =
|
|
||||||
html('<div>{{reset(); l(\'1A\');}}<ng2>{{l(\'1B\')}}</ng2>{{l(\'1C\')}}</div>');
|
|
||||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
|
||||||
expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;');
|
|
||||||
expect(log).toEqual(['1A', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
fixmeIvy(
|
|
||||||
'FW-712: Rendering is being run on next "animation frame" rather than "Zone.microTaskEmpty" trigger')
|
|
||||||
.it('should propagate changes to a downgraded component inside the ngZone', async(() => {
|
|
||||||
const element = html('<my-app></my-app>');
|
|
||||||
let appComponent: AppComponent;
|
|
||||||
|
|
||||||
@Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'})
|
|
||||||
class AppComponent {
|
|
||||||
value?: number;
|
|
||||||
constructor() { appComponent = this; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'my-child',
|
|
||||||
template: '<div>{{ valueFromPromise }}</div>',
|
|
||||||
})
|
|
||||||
class ChildComponent {
|
|
||||||
valueFromPromise?: number;
|
|
||||||
@Input()
|
|
||||||
set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); }
|
|
||||||
|
|
||||||
constructor(private zone: NgZone) {}
|
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
|
||||||
if (changes['value'].isFirstChange()) return;
|
|
||||||
|
|
||||||
// HACK(ivy): Using setTimeout allows this test to pass but hides the ivy renderer
|
|
||||||
// timing BC.
|
|
||||||
// setTimeout(() => expect(element.textContent).toEqual('5'), 0);
|
|
||||||
this.zone.onMicrotaskEmpty.subscribe(
|
|
||||||
() => { expect(element.textContent).toEqual('5'); });
|
|
||||||
|
|
||||||
// Create a micro-task to update the value to be rendered asynchronously.
|
|
||||||
Promise.resolve().then(
|
|
||||||
() => this.valueFromPromise = changes['value'].currentValue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Directive({selector: 'ng1b'})
|
||||||
|
class Ng1bComponent extends UpgradeComponent {
|
||||||
|
constructor(elementRef: ElementRef, injector: Injector) {
|
||||||
|
super('ng1b', elementRef, injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ng2',
|
||||||
|
template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}`
|
||||||
|
})
|
||||||
|
class Ng2Component {
|
||||||
|
l = l;
|
||||||
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent, ChildComponent],
|
declarations: [Ng1aComponent, Ng1bComponent, Ng2Component],
|
||||||
entryComponents: [AppComponent],
|
entryComponents: [Ng2Component],
|
||||||
imports: [BrowserModule, UpgradeModule]
|
imports: [BrowserModule, UpgradeModule]
|
||||||
})
|
})
|
||||||
class Ng2Module {
|
class Ng2Module {
|
||||||
ngDoBootstrap() {}
|
ngDoBootstrap() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ng1Module = angular.module('ng1', []).directive(
|
const ng1Module = angular.module('ng1', [])
|
||||||
'myApp', downgradeComponent({component: AppComponent}));
|
.directive('ng1a', () => ({template: '{{ l(\'ng1a\') }}'}))
|
||||||
|
.directive('ng1b', () => ({template: '{{ l(\'ng1b\') }}'}))
|
||||||
|
.directive('ng2', downgradeComponent({component: Ng2Component}))
|
||||||
|
.run(($rootScope: angular.IRootScopeService) => {
|
||||||
|
$rootScope.l = l;
|
||||||
|
$rootScope.reset = () => log.length = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const element =
|
||||||
|
html('<div>{{reset(); l(\'1A\');}}<ng2>{{l(\'1B\')}}</ng2>{{l(\'1C\')}}</div>');
|
||||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||||
appComponent.value = 5;
|
expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;');
|
||||||
|
expect(log).toEqual(['1A', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should propagate changes to a downgraded component inside the ngZone', async(() => {
|
||||||
|
const element = html('<my-app></my-app>');
|
||||||
|
let appComponent: AppComponent;
|
||||||
|
|
||||||
|
@Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'})
|
||||||
|
class AppComponent {
|
||||||
|
value?: number;
|
||||||
|
constructor() { appComponent = this; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-child',
|
||||||
|
template: '<div>{{ valueFromPromise }}</div>',
|
||||||
|
})
|
||||||
|
class ChildComponent {
|
||||||
|
valueFromPromise?: number;
|
||||||
|
@Input()
|
||||||
|
set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); }
|
||||||
|
|
||||||
|
constructor(private zone: NgZone) {}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (changes['value'].isFirstChange()) return;
|
||||||
|
|
||||||
|
this.zone.onMicrotaskEmpty.subscribe(
|
||||||
|
() => { expect(element.textContent).toEqual('5'); });
|
||||||
|
|
||||||
|
// Create a micro-task to update the value to be rendered asynchronously.
|
||||||
|
Promise.resolve().then(() => this.valueFromPromise = changes['value'].currentValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [AppComponent, ChildComponent],
|
||||||
|
entryComponents: [AppComponent],
|
||||||
|
imports: [BrowserModule, UpgradeModule]
|
||||||
|
})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ng1Module = angular.module('ng1', []).directive(
|
||||||
|
'myApp', downgradeComponent({component: AppComponent}));
|
||||||
|
|
||||||
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||||
|
appComponent.value = 5;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
// This test demonstrates https://github.com/angular/angular/issues/6385
|
// This test demonstrates https://github.com/angular/angular/issues/6385
|
||||||
// which was invalidly fixed by https://github.com/angular/angular/pull/6386
|
// which was invalidly fixed by https://github.com/angular/angular/pull/6386
|
||||||
// it('should not trigger $digest from an async operation in a watcher', async(() => {
|
// it('should not trigger $digest from an async operation in a watcher', async(() => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user