/**
 * @license
 * Copyright Google Inc. All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */

import { JsonParseMode, parseJson } from '@angular-devkit/core';
import { EmptyTree } from '@angular-devkit/schematics';
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import { getWorkspaceTargets, updateWorkspaceTargets } from './update-workspace-config_spec';

// tslint:disable-next-line: no-any
function readJsonFile(tree: UnitTestTree, path: string): any {
  return parseJson(tree.readContent(path).toString(), JsonParseMode.Loose);
}

function overrideJsonFile(tree: UnitTestTree, path: string, newContent: object) {
  tree.overwrite(path, JSON.stringify(newContent, undefined, 2));
}

const defaultTsConfigOptions = {
  extends: './tsconfig.json',
  compilerOptions: {
    module: 'es2015',
    outDir: './out-tsc/app',
    types: [],
  },
  exclude: [
    'src/test.ts',
    'src/**/*.spec.ts',
  ],
  angularCompilerOptions: {
    enableIvy: true,
  },
};

// tslint:disable:no-big-function
describe('Migration to version 9', () => {
  describe('Update applications tsconfig', () => {
    const schematicRunner = new SchematicTestRunner(
      'migrations',
      require.resolve('../migration-collection.json'),
    );

    let tree: UnitTestTree;

    beforeEach(async () => {
      tree = new UnitTestTree(new EmptyTree());
      tree = await schematicRunner
        .runExternalSchematicAsync(
          require.resolve('../../collection.json'),
          'ng-new',
          {
            name: 'migration-test',
            version: '1.2.3',
            directory: '.',
          },
          tree,
        )
        .toPromise();

      // Pre version 9 - tsconfig.json was the base tsconfig file.
      tree.overwrite('tsconfig.json', tree.readContent('tsconfig.json'));
    });

    it('should update apps tsConfig with stricter files inclusions', async () => {
      overrideJsonFile(tree, 'tsconfig.app.json', defaultTsConfigOptions);
      const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise();
      const { exclude, files, include } = readJsonFile(tree2 , 'tsconfig.app.json');
      expect(exclude).toBeUndefined();
      expect(files).toEqual(['src/main.ts', 'src/polyfills.ts']);
      expect(include).toEqual(['src/**/*.d.ts']);
    });

    it('should resolve paths correctly even if they are using windows separators', async () => {
      const tree2 = await schematicRunner
        .runExternalSchematicAsync(
          require.resolve('../../collection.json'),
          'application',
          {
            name: 'another-app',
          },
          tree,
        )
        .toPromise();

      const tsCfgPath = 'projects/another-app/tsconfig.app.json';
      overrideJsonFile(tree2, tsCfgPath, defaultTsConfigOptions);
      const config = getWorkspaceTargets(tree2, 'another-app');
      config.build.options.main = 'projects\\another-app\\src\\main.ts';
      config.build.options.polyfills = 'projects\\another-app\\src\\polyfills.ts';
      config.build.options.tsConfig = 'projects\\another-app\\tsconfig.app.json';
      updateWorkspaceTargets(tree2, config, 'another-app');

      const tree3 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree2.branch()).toPromise();
      const { exclude, files } = readJsonFile(tree3, tsCfgPath);
      expect(exclude).toBeUndefined();
      expect(files).toEqual(['src/main.ts', 'src/polyfills.ts']);
    });

    it('should update apps tsConfig when tsconfig has include', async () => {
      const tsConfigContent = {
        ...defaultTsConfigOptions,
        include: ['foo.ts'],
      };

      overrideJsonFile(tree, 'tsconfig.app.json', tsConfigContent);

      const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise();
      const { files, include } = readJsonFile(tree2, 'tsconfig.app.json');
      expect(files).toEqual(['src/main.ts', 'src/polyfills.ts']);
      expect(include).toEqual(['foo.ts']);
    });

    it(`should update include '**/*.ts' in apps tsConfig to '**/*.d.ts'`, async () => {
      const tsConfigContent = {
        ...defaultTsConfigOptions,
        include: ['src/**/*.ts'],
        exclude: ['test.ts'],
      };

      overrideJsonFile(tree, 'tsconfig.app.json', tsConfigContent);

      const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise();
      const { files, include, exclude } = readJsonFile(tree2, 'tsconfig.app.json');
      expect(files).toEqual(['src/main.ts', 'src/polyfills.ts']);
      expect(include).toEqual(['src/**/*.d.ts']);
      expect(exclude).toBeUndefined();
    });

    it(`should update include '**/*.ts' in apps tsConfig to '**/*.d.ts' when includes contains multiple elements`, async () => {
      const tsConfigContent = {
        ...defaultTsConfigOptions,
        include: ['foo.ts', 'src/**/*.ts'],
      };

      overrideJsonFile(tree, 'tsconfig.app.json', tsConfigContent);

      const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise();
      const { files, include, exclude } = readJsonFile(tree2, 'tsconfig.app.json');
      expect(files).toEqual(['src/main.ts', 'src/polyfills.ts']);
      expect(include).toEqual(['foo.ts', 'src/**/*.d.ts']);
      expect(exclude).toBeUndefined();
    });

    it(`should remove angularCompilerOptions when enableIvy is true and it's the only option`, async () => {
      overrideJsonFile(tree, 'tsconfig.app.json', defaultTsConfigOptions);
      const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise();
      const { angularCompilerOptions } = readJsonFile(tree2, 'tsconfig.app.json');
      expect(angularCompilerOptions).toBeUndefined();
    });

    it('should remove enableIvy only when true and there are other angularCompilerOptions', async () => {
      const tsConfigContent = {
        ...defaultTsConfigOptions,
        angularCompilerOptions: {
          enableIvy: true,
          fullTemplateTypeCheck: true,
        },
      };

      overrideJsonFile(tree, 'tsconfig.app.json', tsConfigContent);
      const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise();
      const { angularCompilerOptions } = readJsonFile(tree2, 'tsconfig.app.json');
      expect(angularCompilerOptions.enableIvy).toBeUndefined();
      expect(angularCompilerOptions.fullTemplateTypeCheck).toBe(true);
    });

    it('should note remove enableIvy is set to false', async () => {
      const tsConfigContent = {
        ...defaultTsConfigOptions,
        angularCompilerOptions: {
          enableIvy: false,
          fullTemplateTypeCheck: true,
        },
      };

      overrideJsonFile(tree, 'tsconfig.app.json', tsConfigContent);
      const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise();
      const { angularCompilerOptions } = readJsonFile(tree2, 'tsconfig.app.json');
      expect(angularCompilerOptions.enableIvy).toBe(false);
      expect(angularCompilerOptions.fullTemplateTypeCheck).toBe(true);
    });

    it(`should remove 'module' if in an extended configuration`, async () => {
      const tsConfigContent = {
        ...defaultTsConfigOptions,
        angularCompilerOptions: {
          enableIvy: true,
          fullTemplateTypeCheck: true,
        },
      };

      overrideJsonFile(tree, 'tsconfig.app.json', tsConfigContent);
      const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise();
      const { compilerOptions } = readJsonFile(tree2, 'tsconfig.app.json');
      expect(compilerOptions.module).toBeUndefined();

      const { compilerOptions: workspaceCompilerOptions } = readJsonFile(tree2, 'tsconfig.json');
      expect(workspaceCompilerOptions.module).toBe('esnext');
    });

    it(`should set 'module' to 'esnext' if app tsconfig is not extended`, async () => {
      const tsConfigContent = {
        ...defaultTsConfigOptions,
        extends: undefined,
        angularCompilerOptions: {
          enableIvy: true,
          fullTemplateTypeCheck: true,
        },
      };

      overrideJsonFile(tree, 'tsconfig.app.json', tsConfigContent);
      const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise();
      const { compilerOptions } = readJsonFile(tree2, 'tsconfig.app.json');
      expect(compilerOptions.module).toBe('esnext');
    });

    it(`should set 'module' to 'commonjs' in server tsconfig`, async () => {
      const tsConfigContent = {
        ...defaultTsConfigOptions,
        compilerOptions: {
          module: 'es2015',
          outDir: './out-tsc/server',
        },
        angularCompilerOptions: {
          enableIvy: true,
        },
      };

      tree = await schematicRunner
        .runExternalSchematicAsync(
          require.resolve('../../collection.json'),
          'universal',
          {
            clientProject: 'migration-test',
          },
          tree,
        )
        .toPromise();

      overrideJsonFile(tree, 'tsconfig.server.json', tsConfigContent);
      const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise();
      const { compilerOptions } = readJsonFile(tree2, 'tsconfig.server.json');
      expect(compilerOptions.module).toBe('commonjs');
    });

    it(`should set 'module' to 'esnext' in workspace tsconfig`, async () => {
      const tsConfigContent = {
        ...defaultTsConfigOptions,
        extends: undefined,
      };

      overrideJsonFile(tree, 'tsconfig.json', tsConfigContent);
      const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise();
      const { compilerOptions } = readJsonFile(tree2, 'tsconfig.json');
      expect(compilerOptions.module).toBe('esnext');
    });
  });
});
