Angular 1.x/2 Hybrid, karma tests not bootstrapping ng1 app

I currently have a Hybrid Angular app (2.4.9 and 1.5.0) using angular-cli. Currently, when running our application, we are able to bootstrap the 1.5 app correctly:

// main.ts
import ...

platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
  angular.element(document).ready(() => { 
    const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
    upgrade.bootstrap(document.body, ['myApp'], {strictDi: true});
  });
});

However, in our test.ts file:

// test.ts
// This file is required by karma.conf.js and loads recursively all the .spec and framework files

import ...;

declare var __karma__: any;
declare var require: any;

__karma__.loaded = function () {};

getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule,
  // I'm assuming that I need to call 'boostrapModule()' somehow here...
  platformBrowserDynamicTesting() 
);

const context = require.context('./', true, /\.spec\.ts$/);

context.keys().map(context);

__karma__.start();

I'm not exactly sure how to bootstrap our 1.5 application into the test environment, all I've gotten is Module 'myApp' is not available!, and my Google skills have failed trying to find an example.


ANSWERS:


I was hoping the bounty I added last night would mean I could log on this morning to a nice solution laid out for me. Alas, it did not. So instead I spent the day cruising around many SO answers and github issues getting it to work. I'm sorry I did not keep track of everything that helped me to credit them, but here is my solution. It is probably not ideal, but it is working so far so I hope it is a good start.

This github issue indicates that downgradeComponent isn't going to work for now, so I went with what I assume is an older technique using UpgradeAdapter. Note that this technique does not use initTestEnvironment. Here are the relevant snippets, with some explanations below:

// downgrade.ts:
export const componentsToDowngrade = {
    heroDetail: HeroDetailComponent,
    ...
};
export function downgradeForApp() {
    forOwn(componentsToDowngrade, (component, name) => {
        app.directive(name!, downgradeComponent({ component }));
    });
}


// main.ts:
downgradeForApp();
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory).then((platformRef) => {
    ...
});

// test.ts:
require("../src/polyfills.ts");
require("zone.js/dist/proxy");
require('zone.js/dist/sync-test');
require("zone.js/dist/mocha-patch");

// test-helper.ts
let upgradeAdapterRef: UpgradeAdapterRef;
const upgradeAdapter = new UpgradeAdapter(AppModule);
forEach(componentsToDowngrade, (component, selectorName) => {
    angular.module("app").directive(
        selectorName!,
        upgradeAdapter.downgradeNg2Component(component) as any,
    );
});
export function useAdaptedModule() {
    beforeEach(() => {
        upgradeAdapterRef = upgradeAdapter.registerForNg1Tests(["app"]);
    });
}
export function it(expectation: string, callback: () => void) {
    test(expectation, (done) => {
        inject(() => { }); // triggers some kind of needed initialization
        upgradeAdapterRef.ready(() => {
            try {
                callback();
                done();
            } catch (ex) { done(ex); }
        });
    });
}


// hero-detail.component.spec.ts
import { it, useAdaptedModule } from "test-helpers/sd-app-helpers";
describe("", () => {
    useAdaptedModule();
    it("behaves as expected", () => { ... });
});

A few of the highlights from that code:

  • I downgrade components differently for tests than for the app, so I made a DRY list of them in downgrade.ts
  • I downgrade components for the main app from main.ts by calling downgradeForApp() as shown above (used with AOT for a production bundle), and also main-jit.ts, not shown above (used for development)
  • I showed the imports I needed to add to start integrating Angular components into my AngularJS tests. You may need more/different ones depending e.g. on whether your tests are asynchronous, or you use Jasmine instead of Mocha.
  • At the beginning of each test that needs to use downgraded components, I "bootstrap" things with useAdaptedModule() instead of beforeEach(angular.mock.module("app"));
  • I import an alternative it from my helpers, which wraps the it provided by Mocha. None of my tests are asynchronous; if you have some that are it may require tweaking. I do not know how it may need to be adapted for Jasmine.

A caveat: Instantiating the component must happen within an it callback so that it happens within upgradeAdapterRef.ready(...). Trying to do it within a beforeEach is too soon.



 MORE:


 ? Node server to intercept all request from angular (http) to java spring backend
 ? Angular change url without executing routeprovider
 ? Angular js - call a route without view
 ? .config $routeprovider angularjs
 ? Angular JS not loading view
 ? AngularJS directive vs routeProvider
 ? controller not found through routeProvider
 ? routeProvider not working correctly
 ? AngularJS redirecting to another ng-app module
 ? Issue in angular js routeProvider