Skip to content

Commit 785522e

Browse files
feat(testing): support TestBed and export jasmine/mocha zone scripts
- update zone build and add jasmine/mocha test scripts, see: NativeScript/zone.js#1 - refactor testing and zone-js modules to work with each other - add generous TestBed setup helpers in testing/src/util.ts - add single import test helpers for zone-js patches in zone-js/testing.[framework].ts files - update peer deps to reflect updated zone (could peer dep be dropped since prebuilts are exported?) chore: disable most tests and enable ones that use TestApp one-by-one - add test entry point script that inits the test environment for TestBed - list view, modal dialog pass - detached-loader and platform-filter-component could use feedback. see todos chore: replace the remaining TestApp usages in test suite - xdescribe the failing tests. - I think the remaining problems boil down to `dumpView` indicating the ComponentFixture comes back with the root components, and `@ViewChild` not finding DetachedLoader by its class. - remove some duplication in testing utilities chore: cleanup and remove some diff noise from a few tests chore: remove more test noise - are the line-endings different on this file? :( chore: convince dumpView and TestComponentRenderer to agree on things - all the TestBed tests except for the DetachedLoader ones and a single Renderer lifecycle are passing. - update NativeScriptRenderer.selectRootElement to find views by ID when given a selector of an id string. TestBed uses this when creating componentRefs to get at the correct views. - change NativeScriptTestComponentRenderer to inject only a ProxyViewContainer which mimics what TestApp did. - update dumpView to strip off the new "source" data attached to a view.toString() result. chore: cleanup lint chore: make nTestBed helpers automatically clean up test components - before the components were destroyed by TestBed, but not removed from the rootView. - maintain a list of active fixtures for a set of tests, and remove them all when the tests complete. - reorder to the testing utils to flow better when reading (start with test init, then before/after then render components) - clean up some lint. chore: fix issue where nTestBedBeforeEach overwrote its own imports - Fixes the DetachedLoader tests, and makes them MUCH simpler. - When you configure the test bed module, you need to specify a full list of imports, because they completely overwrite the imports array that is used. - That's yet another reason to use the provided helper functions, they merge in the common {N} imports for you. - ... and some lint cleanup chore: make renderer lifecyle test more robust - the first assertion is that the view after init has been called. rather than assert it, just wait for it using an observable and avoid asserting about timing and implementation specific details of the system. Specifically this removes the assumption that `app.tick()` will advance time and call `ngAfterViewInit` on the component. chore: cleanup from review - re-enable all tests in karma.conf.js - remove some diff noise
1 parent 75077fa commit 785522e

23 files changed

+3732
-1521
lines changed

Diff for: .gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ tags
1313
!/nativescript-angular/postinstall.js
1414
!/nativescript-angular/hooks/**/*.js
1515
!/nativescript-angular/gulpfile.js
16-
!/nativescript-angular/zone-js/**/*.js
16+
!/nativescript-angular/zone-js/dist/*.js
1717

1818
.tscache
1919
.nvm

Diff for: nativescript-angular/renderer.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
} from "@angular/core";
66

77
import { Device } from "tns-core-modules/platform";
8-
import { View } from "tns-core-modules/ui/core/view";
8+
import { View, getViewById } from "tns-core-modules/ui/core/view";
99
import { addCss } from "tns-core-modules/application";
1010
import { topmost } from "tns-core-modules/ui/frame";
1111
import { profile } from "tns-core-modules/profiling";
@@ -110,6 +110,10 @@ export class NativeScriptRenderer extends Renderer2 {
110110
@profile
111111
selectRootElement(selector: string): NgView {
112112
traceLog("NativeScriptRenderer.selectRootElement: " + selector);
113+
if (selector && selector[0] === "#") {
114+
const result = getViewById(this.rootView, selector.slice(1));
115+
return (result || this.rootView) as NgView;
116+
}
113117
return this.rootView;
114118
}
115119

Diff for: nativescript-angular/testing/index.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { NgModule } from "@angular/core";
2+
import { TestComponentRenderer } from "@angular/core/testing";
3+
import { NativeScriptTestComponentRenderer } from "./src/nativescript_test_component_renderer";
4+
import { COMMON_PROVIDERS } from "../platform-common";
5+
import { APP_ROOT_VIEW } from "../platform-providers";
6+
import { testingRootView } from "./src/util";
7+
export * from "./src/util";
8+
9+
/**
10+
* Providers array is exported for cases where a custom module has to be constructed
11+
* to test a particular piece of code. This can happen, for example, if you are trying
12+
* to test dynamic component loading and need to specify an entryComponent for the testing
13+
* module.
14+
*/
15+
export const NATIVESCRIPT_TESTING_PROVIDERS: any[] = [
16+
COMMON_PROVIDERS,
17+
{provide: APP_ROOT_VIEW, useFactory: testingRootView},
18+
{provide: TestComponentRenderer, useClass: NativeScriptTestComponentRenderer},
19+
];
20+
21+
/**
22+
* NativeScript testing support module. Enables use of TestBed for angular components, directives,
23+
* pipes, and services.
24+
*/
25+
@NgModule({
26+
providers: NATIVESCRIPT_TESTING_PROVIDERS
27+
})
28+
export class NativeScriptTestingModule {
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Injectable } from "@angular/core";
2+
import { TestComponentRenderer } from "@angular/core/testing";
3+
import { topmost } from "tns-core-modules/ui/frame";
4+
import { LayoutBase } from "tns-core-modules/ui/layouts/layout-base";
5+
import { ProxyViewContainer } from "tns-core-modules/ui/proxy-view-container";
6+
7+
/**
8+
* A NativeScript based implementation of the TestComponentRenderer.
9+
*/
10+
@Injectable()
11+
export class NativeScriptTestComponentRenderer extends TestComponentRenderer {
12+
13+
insertRootElement(rootElId: string) {
14+
const page = topmost().currentPage;
15+
16+
const layout = new ProxyViewContainer();
17+
layout.id = rootElId;
18+
19+
const rootLayout = page.layoutView as LayoutBase;
20+
rootLayout.addChild(layout);
21+
}
22+
23+
}
24+

