import Vue from 'vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { createStore } from '~/ide/stores';
import Clientside from '~/ide/components/preview/clientside.vue';
import timeoutPromise from 'spec/helpers/set_timeout_promise_helper';
import { resetStore, file } from '../../helpers';

describe('IDE clientside preview', () => {
  let vm;
  let Component;

  beforeAll(() => {
    Component = Vue.extend(Clientside);
  });

  beforeEach(done => {
    const store = createStore();

    Vue.set(store.state.entries, 'package.json', {
      ...file('package.json'),
    });
    Vue.set(store.state, 'currentProjectId', 'gitlab-ce');
    Vue.set(store.state.projects, 'gitlab-ce', {
      visibility: 'public',
    });

    vm = createComponentWithStore(Component, store);

    spyOn(vm, 'getFileData').and.returnValue(Promise.resolve());
    spyOn(vm, 'getRawFileData').and.returnValue(Promise.resolve(''));
    spyOn(vm, 'initManager');

    vm.$mount();

    timeoutPromise()
      .then(() => vm.$nextTick())
      .then(done)
      .catch(done.fail);
  });

  afterEach(() => {
    vm.$destroy();
    resetStore(vm.$store);
  });

  describe('without main entry', () => {
    it('creates sandpack manager', () => {
      expect(vm.initManager).not.toHaveBeenCalled();
    });
  });

  describe('with main entry', () => {
    beforeEach(done => {
      Vue.set(
        vm.$store.state.entries['package.json'],
        'raw',
        JSON.stringify({
          main: 'index.js',
        }),
      );

      vm.$nextTick()
        .then(() => vm.initPreview())
        .then(vm.$nextTick)
        .then(done)
        .catch(done.fail);
    });

    it('creates sandpack manager', () => {
      expect(vm.initManager).toHaveBeenCalledWith(
        '#ide-preview',
        {
          files: jasmine.any(Object),
          entry: '/index.js',
          showOpenInCodeSandbox: true,
        },
        {
          fileResolver: {
            isFile: jasmine.any(Function),
            readFile: jasmine.any(Function),
          },
        },
      );
    });
  });

  describe('computed', () => {
    describe('normalizedEntries', () => {
      beforeEach(done => {
        vm.$store.state.entries['index.js'] = {
          ...file('index.js'),
          type: 'blob',
          raw: 'test',
        };
        vm.$store.state.entries['index2.js'] = {
          ...file('index2.js'),
          type: 'blob',
          content: 'content',
        };
        vm.$store.state.entries.tree = {
          ...file('tree'),
          type: 'tree',
        };
        vm.$store.state.entries.empty = {
          ...file('empty'),
          type: 'blob',
        };

        vm.$nextTick(done);
      });

      it('returns flattened list of blobs with content', () => {
        expect(vm.normalizedEntries).toEqual({
          '/index.js': {
            code: 'test',
          },
          '/index2.js': {
            code: 'content',
          },
        });
      });
    });

    describe('mainEntry', () => {
      it('returns false when package.json is empty', () => {
        expect(vm.mainEntry).toBe(false);
      });

      it('returns main key from package.json', done => {
        Vue.set(
          vm.$store.state.entries['package.json'],
          'raw',
          JSON.stringify({
            main: 'index.js',
          }),
        );

        vm.$nextTick(() => {
          expect(vm.mainEntry).toBe('index.js');

          done();
        });
      });
    });

    describe('showPreview', () => {
      it('returns false if no mainEntry', () => {
        expect(vm.showPreview).toBe(false);
      });

      it('returns false if loading', done => {
        Vue.set(
          vm.$store.state.entries['package.json'],
          'raw',
          JSON.stringify({
            main: 'index.js',
          }),
        );
        vm.loading = true;

        vm.$nextTick(() => {
          expect(vm.showPreview).toBe(false);

          done();
        });
      });

      it('returns true if not loading and mainEntry exists', done => {
        Vue.set(
          vm.$store.state.entries['package.json'],
          'raw',
          JSON.stringify({
            main: 'index.js',
          }),
        );
        vm.loading = false;

        vm.$nextTick(() => {
          expect(vm.showPreview).toBe(true);

          done();
        });
      });
    });

    describe('showEmptyState', () => {
      it('returns true if no mainEnry exists', () => {
        expect(vm.showEmptyState).toBe(true);
      });

      it('returns false if loading', done => {
        Vue.set(
          vm.$store.state.entries['package.json'],
          'raw',
          JSON.stringify({
            main: 'index.js',
          }),
        );
        vm.loading = true;

        vm.$nextTick(() => {
          expect(vm.showEmptyState).toBe(false);

          done();
        });
      });

      it('returns false if not loading and mainEntry exists', done => {
        Vue.set(
          vm.$store.state.entries['package.json'],
          'raw',
          JSON.stringify({
            main: 'index.js',
          }),
        );
        vm.loading = false;

        vm.$nextTick(() => {
          expect(vm.showEmptyState).toBe(false);

          done();
        });
      });
    });

    describe('showOpenInCodeSandbox', () => {
      it('returns true when visiblity is public', () => {
        expect(vm.showOpenInCodeSandbox).toBe(true);
      });

      it('returns false when visiblity is private', done => {
        vm.$store.state.projects['gitlab-ce'].visibility = 'private';

        vm.$nextTick(() => {
          expect(vm.showOpenInCodeSandbox).toBe(false);

          done();
        });
      });
    });

    describe('sandboxOpts', () => {
      beforeEach(done => {
        vm.$store.state.entries['index.js'] = {
          ...file('index.js'),
          type: 'blob',
          raw: 'test',
        };
        Vue.set(
          vm.$store.state.entries['package.json'],
          'raw',
          JSON.stringify({
            main: 'index.js',
          }),
        );

        vm.$nextTick(done);
      });

      it('returns sandbox options', () => {
        expect(vm.sandboxOpts).toEqual({
          files: {
            '/index.js': {
              code: 'test',
            },
            '/package.json': {
              code: '{"main":"index.js"}',
            },
          },
          entry: '/index.js',
          showOpenInCodeSandbox: true,
        });
      });
    });
  });

  describe('methods', () => {
    describe('loadFileContent', () => {
      it('calls getFileData', () => {
        expect(vm.getFileData).toHaveBeenCalledWith({
          path: 'package.json',
          makeFileActive: false,
        });
      });

      it('calls getRawFileData', () => {
        expect(vm.getRawFileData).toHaveBeenCalledWith({ path: 'package.json' });
      });
    });

    describe('update', () => {
      beforeEach(() => {
        jasmine.clock().install();

        vm.sandpackReady = true;
        vm.manager.updatePreview = jasmine.createSpy('updatePreview');
        vm.manager.listener = jasmine.createSpy('updatePreview');
      });

      afterEach(() => {
        jasmine.clock().uninstall();
      });

      it('calls initPreview if manager is empty', () => {
        spyOn(vm, 'initPreview');
        vm.manager = {};

        vm.update();

        jasmine.clock().tick(250);

        expect(vm.initPreview).toHaveBeenCalled();
      });

      it('calls updatePreview', () => {
        vm.update();

        jasmine.clock().tick(250);

        expect(vm.manager.updatePreview).toHaveBeenCalledWith(vm.sandboxOpts);
      });
    });
  });

  describe('template', () => {
    it('renders ide-preview element when showPreview is true', done => {
      Vue.set(
        vm.$store.state.entries['package.json'],
        'raw',
        JSON.stringify({
          main: 'index.js',
        }),
      );
      vm.loading = false;

      vm.$nextTick(() => {
        expect(vm.$el.querySelector('#ide-preview')).not.toBe(null);
        done();
      });
    });

    it('renders empty state', done => {
      vm.loading = false;

      vm.$nextTick(() => {
        expect(vm.$el.textContent).toContain(
          'Preview your web application using Web IDE client-side evaluation.',
        );

        done();
      });
    });

    it('renders loading icon', done => {
      vm.loading = true;

      vm.$nextTick(() => {
        expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
        done();
      });
    });
  });
});
