/* eslint-disable prefer-destructuring */
/* eslint-disable no-param-reassign */
import { fixture, assert, nextFrame, html, aTimeout, oneEvent } from '@open-wc/testing';
import { AmfDefinitions } from '@api-client/core/build/browser.js';
import sinon from 'sinon';
import { AmfLoader } from '../AmfLoader.js';
import { DomEventsAmfStore } from '../../../src/amf/store/DomEventsAmfStore.js';
import { SelectionType } from '../../../src/amf/types.js';
import ApiServerPickerElement from '../../../src/elements/amf/ApiServerPicker.element.js';
import { EventTypes } from '../../../src/events/amf/EventTypes.js';
import Input from '../../../src/ui/input/Input.js';
import UiIconButton from '../../../src/ui/button/UiIconButton.js';
import '../../../src/define/amf/api-server-picker.js';

describe('ApiServerPickerElement', () => {
  const loader = new AmfLoader();
  const store = new DomEventsAmfStore(window);
  store.listen();

  let serversApiModel: AmfDefinitions.IAmfDocument;
  let noServersApiModel: AmfDefinitions.IAmfDocument;

  before(async () => {
    serversApiModel = await loader.getGraph('servers-api');
    noServersApiModel = await loader.getGraph('no-servers-api');
  });

  async function basicFixture(): Promise<ApiServerPickerElement> {
    const element = (await fixture(html`
      <api-server-picker queryDebouncerTimeout="0"></api-server-picker>`)) as ApiServerPickerElement;
    await aTimeout(2);
    return element;
  }
  
  async function allowCustomFixture(): Promise<ApiServerPickerElement> {
    const element = await fixture(html`
    <api-server-picker 
      custom 
      queryDebouncerTimeout="0"
    ></api-server-picker>`) as ApiServerPickerElement;
    await aTimeout(2);
    return element;
  }

  async function customInputFixture(): Promise<ApiServerPickerElement> {
    const element = (await fixture(html`<api-server-picker
      custom
      type="custom"
      queryDebouncerTimeout="0"
    ></api-server-picker>`)) as ApiServerPickerElement;
    await aTimeout(2);
    return element;
  }

  async function extraOptionsFixture(): Promise<ApiServerPickerElement> {
    const element = (await fixture(html`
      <api-server-picker queryDebouncerTimeout="0">
        <ui-list-item slot="custom-base-uri" data-value="http://customServer.com">
          http://customServer.com
        </ui-list-item>
        <ui-list-item slot="custom-base-uri" data-value="http://customServer2.com">
          http://customServer2.com
        </ui-list-item>
    </api-server-picker>`)) as ApiServerPickerElement;
    await aTimeout(2);
    return element;
  }

  async function slotChangeFixture(): Promise<ApiServerPickerElement> {
    const element = (await fixture(html`
      <api-server-picker queryDebouncerTimeout="0">
        <ui-list-item slot="custom-base-uri" data-value="http://customServer.com">
          http://customServer.com
        </ui-list-item>
        <ui-list-item slot="custom-base-uri" data-value="http://customServer2.com">
          http://customServer2.com
        </ui-list-item>
        <ui-list-item data-value="http://customServer3.com">
          http://customServer3.com
        </ui-list-item>
    </api-server-picker>`)) as ApiServerPickerElement;
    await aTimeout(2);
    return element;
  }

  async function baseUriFixture(): Promise<ApiServerPickerElement> {
    const element = (await fixture(html`<api-server-picker 
      queryDebouncerTimeout="0" 
      custom 
      baseUri="https://www.google.com"
    ></api-server-picker>`)) as ApiServerPickerElement;
    await aTimeout(2);
    return element;
  }

  async function autoSelectFixture(): Promise<ApiServerPickerElement> {
    const element = (await fixture(html`
      <api-server-picker auto queryDebouncerTimeout="0"></api-server-picker>
    `)) as ApiServerPickerElement;
    await aTimeout(2);
    return element;
  }

  async function unselectableFixture(): Promise<ApiServerPickerElement> {
    const element = (await fixture(html`
      <api-server-picker queryDebouncerTimeout="0">
      <ui-list-item slot="custom-base-uri">
        srv 1
      </ui-list-item>
      <ui-list-item slot="custom-base-uri" data-value="http://srv.com">
        srv 2
      </ui-list-item>
      </api-server-picker>
    `)) as ApiServerPickerElement;
    await aTimeout(2);
    return element;
  }

  async function extraOptionsCustomFixture(): Promise<ApiServerPickerElement> {
    const element = (await fixture(html`
      <api-server-picker custom queryDebouncerTimeout="0">
        <ui-list-item slot="custom-base-uri" data-value="http://customServer.com">
          http://customServer.com
        </ui-list-item>
        <ui-list-item slot="custom-base-uri" data-value="http://customServer2.com">
          http://customServer2.com
        </ui-list-item>
    </api-server-picker>`)) as ApiServerPickerElement;
    await aTimeout(2);
    return element;
  }

  async function autoSelectFixtureWithSlots(): Promise<ApiServerPickerElement> {
    const element = (await fixture(html`
      <api-server-picker custom auto queryDebouncerTimeout="0">
        <ui-list-item slot="custom-base-uri" data-value="http://customServer.com">
          http://customServer.com
        </ui-list-item>
        <ui-list-item slot="custom-base-uri" data-value="http://customServer2.com">
          http://customServer2.com
        </ui-list-item>
    </api-server-picker>`)) as ApiServerPickerElement;
    await aTimeout(2);
    return element;
  }

  async function domainIdFixture(domainId?: string, domainType?: SelectionType): Promise<ApiServerPickerElement> {
    const element = (await fixture(html`
      <api-server-picker queryDebouncerTimeout="0" .domainId="${domainId}" .domainType="${domainType}">
    </api-server-picker>`)) as ApiServerPickerElement;
    await aTimeout(2);
    return element;
  }

  async function domainIdFixtureWithValue(domainId?: string, domainType?: SelectionType, value?: string): Promise<ApiServerPickerElement> {
    const element = (await fixture(html`
      <api-server-picker queryDebouncerTimeout="0"
        .domainId="${domainId}"
        .domainType="${domainType}"
        .value="${value}"
      >
    </api-server-picker>`)) as ApiServerPickerElement;
    await aTimeout(2);
    return element;
  }

  describe('basic usage', () => {
    it('renders empty dropdown', async () => {
      const element = await basicFixture();
      const node = element.shadowRoot!.querySelector('.api-server-dropdown')!;
      assert.exists(node, 'dropdown exists');
      const items = node.querySelectorAll('ui-list-items');
      assert.lengthOf(items, 0, 'has no list items');
    });

    it('does not render custom input', async () => {
      const element = await basicFixture();
      const node = element.shadowRoot!.querySelector('.uri-input');
      assert.notExists(node);
    });

    it('does not render custom value when allowCustom is not set', async () => {
      const element = await basicFixture();
      element.value = 'https://custom';
      await nextFrame();
      const node = element.shadowRoot!.querySelector('.uri-input');
      assert.notExists(node);
    });

    it('does not render custom value when selection is made', async () => {
      const element = await extraOptionsFixture();
      element.value = 'http://customServer.com';
      await nextFrame();
      const node = element.shadowRoot!.querySelector('.uri-input');
      assert.notExists(node);
    });

    it('renders custom uri input', async () => {
      const element = await customInputFixture();
      const node = element.shadowRoot!.querySelector('.uri-input');
      assert.exists(node);
    });

    it('custom uri input has no value', async () => {
      const element = await customInputFixture();
      const node = element.shadowRoot!.querySelector('.uri-input') as Input;
      assert.equal(node.value, '');
    });

    it('renders empty slot', async () => {
      const element = await basicFixture();
      const slot = element.shadowRoot!.querySelector('slot')!;
      assert.exists(slot);
      assert.lengthOf(slot.assignedElements(), 0)
    });

    it('does not render the `Custom URI` option by default', async () => {
      const element = await basicFixture();
      assert.notExists(element.shadowRoot!.querySelector('.custom-option'));
    });

    it('sets the baseUri and renders in custom mode', async () => {
      const element = await baseUriFixture();
      await nextFrame();
      element.custom = false;
      await nextFrame();
      assert.equal(element.value, 'https://www.google.com');
      assert.equal(element.type, 'custom');
      assert.equal(element.baseUri, 'https://www.google.com');
    });
  });

  describe('#custom', () => {
    it('renders the `Custom URI` option', async () => {
      const element = await allowCustomFixture();
      assert.exists(element.shadowRoot!.querySelector('.custom-option'));
    });

    it('dispatches the change event when the value changes', async () => {
      const element = await allowCustomFixture();
      const spy = sinon.spy();
      element.addEventListener(EventTypes.Server.serverChange, spy);
      const node = (element.shadowRoot!.querySelector('[data-value="custom"]')) as HTMLElement;
      node.click();
      await nextFrame();

      assert.isTrue(spy.called, 'the event is dispatched');
      const { detail } = spy.args[0][0];
      assert.deepEqual(detail, {
        value: '', // no value because no input
        type: 'custom',
      })
    });

    it('selects the custom uri', async () => {
      const element = await allowCustomFixture();
      const node = (element.shadowRoot!.querySelector('[data-value="custom"]')) as HTMLElement;
      node.click();
      await nextFrame();
      assert.equal(element.value, '');
      assert.equal(element.type, 'custom');
    });

    it('removed the input when close is clicked', async () => {
      const element = await customInputFixture();
      element.value = 'test';
      await nextFrame();
      const node = element.shadowRoot!.querySelector('ui-icon-button') as UiIconButton;
      node.click();
      await nextFrame();
      assert.equal(element.value, '', 'value is reset');
      assert.equal(element.type, 'server', 'type is reset');
      assert.notExists(element.shadowRoot!.querySelector('.uri-input'), 'input is removed');
    });

    it('resets when disabling the value', async () => {
      const element = await customInputFixture();
      element.custom = false;
      await nextFrame();
      assert.equal(element.type, 'server');
    });
  });

  describe('#baseUri', () => {
    let element: ApiServerPickerElement;

    beforeEach(async () => {
      element = await baseUriFixture();
    });

    it('has the baseUri as value', () => {
      assert.equal(element.value, 'https://www.google.com');
    })

    it('returns the baseUri as value after setting the value', async () => {
      element.value = 'test uri';
      await nextFrame();
      assert.equal(element.value, 'https://www.google.com');
    })

    it('updates the value when new baseUri is set', async () => {
      element.baseUri = 'https://www.google.com/v1';
      await nextFrame();
      assert.equal(element.value, 'https://www.google.com/v1');
      assert.equal(element.baseUri, 'https://www.google.com/v1');
      assert.equal(element.type, 'custom');
    })
  });

  describe('#opened', () => {
    let element: ApiServerPickerElement;
    beforeEach(async () => {
      element = await extraOptionsFixture();
    });

    it('opened is undefined by default', async () => {
      assert.isUndefined(element.opened);
    });

    it('sets #opened to true when drop down is opened', async () => {
      const node = element.shadowRoot!.querySelector('ui-dropdown-list')!;
      node.open = true;
      node.dispatchEvent(new Event('open'));
      await nextFrame();

      assert.isTrue(element.opened);
    });

    it('opens the dropdown when the property is set', async () => {
      element.opened = true;
      await nextFrame();
      const node = element.shadowRoot!.querySelector('ui-dropdown-list')!;
      assert.isTrue(node.open);
    });

    it('closes the dropdown when the property is set', async () => {
      element.opened = true;
      await nextFrame();
      element.opened = false;
      await nextFrame();
      const node = element.shadowRoot!.querySelector('ui-dropdown-list')!;
      assert.isFalse(node.open);
    });

    it('sets matchTriggerWidth', async () => {
      element.opened = true;
      await nextFrame();
      element.opened = false;
      await nextFrame();
      const node = element.shadowRoot!.querySelector('ui-dropdown-list')!;
      assert.isTrue(node.matchTriggerWidth);
    });
  });

  describe('slotted items', () => {
    it('has two assigned nodes to the slot', async () => {
      const element = await extraOptionsFixture();
      const slot = (element.shadowRoot!.querySelector('slot[name="custom-base-uri"]')) as HTMLSlotElement;
      const nodes = slot.assignedElements();
      assert.lengthOf(nodes, 2);
    });

    it('has a total of 2 servers', async () => {
      const element = await extraOptionsFixture();
      assert.equal(element.serversCount, 2)
    });

    it('renders 2 servers', async () => {
      const element = await slotChangeFixture();
      assert.equal(element.serversCount, 2);
    })

    it('dispatches serverscountchanged event when a slot is added', async () => {
      const element = await slotChangeFixture();
      const toBeSlotted = element.children[2];
      const spy = sinon.spy();
      element.addEventListener(EventTypes.Server.serverCountChange, spy);
      toBeSlotted.setAttribute('slot', 'custom-base-uri');
      await nextFrame();

      assert.isTrue(spy.called, 'event is dispatched');
      const { detail } = spy.args[0][0];
      assert.deepEqual(detail, {
        value: 3
      });
    })

    it('renders 3 servers after the update', async () => {
      const element = await slotChangeFixture();
      const toBeSlotted = element.children[2];
      toBeSlotted.setAttribute('slot', 'custom-base-uri');
      await nextFrame();
      assert.equal(element.serversCount, 3);
    })
  });

  async function changeSelection(element: ApiServerPickerElement, { selected, type }): Promise<void> {
    element.domainId = selected;
    element.domainType = type;
    await nextFrame();
  }

  describe('Listing servers', () => {
    let element: ApiServerPickerElement;
    
    before(async () => {
      store.amf = serversApiModel;
    });

    beforeEach(async () => {
      element = await basicFixture();
    });

    it('does not render servers when no servers available', async () => {
      element.removeServers();
      await nextFrame();
      const nodes = element.shadowRoot!.querySelectorAll('[data-item="server-dropdown-option"]');
      assert.lengthOf(nodes, 0);
    });

    it('renders the list of servers for the loaded API', () => {
      const nodes = element.shadowRoot!.querySelectorAll('[data-item="server-dropdown-option"]');
      assert.lengthOf(nodes, 4);
    });

    it('renders the list of servers for an endpoint', async () => {
      const endpoint = loader.lookupEndpoint(serversApiModel, '/ping');
      const endpointId = endpoint['@id'];
      const detail = {
        selected: endpointId,
        type: 'resource',
      };
      await changeSelection(element, detail);
      await nextFrame();
      const nodes = element.shadowRoot!.querySelectorAll('[data-item="server-dropdown-option"]');
      assert.lengthOf(nodes, 1);
    });

    it('returns the list of servers for an operation', async () => {
      const endpoint = loader.lookupEndpoint(serversApiModel, '/ping');
      const method = loader.lookupOperation(serversApiModel, '/ping', 'get')!;
      const endpointId = endpoint['@id'];
      const methodId = method['@id'];
      const detail = {
        selected: methodId,
        type: 'operation',
        endpointId,
      };
      await changeSelection(element, detail);
      const nodes = element.shadowRoot!.querySelectorAll('[data-item="server-dropdown-option"]');
      assert.lengthOf(nodes, 2);
    });
  });

  describe('Changing drop-down selection', () => {
    before(async () => {
      store.amf = serversApiModel;
    });

    it('selects a server', async () => {
      const element = await basicFixture();

      const node = (element.shadowRoot!.querySelector('[data-value="https://{customerId}.saas-app.com:{port}/v2"]')) as HTMLElement;
      node.click();
      await nextFrame();

      assert.equal(element.value, 'https://{customerId}.saas-app.com:{port}/v2');
      assert.equal(element.type, 'server');
    });

    it('selects custom URI', async () => {
      const element = await allowCustomFixture();

      const node = (element.shadowRoot!.querySelector('[data-value="custom"]')) as HTMLElement;
      node.click();

      assert.equal(element.value, '');
      assert.equal(element.type, 'custom');
    });
  });

  describe('The server count event', () => {
    before(async () => {
      store.amf = undefined;
    });

    describe('initialization', () => {
      let element: ApiServerPickerElement;
      let event: CustomEvent;

      beforeEach(async () => {
        element = document.createElement('api-server-picker');
        document.body.appendChild(element);
        event = await oneEvent(element, EventTypes.Server.serverCountChange);
      });

      afterEach(() => {
        document.body.removeChild(element);
      });

      it('renders 0 servers', () => {
        assert.equal(element.serversCount, 0);
      });

      it('has triggered the event', () => {
        assert.deepEqual(event.detail, {
          value: 0
        });
      });
    });

    describe('custom change', () => {
      let element: ApiServerPickerElement;
      let event: CustomEvent;

      beforeEach(async () => {
        element = await basicFixture();
        const handler = (e: Event): void => {
          event = e as CustomEvent;
        }
        element.addEventListener(EventTypes.Server.serverCountChange, handler);
        element.custom = true;
        await nextFrame();
      });

      it('triggers the event', () => {
        assert.deepEqual(event.detail, {
          value: 1
        });
      })
    });
  });

  describe('slot elements', () => {
    let element: ApiServerPickerElement;
    
    before(async () => {
      store.amf = serversApiModel;
    });

    beforeEach(async () => {
      element = await extraOptionsFixture();
    });

    it('selects a slot value', async () => {
      element.value = 'http://customServer2.com';
      await nextFrame();
      assert.equal(element.value, 'http://customServer2.com');
    });
  });

  describe('auto selection', () => {
    before(async () => {
      store.amf = serversApiModel;
    });

    it('selects the default server for the API model', async () => {
      const element = await autoSelectFixture();
      assert.equal(element.value, 'https://{customerId}.saas-app.com:{port}/v2');
    });

    it('makes a selection when changing selection to an endpoint', async () => {
      const element = await autoSelectFixture();
      const endpoint = loader.lookupEndpoint(serversApiModel, '/ping');
      const endpointId = endpoint['@id'];
      const detail = {
        selected: endpointId,
        type: 'resource',
      };
      await changeSelection(element, detail);
      await nextFrame();
      assert.equal(element.value, 'https://endpoint.example.com');
    });

    it('makes a selection when changing selection to a method', async () => {
      const element = await autoSelectFixture();
      const endpoint = loader.lookupEndpoint(serversApiModel, '/ping')!;
      const method = loader.lookupOperation(serversApiModel, '/ping', 'get')!;
      const endpointId = endpoint['@id'];
      const methodId = method['@id'];
      const detail = {
        selected: methodId,
        type: 'operation',
        endpointId,
      };
      await changeSelection(element, detail);
      await nextFrame();
      assert.equal(element.value, 'https://echo.example.com');
    });

    it('keeps the selection when possible', async () => {
      const element = await autoSelectFixture();
      const endpoint1 = loader.lookupEndpoint(serversApiModel, '/default');
      const method1 = loader.lookupOperation(serversApiModel, '/default', 'get')!;
      const endpoint2 = loader.lookupEndpoint(serversApiModel, '/duplicated');
      const method2 = loader.lookupOperation(serversApiModel, '/duplicated', 'get')!;

      const detail = {
        selected: method1['@id'],
        type: 'operation',
        endpointId: endpoint1['@id'],
      };
      await changeSelection(element, detail);
      await nextFrame();
      element.value = 'http://beta.api.openweathermap.org/data/2.5/';
      await nextFrame();
      const detail2 = {
        selected: method2['@id'],
        type: 'operation',
        endpointId: endpoint2['@id'],
      };
      await changeSelection(element, detail2);
      await nextFrame();
      assert.equal(element.value, 'http://beta.api.openweathermap.org/data/2.5/');
    });
  });

  describe('no servers in the model', () => {
    before(async () => {
      store.amf = noServersApiModel;
    });

    it('selects first slot element if there are no model servers', async () => {
      const element = await autoSelectFixtureWithSlots();
      assert.equal(element.value, 'http://customServer.com');
      assert.equal(element.type, 'extra');
    });

    it('dispatches slot selection event if there are no model servers', async () => {
      let detail;
      // @ts-ignore
      window.addEventListener('apiserverchanged', (e) => { detail = e.detail; });
      await autoSelectFixtureWithSlots();
      assert.isDefined(detail, 'has the event');
      // @ts-ignore
      assert.equal(detail.value, 'http://customServer.com');
      // @ts-ignore
      assert.equal(detail.type, 'extra');
    });
  });

  describe('items without a value', () => {
    let element: ApiServerPickerElement;
    
    before(async () => {
      store.amf = serversApiModel;
    });

    beforeEach(async () => {
      element = await unselectableFixture();
    });

    it('does not select an element that has no value', () => {
      // first item has no value
      const node = element.querySelector('ui-list-item')!;
      node.click();

      assert.isUndefined(element.value);
    });
  });

  describe('isValueCustom()', () => {
    before(async () => {
      store.amf = serversApiModel;
    });

    it('returns false when no value', async () => {
      const element = await basicFixture();
      const result = element.isValueCustom();
      assert.isFalse(result);
    });

    it('returns false when value is on the servers list', async () => {
      const element = await basicFixture();
      const value = 'https://{customerId}.saas-app.com:{port}/v2';
      const result = element.isValueCustom(value);
      assert.isFalse(result);
    });

    it('returns false when value is on the custom servers list', async () => {
      const element = await extraOptionsFixture();
      const value = 'http://customServer.com';
      const result = element.isValueCustom(value);
      assert.isFalse(result);
    });

    it('returns true when on either of lists', async () => {
      const element = await extraOptionsFixture();
      const value = 'http://mostCustomServer.com';
      const result = element.isValueCustom(value);
      assert.isTrue(result);
    });
  });

  describe('#customServers', () => {
    const customValues = [
      'http://customServer.com',
      'http://customServer2.com'
    ];
    
    before(async () => {
      store.amf = serversApiModel;
    });

    it('is empty array by default', async () => {
      const element = await basicFixture();
      const result = element.customServers;
      assert.deepEqual(result, []);
    });

    it('adds custom items to the list', async () => {
      const element = await slotChangeFixture();
      const result = element.customServers;
      assert.deepEqual(result, customValues);
    });

    it('updates a list when a child is removed', async () => {
      const element = await slotChangeFixture();
      const item = element.querySelector('ui-list-item')!;
      element.removeChild(item);
      await nextFrame();
      const result = element.customServers;
      assert.deepEqual(result, [customValues[1]]);
    });

    it('sets empty array when no items', async () => {
      const element = await slotChangeFixture();
      const items = element.querySelectorAll('ui-list-item');
      Array.from(items).forEach((item) => element.removeChild(item));
      await nextFrame();
      const result = element.customServers;
      assert.deepEqual(result, []);
    });
  });

  describe('#value setter', () => {
    const serverValues = [
      'https://{customerId}.saas-app.com:{port}/v2',
      'https://{region}.api.cognitive.microsoft.com',
      'https://api.openweathermap.org/data/2.5/',
      'http://beta.api.openweathermap.org/data/2.5/'
    ];
    const customValues = [
      'http://customServer.com',
      'http://customServer2.com'
    ];
    
    before(async () => {
      store.amf = serversApiModel;
    });

    it('selects value from rendered servers', async () => {
      const element = await basicFixture();
      element.value = serverValues[1];
      await nextFrame();
      const node = element.shadowRoot!.querySelector('.trigger') as Input;
      assert.equal(node.value, serverValues[1]);
    });

    it('selects value from rendered custom servers', async () => {
      const element = await extraOptionsFixture();
      element.value = customValues[1];
      await nextFrame();
      const node = element.shadowRoot!.querySelector('.trigger') as Input;
      assert.equal(node.value, customValues[1]);
    });

    it('opens the custom editor when value is custom', async () => {
      const element = await extraOptionsCustomFixture();
      element.value = 'super-custom';
      element.type = 'custom';
      await nextFrame();
      assert.isTrue(element.custom);
    });
  });

  describe('domainId & domainType', () => {
    let element: ApiServerPickerElement;
    let method: AmfDefinitions.IAmfDocument;
    let methodId: string;
    
    before(async () => {
      store.amf = serversApiModel;
    });

    beforeEach(async () => {
      element = await basicFixture();
      method = loader.lookupOperation(serversApiModel, '/ping', 'get')!;
      methodId = method['@id'];
    });

    it('should load method servers on init', async () => {
      element = await domainIdFixture(methodId, 'operation');
      assert.equal(element.apiServers.length, 2)
    });

    it('should load root servers on init with invalid domainId', async () => {
      element = await domainIdFixture('', 'operation');
      assert.equal(element.apiServers.length, 4)
    });

    it('should load root servers on init with invalid domainType', async () => {
      // @ts-ignore
      element = await domainIdFixture(methodId, '');
      assert.equal(element.apiServers.length, 4)
    });

    it('should change to new endpoint\'s servers after navigation', async () => {
      element = await domainIdFixture(methodId, 'operation');
      const endpoint = loader.lookupEndpoint(serversApiModel, '/ping');
      const id = endpoint['@id'];
      const detail = {
        selected: id,
        type: 'resource'
      };
      await changeSelection(element, detail);
      await nextFrame();
      assert.lengthOf(element.apiServers, 1);
    });

    it('should change to new method\'s servers after navigation', async () => {
      element = await domainIdFixture(methodId, 'operation');
      const nextMethod = loader.lookupOperation(serversApiModel, '/files', 'get')!;
      const id = nextMethod['@id'];
      const detail = {
        selected: id,
        type: 'operation'
      };
      await changeSelection(element, detail);
      await nextFrame();
      assert.lengthOf(element.apiServers, 1);
    });

    it('starts with a selected server value that is not the first one available', async () => {
      element = await domainIdFixtureWithValue(methodId, 'operation', 'https://echo2.example.com');
      assert.equal(element.value, 'https://echo2.example.com');
    });
  });

  describe('onapiserverchange', () => {
    let element: ApiServerPickerElement;
    beforeEach(async () => {
      element = await allowCustomFixture();
    });

    it('has no initial value', () => {
      assert.equal(element.onapiserverchange, null);
    });

    it('sets an event listener', () => {
      const fn = (): void => {
        // ..
      };
      element.onapiserverchange = fn;
      assert.isTrue(element.onapiserverchange === fn);
    });

    it('calls the callback function', async () => {
      let called = false;
      const fn = (): void => { called = true; };
      element.onapiserverchange = fn;
      const node = (element.shadowRoot!.querySelector('[data-value="custom"]')) as HTMLElement;
      node.click();
      await nextFrame();
      assert.isTrue(called);
    });

    it('clears the callback function', async () => {
      let called = false;
      const fn = (): void => { called = true; };
      element.onapiserverchange = fn;
      element.onapiserverchange = null;
      const node = (element.shadowRoot!.querySelector('[data-value="custom"]')) as HTMLElement;
      node.click();
      await nextFrame();
      assert.isFalse(called);
    });

    it('unregisters the old function', async () => {
      let called1 = false;
      let called2 = false;
      const fn1 = (): void => { called1 = true; };
      const fn2 = (): void => { called2 = true; };
      element.onapiserverchange = fn1;
      element.onapiserverchange = fn2;
      const node = (element.shadowRoot!.querySelector('[data-value="custom"]')) as HTMLElement;
      node.click();
      await nextFrame();
      assert.isFalse(called1);
      assert.isTrue(called2);
    });
  });

  describe('onserverscountchange', () => {
    let element: ApiServerPickerElement;
    beforeEach(async () => {
      element = await basicFixture();
    });

    it('has no initial value', () => {
      assert.equal(element.onserverscountchange, null);
    });

    it('sets an event listener', () => {
      const fn = (): void => {
        // ...
      };
      element.onserverscountchange = fn;
      assert.isTrue(element.onserverscountchange === fn);
    });

    it('calls the callback function', async () => {
      let called = false;
      const fn = (): void => { called = true; };
      element.onserverscountchange = fn;
      element.custom = true;
      await nextFrame();
      assert.isTrue(called);
    });

    it('clears the callback function', async () => {
      let called = false;
      const fn = (): void => { called = true; };
      element.onserverscountchange = fn;
      element.onserverscountchange = null;
      element.custom = true;
      await nextFrame();
      assert.isFalse(called);
    });

    it('unregisters the old function', async () => {
      let called1 = false;
      let called2 = false;
      const fn1 = (): void => { called1 = true; };
      const fn2 = (): void => { called2 = true; };
      element.onserverscountchange = fn1;
      element.onserverscountchange = fn2;
      element.custom = true;
      await nextFrame();
      assert.isFalse(called1);
      assert.isTrue(called2);
    });
  });
});