Diff for: nativescript-angular/testing/src/util.ts

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
2+
import { View } from "tns-core-modules/ui/core/view";
3+
import { topmost } from "tns-core-modules/ui/frame";
4+
import { LayoutBase } from "tns-core-modules/ui/layouts/layout-base";
5+
import { ComponentFixture, TestBed } from "@angular/core/testing";
6+
import { NgModule, Type } from "@angular/core";
7+
import { NativeScriptModule } from "../../nativescript.module";
8+
import { platformBrowserDynamicTesting } from "@angular/platform-browser-dynamic/testing";
9+
import { NS_COMPILER_PROVIDERS } from "../../platform";
10+
import { NATIVESCRIPT_TESTING_PROVIDERS, NativeScriptTestingModule } from "../index";
11+
import { CommonModule } from "@angular/common";
12+
/**
13+
* Get a reference to the root application view.
14+
*/
15+
export function testingRootView(): View {
16+
return topmost().currentPage.content;
17+
}
18+
19+
/**
20+
* Declared test contexts. When the suite is done this map should be empty if all lifecycle
21+
* calls have happened as expected.
22+
* @private
23+
*/
24+
const activeTestFixtures: ComponentFixture<any>[][] = [];
25+
26+
/**
27+
* Return a promise that resolves after (durationMs) milliseconds
28+
*/
29+
export function promiseWait(durationMs: number) {
30+
return () => new Promise((resolve) => setTimeout(() => resolve(), durationMs));
31+
}
32+
33+
/**
34+
* Perform basic TestBed environment initialization. Call this once in the main entry point to your tests.
35+
*/
36+
export function nTestBedInit() {
37+
TestBed.initTestEnvironment(
38+
NativeScriptTestingModule,
39+
platformBrowserDynamicTesting(NS_COMPILER_PROVIDERS)
40+
);
41+
}
42+
43+
/**
44+
* Helper for configuring a TestBed instance for rendering components for test. Ideally this
45+
* would not be needed, and in truth it's just a wrapper to eliminate some boilerplate. It
46+
* exists because when you need to specify `entryComponents` for a test the setup becomes quite
47+
* a bit more complex than if you're just doing a basic component test.
48+
*
49+
* More about entryComponents complexity: https://github.com/angular/angular/issues/12079
50+
*
51+
* Use:
52+
* ```
53+
* beforeEach(nTestBedBeforeEach([MyComponent,MyFailComponent]));
54+
* ```
55+
*
56+
* **NOTE*** Remember to pair with {@see nTestBedAfterEach}
57+
*
58+
* @param components Any components that you will create during the test
59+
* @param providers Any services your tests depend on
60+
* @param imports Any module imports your tests depend on
61+
* @param entryComponents Any entry components that your tests depend on
62+
*/
63+
export function nTestBedBeforeEach(
64+
components: any[],
65+
providers: any[] = [],
66+
imports: any[] = [],
67+
entryComponents: any[] = []) {
68+
return (done) => {
69+
activeTestFixtures.push([]);
70+
// If there are no entry components we can take the simple path.
71+
if (entryComponents.length === 0) {
72+
TestBed.configureTestingModule({
73+
declarations: [...components],
74+
providers: [...providers],
75+
imports: [NativeScriptModule, ...imports]
76+
});
77+
} else {
78+
// If there are entry components, we have to reset the testing platform.
79+
//
80+
// There's got to be a better way... (o_O)
81+
TestBed.resetTestEnvironment();
82+
@NgModule({
83+
declarations: entryComponents,
84+
exports: entryComponents,
85+
entryComponents: entryComponents
86+
})
87+
class EntryComponentsTestModule {
88+
}
89+
TestBed.initTestEnvironment(
90+
EntryComponentsTestModule,
91+
platformBrowserDynamicTesting(NS_COMPILER_PROVIDERS)
92+
);
93+
TestBed.configureTestingModule({
94+
declarations: components,
95+
imports: [
96+
NativeScriptModule, NativeScriptTestingModule, CommonModule,
97+
...imports
98+
],
99+
providers: [...providers, ...NATIVESCRIPT_TESTING_PROVIDERS],
100+
});
101+
}
102+
TestBed.compileComponents()
103+
.then(() => done())
104+
.catch((e) => {
105+
console.log(`Failed to instantiate test component with error: ${e}`);
106+
console.log(e.stack);
107+
done();
108+
});
109+
};
110+
}
111+
112+
/**
113+
* Helper for a basic component TestBed clean up.
114+
* @param resetEnv When true the testing environment will be reset
115+
* @param resetFn When resetting the environment, use this init function
116+
*/
117+
export function nTestBedAfterEach(resetEnv = true, resetFn = nTestBedInit) {
118+
return () => {
119+
if (activeTestFixtures.length === 0) {
120+
throw new Error(
121+
`There are no more declared fixtures.` +
122+
`Did you call "nTestBedBeforeEach" and "nTestBedAfterEach" an equal number of times?`
123+
);
124+
}
125+
const root = testingRootView() as LayoutBase;
126+
const fixtures = activeTestFixtures.pop();
127+
fixtures.forEach((fixture) => {
128+
root.removeChild(fixture.nativeElement);
129+
fixture.destroy();
130+
});
131+
TestBed.resetTestingModule();
132+
if (resetEnv) {
133+
TestBed.resetTestEnvironment();
134+
resetFn();
135+
}
136+
};
137+
}
138+
139+
/**
140+
* Render a component using the TestBed helper, and return a promise that resolves when the
141+
* ComponentFixture is fully initialized.
142+
*/
143+
export function nTestBedRender<T>(componentType: Type<T>): Promise<ComponentFixture<T>> {
144+
const fixture = TestBed.createComponent(componentType);
145+
fixture.detectChanges();
146+
return fixture.whenRenderingDone()
147+
// TODO(jd): it seems that the whenStable and whenRenderingDone utilities of ComponentFixture
148+
// do not work as expected. I looked at how to fix it and it's not clear how to provide
149+
// a {N} specific subclass, because ComponentFixture is newed directly rather than injected
150+
// What to do about it? Maybe fakeAsync can help? For now just setTimeout for 100ms (x_X)
151+
.then(promiseWait(100))
152+
.then(() => {
153+
const list = activeTestFixtures[activeTestFixtures.length - 1];
154+
if (!list) {
155+
console.warn(
156+
"nTestBedRender called without nTestBedBeforeEach/nTestBedAfter each. " +
157+
"You are responsible for calling 'fixture.destroy()' when your test is done " +
158+
"in order to clean up the components that are created."
159+
);
160+
} else {
161+
list.push(fixture);
162+
}
163+
return fixture;
164+
});
165+
}

Diff for: nativescript-angular/zone-js/README.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Zone.js for NativeScript
2+
---
3+
4+
Zone.js is a library that aims to intercept all asynchronous API calls made in an environment, in order
5+
to wrap them into coherent execution contexts over time.
6+
7+
NativeScript executes inside an environment that Zone.js is not designed to work in, so a custom Zone.js output
8+
must be created.
9+
10+
Find out more about this in the [Upgrading Zone.js document](../../doc/upgrading-zonejs.md)

0 commit comments

Comments
 (0)