/**
 * @jest-environment jsdom
 */

/*
 * Silex website builder, free/libre no-code tool for makers.
 * Copyright (c) 2023 lexoyo and Silex Labs foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

import grapesjs, { Editor, Component } from 'grapesjs'
import { DataTree } from './DataTree'
import { Type, Filter, Property, Expression, Tree } from '../types'
import { DataSourceEditor } from '..'
import { getStates } from './state'
import { simpleFilters, simpleQueryables, simpleTypes, testDataSourceId, testTokens } from '../test-data'

// Mock only getState
jest.mock('./state', () => ({
  ...jest.requireActual('./state'),
  getState: jest.fn(),
  getStates: jest.fn(),
  getOrCreatePersistantId: jest.fn(),
  getParentByPersistentId: jest.fn(),
}))

// FIXME: Workaround to avoid import of lit-html which breakes unit tests
jest.mock('lit', () => ({
  html: jest.fn(),
  render: jest.fn(),
}))

let editor: Editor
let firstComponent: Component
beforeEach(async () => {
  jest.resetAllMocks()
  ;(getStates as jest.Mock).mockReturnValue([]) // Default for getStates
  editor = grapesjs.init({
    container: document.createElement('div'),
    components: '<div></div>',
  })
  firstComponent = editor.getComponents().first()
})

test('DataTree instanciation', () => {
  expect(DataTree).toBeDefined()
  const dataTree = new DataTree(editor as DataSourceEditor, {filters: [], dataSources: []})
  expect(dataTree).toBeDefined()
})

test('Find type from  id', () => {
  const dataTree = new DataTree(editor as DataSourceEditor, {filters: [], dataSources: [{
    id: testDataSourceId,
    connect: async () => {},
    isConnected: () => true,
    getTypes: () => simpleTypes,
    getQueryables: () => simpleTypes[0].fields,
    getQuery: () => '',
  }]})

  // Type not found
  expect(() => dataTree.getType('unknown type', null, null)).toThrow()

  // Type found
  const type = dataTree.getType('testTypeId', null, null)
  expect(type).not.toBeNull()
  expect(type?.id).toBe('testTypeId')

  // With data source id
  expect(dataTree.getType('testTypeId', testDataSourceId, null)).not.toBeNull()
  expect(() => dataTree.getType('testTypeId', 'unknown type', null)).toThrow()
})

test('Expressions to tree', () => {
  const dataTree = new DataTree(editor as DataSourceEditor, {filters: [], dataSources: []})
  const expression: Expression = [{
    type: 'property',
    propType: 'field',
    fieldId: 'testFieldId',
    label: 'test field name',
    typeIds: ['testTypeId'],
    kind: 'object',
    dataSourceId: testDataSourceId,
  }]
  const component = editor.getComponents().first()
  expect(dataTree.getTrees({expression, component}, testDataSourceId))
    .toEqual([{
      token: {
        type: 'property',
        propType: 'field',
        fieldId: 'testFieldId',
        label: 'test field name',
        typeIds: ['testTypeId'],
        kind: 'object',
        dataSourceId: testDataSourceId,
      },
      children: [],
    }])
})

test('merge trees', () => {
  class DataTreeTest extends DataTree {
    public mergeTrees(tree1: Tree, tree2: Tree): Tree {
      return super.mergeTrees(tree1, tree2)
    }
  }
  const dataTree = new DataTreeTest(editor as DataSourceEditor, {filters: [], dataSources: []})
  const tree1: Tree = {
    token: {
      type: 'property',
      propType: 'field',
      fieldId: 'testFieldId',
      label: 'test field name',
      typeIds: ['testTypeId'],
      kind: 'object',
      dataSourceId: testDataSourceId,
    },
    children: [{
      token: {
        type: 'property',
        propType: 'field',
        fieldId: 'testFieldPropertyId',
        label: 'test field property name',
        typeIds: ['testFieldPropertyTypeId'],
        kind: 'scalar',
        dataSourceId: testDataSourceId,
      },
      children: [],
    }],
  }
  const tree2: Tree = {
    token: {
      type: 'property',
      propType: 'field',
      fieldId: 'testFieldId',
      label: 'test field name',
      typeIds: ['testTypeId'],
      kind: 'object',
      dataSourceId: testDataSourceId,
    },
    children: [{
      token: {
        type: 'property',
        propType: 'field',
        fieldId: 'testFieldPropertyId',
        label: 'test field property name',
        typeIds: ['testFieldPropertyTypeId'],
        kind: 'scalar',
        dataSourceId: testDataSourceId,
      },
      children: [],
    }, {
      token: {
        type: 'property',
        propType: 'field',
        fieldId: 'testFieldPropertyId2',
        label: 'test field property name',
        typeIds: ['testFieldPropertyTypeId'],
        kind: 'scalar',
        dataSourceId: testDataSourceId,
      },
      children: [],
    }],
  }
  expect(dataTree.mergeTrees(tree1, tree2))
    .toEqual({
      token: {
        type: 'property',
        propType: 'field',
        fieldId: 'testFieldId',
        label: 'test field name',
        typeIds: ['testTypeId'],
        kind: 'object',
        dataSourceId: testDataSourceId,
      },
      children: [{
        token: {
          type: 'property',
          propType: 'field',
          fieldId: 'testFieldPropertyId2',
          label: 'test field property name',
          typeIds: ['testFieldPropertyTypeId'],
          kind: 'scalar',
          dataSourceId: testDataSourceId,
        },
        children: [],
      }, {
        token: {
          type: 'property',
          propType: 'field',
          fieldId: 'testFieldPropertyId',
          label: 'test field property name',
          typeIds: ['testFieldPropertyTypeId'],
          kind: 'scalar',
          dataSourceId: testDataSourceId,
        },
        children: [],
      }],
    })
})

test('merge trees with multiple possible types', () => {
  class DataTreeTest extends DataTree {
    public mergeTrees(tree1: Tree, tree2: Tree): Tree {
      return super.mergeTrees(tree1, tree2)
    }
  }
  const dataTree = new DataTreeTest(editor as DataSourceEditor, {filters: [], dataSources: []})
  const tree1: Tree = {
    token: {
      type: 'property',
      propType: 'field',
      fieldId: 'parentFieldId',
      label: 'test parent name',
      typeIds: ['testParentId'],
      kind: 'object',
      dataSourceId: testDataSourceId,
    },
    children: [{
      token: {
        type: 'property',
        propType: 'field',
        fieldId: 'testFieldId',
        label: 'test field name',
        typeIds: ['testTypeId1'],
        kind: 'object',
        dataSourceId: testDataSourceId,
      },
      children: [{
        token: {
          type: 'property',
          propType: 'field',
          fieldId: 'ONLY_TEST_TYPE_ID1',
          label: 'test field property name',
          typeIds: ['testFieldPropertyTypeId1'],
          kind: 'scalar',
          dataSourceId: testDataSourceId,
        },
        children: [],
      }],
    }],
  }
  const tree2: Tree = {
    token: {
      type: 'property',
      propType: 'field',
      fieldId: 'parentFieldId',
      label: 'test parent name',
      typeIds: ['testParentId'],
      kind: 'object',
      dataSourceId: testDataSourceId,
    },
    children: [{
      token: {
        type: 'property',
        propType: 'field',
        fieldId: 'testFieldId',
        label: 'test field name',
        typeIds: ['testTypeId2'],
        kind: 'object',
        dataSourceId: testDataSourceId,
      },
      children: [{
        token: {
          type: 'property',
          propType: 'field',
          fieldId: 'ONLY_TEST_TYPE_ID2',
          label: 'test field property name',
          typeIds: ['testFieldPropertyTypeId2'],
          kind: 'scalar',
          dataSourceId: testDataSourceId,
        },
        children: [],
      }],
    }],
  }
  expect(dataTree.mergeTrees(tree1, tree2))
    .toEqual({
      token: {
        type: 'property',
        propType: 'field',
        fieldId: 'parentFieldId',
        label: 'test parent name',
        typeIds: ['testParentId'],
        kind: 'object',
        dataSourceId: testDataSourceId,
      },
      children: [{
        token: {
          type: 'property',
          propType: 'field',
          fieldId: 'testFieldId',
          label: 'test field name',
          typeIds: ['testTypeId1'],
          kind: 'object',
          dataSourceId: testDataSourceId,
        },
        children: [{
          token: {
            type: 'property',
            propType: 'field',
            fieldId: 'ONLY_TEST_TYPE_ID1',
            label: 'test field property name',
            typeIds: ['testFieldPropertyTypeId1'],
            kind: 'scalar',
            dataSourceId: testDataSourceId,
          },
          children: [],
        }],
      }, {
        token: {
          type: 'property',
          propType: 'field',
          fieldId: 'testFieldId',
          label: 'test field name',
          typeIds: ['testTypeId2'],
          kind: 'object',
          dataSourceId: testDataSourceId,
        },
        children: [{
          token: {
            type: 'property',
            propType: 'field',
            fieldId: 'ONLY_TEST_TYPE_ID2',
            label: 'test field property name',
            typeIds: ['testFieldPropertyTypeId2'],
            kind: 'scalar',
            dataSourceId: testDataSourceId,
          },
          children: [],
        }],
      }]
    })
})

test('get tree with filters', async () => {
  const queryObjects: Expression = [{
    type: 'property',
    propType: 'field',
    fieldId: 'testId1',
    label: 'test field name',
    typeIds: ['PostEntityResponseCollection'],
    kind: 'list',
    dataSourceId: testDataSourceId,
  }, {
    type: 'filter',
    id: 'testFilterId1',
    label: 'test filter name',
    options: {},
  }, {
    type: 'property',
    propType: 'field',
    fieldId: 'data',
    label: 'test field name',
    typeIds: ['PostEntity'],
    kind: 'list',
    dataSourceId: testDataSourceId,
  }, {
    type: 'filter',
    id: 'testFilterId1',
    label: 'test filter name',
    options: {},
  }, {
    type: 'property',
    propType: 'field',
    fieldId: 'id',
    label: 'test field name',
    typeIds: ['ID'],
    kind: 'scalar',
    dataSourceId: testDataSourceId,
  }]
  const dataTree = new DataTree(editor as DataSourceEditor, {filters: [], dataSources: [{
    id: testDataSourceId,
    connect: async () => {},
    isConnected: () => true,
    getTypes: () => simpleTypes,
    getQueryables: () => simpleTypes[0].fields,
    getQuery: () => '',
  }]})
  // Make sure it treats them all as relative
  dataTree.isRelative = () => true
  const component = editor.getComponents().first()
  const result = dataTree.getTrees({expression: [...queryObjects], component}, testDataSourceId)
  expect(result)
    .toEqual([{
      token: queryObjects[0],
      children: [{
        token: queryObjects[2],
        children: [{
          token: queryObjects[4],
          children: [],
        }],
      }],
    }])
})

test('Merge trees with empty and no options', async () => {
  const trees = [{
    'token': {
      'type': 'property',
      'propType': 'field',
      'typeIds': [],
      'label': 'test field name',
      'dataSourceId': 'testDataSourceId',
      'fieldId': 'query',
      'kind': 'object'
    },
    'children': [
      {
        'token': {
          'type': 'property',
          'propType': 'field',
          'fieldId': 'testFieldId1',
          'label': 'test field name',
          'typeIds': [
            'testTypeId'
          ],
          'kind': 'object',
          'dataSourceId': 'testDataSourceId'
        },
        'children': []
      }
    ],
  }, {
    'token': {
      'type': 'property',
      'propType': 'field',
      'typeIds': [],
      'dataSourceId': 'testDataSourceId',
      'fieldId': 'query',
      'kind': 'object'
    },
    'children': [
      {
        'token': {
          'type': 'property',
          'propType': 'field',
          'fieldId': 'testFieldId1',
          'label': 'test field name',
          'typeIds': [
            'testTypeId'
          ],
          'kind': 'object',
          'dataSourceId': 'testDataSourceId',
          'options': {
            'test': undefined,
            'test2': '',
          },
        },
        'children': []
      }
    ],
  }] as Tree[]
  class DataTreeTest extends DataTree {
    public mergeTrees(tree1: Tree, tree2: Tree): Tree {
      return super.mergeTrees(tree1, tree2)
    }
  }
  const dataTree = new DataTreeTest(editor as DataSourceEditor, {filters: [], dataSources: [{
    id: testDataSourceId,
    connect: async () => {},
    isConnected: () => true,
    getTypes: () => simpleTypes,
    getQueryables: () => simpleTypes[0].fields,
    getQuery: () => '',
  }]})

  expect(async () => dataTree.mergeTrees(trees[0], trees[1]))
    .not.toThrow()
  
  // The 2 trees are the same
  // The 2nd tree has empty options which should be ignored
  expect(dataTree.mergeTrees(trees[0], trees[1]))
    .toEqual(trees[0])
})

test('Get query with errors in options', async () => {
  class DataTreeTest extends DataTree {
    public mergeTrees(tree1: Tree, tree2: Tree): Tree {
      return super.mergeTrees(tree1, tree2)
    }
  }
  const dataTree = new DataTreeTest(editor as DataSourceEditor, {filters: [], dataSources: []})
  expect(() => dataTree.mergeTrees({
    'token': {
      'dataSourceId': 'testDataSourceId',
      'fieldId': 'query',
      'kind': 'object',
      'typeIds': [],
      'type': 'property',
      'propType': 'field',
      'label': 'test field name',
    },
    'children': [
      {
        'token': {
          'type': 'property',
          'propType': 'field',
          'fieldId': 'testFieldId1',
          'label': 'test field name',
          'typeIds': [
            'testTypeId'
          ],
          'kind': 'object',
          'dataSourceId': 'testDataSourceId'
        },
        'children': []
      }
    ]
  }, {
    'token': {
      'dataSourceId': 'testDataSourceId',
      'fieldId': 'query',
      'kind': 'object',
      'typeIds': [],
      'type': 'property',
      'propType': 'field',
      'label': 'test field name',
    },
    'children': [
      {
        'token': {
          'type': 'property',
          'propType': 'field',
          'fieldId': 'testFieldId1',
          'label': 'test field name',
          'typeIds': [
            'testTypeId'
          ],
          'kind': 'object',
          'dataSourceId': 'testDataSourceId',
          'options': {
            'id': 'option'
          }
        },
        'children': []
      }
    ]
  }))
    .toThrow()
})

test('Get query from multiple expressions', async () => {
  class DataTreeTest extends DataTree {
    public mergeTrees(tree1: Tree, tree2: Tree): Tree {
      return super.mergeTrees(tree1, tree2)
    }
  }
  const dataTree = new DataTreeTest(editor as DataSourceEditor, {filters: [], dataSources: []})
  const tree = dataTree.mergeTrees({
    'token': {
      'dataSourceId': 'testDataSourceId',
      'fieldId': 'query',
      'kind': 'object',
      'typeIds': [],
      'type': 'property',
      'propType': 'field',
      'label': 'test field name',
    },
    'children': [
      {
        'token': {
          'type': 'property',
          'propType': 'field',
          'fieldId': 'testFieldId1',
          'label': 'test field name',
          'typeIds': [
            'testTypeId'
          ],
          'kind': 'object',
          'dataSourceId': 'testDataSourceId'
        },
        'children': []
      }
    ]
  }, {
    'token': {
      'dataSourceId': 'testDataSourceId',
      'fieldId': 'query',
      'kind': 'object',
      'typeIds': [],
      'type': 'property',
      'propType': 'field',
      'label': 'test field name',
    },
    'children': [
      {
        'token': {
          'type': 'property',
          'propType': 'field',
          'fieldId': 'testFieldId2',
          'label': 'test field name',
          'typeIds': [
            'testTypeId'
          ],
          'kind': 'object',
          'dataSourceId': 'testDataSourceId'
        },
        'children': []
      }
    ]
  })
  expect(tree).not.toBeUndefined()
  expect(tree.token).not.toBeUndefined()
  expect(tree.token.fieldId).toBe('query')
  expect(tree.children).toHaveLength(2)
  expect(tree.children[0].token.fieldId).toBe('testFieldId1')
})

//test('Get query from expression with filters', async () => {
//  const queryObjects = [{
//    type: 'property',
//    propType: 'field',
//    fieldId: 'testId1',
//    label: 'test field name',
//    typeIds: ['PostEntityResponseCollection'],
//    kind: 'list',
//    dataSourceId: 'testDataSourceId',
//  }, {
//    type: 'filter',
//    id: 'testFilterId1',
//    label: 'test filter name',
//    options: {},
//  }, {
//    type: 'property',
//    propType: 'field',
//    fieldId: 'data',
//    label: 'test field name',
//    typeIds: ['PostEntity'],
//    kind: 'list',
//    dataSourceId: 'testDataSourceId',
//  }, {
//    type: 'filter',
//    id: 'testFilterId1',
//    label: 'test filter name',
//    options: {},
//  }, {
//    type: 'property',
//    propType: 'field',
//    fieldId: 'id',
//    label: 'test field name',
//    typeIds: ['ID'],
//    kind: 'scalar',
//    dataSourceId: 'testDataSourceId',
//  }]
//  await dataSource.getQuery([[...queryObjects]])
//  expect(getQuery).toHaveBeenCalledTimes(1)
//  expect(getQuery).toHaveBeenCalledWith({
//    token: {
//      dataSourceId: 'testDataSourceId',
//      fieldId: 'query',
//      kind: 'object',
//      typeIds: ['Query'],
//    },
//    children: [
//      { token: queryObjects[0], children: [
//        { token: queryObjects[2], children: [
//          { token: queryObjects[4], children: [] }
//        ]},
//      ]},
//    ]
//  })
//})

//test('Get query with options', async () => {
//  const DataSource = (await importDataSource([simpleSchema]))
//  const dataSource = new DataSource(options)
//  dataSource.getTypes = () => ([{
//    id: 'Query',
//    fields: [{
//      id: 'testFieldId1',
//      dataSourceId: 'testDataSourceId',
//      typeIds: ['rootTypeId1'],
//    }, {
//      id: 'testFieldId2',
//      dataSourceId: 'testDataSourceId',
//      typeIds: ['rootTypeId2'],
//    }, {
//      id: 'testFieldId3',
//      dataSourceId: 'testDataSourceId',
//      typeIds: ['rootTypeId1'],
//    }],
//  }, {
//    id: 'testTypeId',
//    dataSourceId: 'testDataSourceId',
//    fields: [],
//  }] as Type[])
//  await dataSource.connect()
//  const query = await dataSource.getQuery([[{
//    type: 'property',
//    propType: 'field',
//    fieldId: 'testFieldId1',
//    label: 'test field name',
//    typeIds: ['testTypeId'],
//    kind: 'object',
//    dataSourceId: 'testDataSourceId',
//    options: {id: 1},
//  }], [{
//    type: 'property',
//    propType: 'field',
//    fieldId: 'testFieldId1',
//    label: 'test field name',
//    typeIds: ['testTypeId'],
//    kind: 'object',
//    dataSourceId: 'testDataSourceId',
//    options: {id: 1},
//  }], [{
//    type: 'property',
//    propType: 'field',
//    fieldId: 'testFieldId2',
//    label: 'test field name',
//    typeIds: ['testTypeId'],
//    kind: 'object',
//    dataSourceId: 'testDataSourceId',
//    options: {name: 'test'},
//  }], [{
//    type: 'property',
//    propType: 'field',
//    fieldId: 'testFieldId3',
//    label: 'test field name',
//    typeIds: ['testTypeId'],
//    kind: 'object',
//    dataSourceId: 'testDataSourceId',
//  }]])
//  expect(query).not.toBeUndefined()
//  expect(query).toEqual(`query {
//  __typename
//  testFieldId1(id: 1) {
//    __typename
//
//
//  }
//  testFieldId2(name: "test") {
//    __typename
//
//
//  }
//  testFieldId3 {
//    __typename
//
//
//  }
//
//}`)
//})
//
//test('Get query with property options', async () => {
//  const DataSource = (await importDataSource([simpleSchema]))
//  const dataSource = new DataSource(options)
//  dataSource.getQuery = jest.fn(() => 'testQuery')
//  await dataSource.connect()
//  await dataSource.getQuery([[{
//    // LEVEL 1
//    type: 'property',
//    propType: 'field',
//    fieldId: 'testFieldId1',
//    label: 'test field name',
//    typeIds: ['PostEntity'],
//    kind: 'object',
//    dataSourceId: 'testDataSourceId',
//    options: {id: 'option'},
//  }, {
//    // LEVEL 2
//    type: 'property',
//    propType: 'field',
//    fieldId: 'test',
//    label: 'test field property name',
//    typeIds: ['String'],
//    kind: 'scalar',
//    dataSourceId: 'testDataSourceId',
//    options: {prop: 'option1'},
//  }], [{
//    // LEVEL 1
//    type: 'property',
//    propType: 'field',
//    fieldId: 'testFieldId1',
//    label: 'test field name',
//    typeIds: ['PostEntity'],
//    kind: 'object',
//    dataSourceId: 'testDataSourceId',
//    options: {id: 'option'},
//  }, {
//    // LEVEL 2
//    type: 'property',
//    propType: 'field',
//    fieldId: 'id',
//    label: 'test field property name',
//    typeIds: ['ID'],
//    kind: 'scalar',
//    dataSourceId: 'testDataSourceId',
//    options: {},
//  }], [{
//    // LEVEL 1
//    type: 'property',
//    propType: 'field',
//    fieldId: 'testFieldId1',
//    label: 'test field name',
//    typeIds: ['PostEntity'],
//    kind: 'object',
//    dataSourceId: 'testDataSourceId',
//    options: {id: 'option'},
//  }, {
//    // LEVEL 2
//    type: 'property',
//    propType: 'field',
//    fieldId: 'attributes',
//    label: 'test field property name',
//    typeIds: ['PostEntity'],
//    kind: 'Object',
//    dataSourceId: 'testDataSourceId',
//    options: {prop: 'option3'},
//  }]])
//  expect(dataSource.getQuery).toHaveBeenCalledTimes(1)
//  expect(dataSource.getQuery).toHaveBeenCalledWith({
//    token: {
//      dataSourceId: 'testDataSourceId',
//      fieldId: 'query',
//      kind: 'object',
//      typeIds: ['Query'],
//    },
//    children: [
//      { token: {
//        type: 'property',
//        propType: 'field',
//        fieldId: 'testFieldId1',
//        label: 'test field name',
//        typeIds: ['PostEntity'],
//        kind: 'object',
//        dataSourceId: 'testDataSourceId',
//        options: {id: 'option'},
//      }, children: [
//        { token: {
//          type: 'property',
//          propType: 'field',
//          fieldId: 'test',
//          label: 'test field property name',
//          typeIds: ['String'],
//          kind: 'scalar',
//          dataSourceId: 'testDataSourceId',
//          options: {prop: 'option1'},
//        }, children: [] },
//        { token: {
//          type: 'property',
//          propType: 'field',
//          fieldId: 'id',
//          label: 'test field property name',
//          typeIds: ['ID'],
//          kind: 'scalar',
//          dataSourceId: 'testDataSourceId',
//          options: {},
//        }, children: [] },
//        { token: {
//          type: 'property',
//          propType: 'field',
//          fieldId: 'attributes',
//          label: 'test field property name',
//          typeIds: ['PostEntity'],
//          kind: 'Object',
//          dataSourceId: 'testDataSourceId',
//          options: {prop: 'option3'},
//        }, children: [] },
//      ]},
//    ]
//  })
//})
//
//test('Get query with filter options', () => {
//  const fn = jest.fn(() => ([{
//    id: 'Query',
//    fields: [{
//      id: 'rootField1',
//      label: 'test',
//      dataSourceId: 'testDataSourceId',
//      typeIds: ['rootTypeId1'],
//    }, {
//      id: 'rootField2',
//      label: 'test',
//      dataSourceId: 'testDataSourceId',
//      typeIds: ['rootTypeId2'],
//    }],
//  }, {
//    id: 'rootTypeId1',
//    label: 'test',
//    dataSourceId: 'testDataSourceId',
//    fields: [{
//      id: 'childField1',
//      label: 'test',
//      typeIds: ['childTypeId1'],
//      kind: 'scalar',
//      dataSourceId: 'testDataSourceId',
//    }, {
//      id: 'childField3',
//      label: 'test',
//      typeIds: ['childTypeId3'],
//      kind: 'scalar',
//      dataSourceId: 'testDataSourceId',
//    }],
//  }, {
//    id: 'rootTypeId2',
//    label: 'test',
//    dataSourceId: 'testDataSourceId',
//    fields: [{
//      id: 'childField2',
//      label: 'test',
//      typeIds: ['childTypeId2'],
//      kind: 'scalar',
//      dataSourceId: 'testDataSourceId',
//    }],
//  }] as Type[]))
//  class GQLTestTrees extends GraphQL {
//    protected queryType: string = 'Query'
//    getTypes(): Type[] {
//      return fn()
//    }
//  }
//  const gql = new GQLTestTrees({
//    url: 'http://localhost',
//    method: 'POST',
//    headers: {},
//    id: 'testDataSourceId',
//    label: 'test',
//    type: 'graphql',
//  })
//  expect(gql.getQuery({
//    token: testTokens.rootField1 as Property,
//    children: [{
//      token: testTokens.childField3 as Property,
//      children: [],
//    }],
//  }))
//    .toEqual(dedent`
//      query {
//        __typename
//        rootField2 {
//          __typename
//          childField2
//
//        }
//        rootField1 {
//          __typename
//          childField3
//          childField1
//
//        }
//
//      }
//    `)
//})

test('isRelative', () => {
  const dataTree = new DataTree(editor as DataSourceEditor, {filters: [], dataSources: [{
    id: testDataSourceId,
    connect: async () => {},
    isConnected: () => true,
    getTypes: () => ([{
      id: 'parentType',
      label: 'test',
      dataSourceId: testDataSourceId,
      fields: [{
        id: 'testId1',
        label: 'test',
        typeIds: ['childType'],
        kind: 'list',
        dataSourceId: testDataSourceId,
      }],
    }, {
      id: 'childType',
      label: 'test',
      dataSourceId: testDataSourceId,
      fields: [{
        id: 'testId2',
        label: 'test',
        typeIds: ['childType'],
        kind: 'scalar',
        dataSourceId: testDataSourceId,
      }],
    }] as Type[]),
    getQueryables: () => ([]),
    getQuery: () => '',
  }]})
  expect(dataTree.isRelative({
    type: 'property',
    propType: 'field',
    fieldId: 'testId1',
    label: 'test field name',
    typeIds: ['parentType'],
    kind: 'list',
    dataSourceId: testDataSourceId,
  }, {
    type: 'property',
    propType: 'field',
    fieldId: 'testId2',
    label: 'test field name',
    typeIds: ['childType'],
    kind: 'scalar',
    dataSourceId: testDataSourceId,
  }, testDataSourceId)).toBeTruthy()
})

test('get tree with options', () => {
  const fn = jest.fn(() => ([{
    id: 'rootTypeId1',
    label: 'test',
    dataSourceId: testDataSourceId,
    fields: [{
      id: 'childField1',
      label: 'test',
      typeIds: ['childTypeId1'],
      kind: 'scalar',
      dataSourceId: testDataSourceId,
    }, {
      id: 'childField3',
      label: 'test',
      typeIds: ['childTypeId3'],
      kind: 'scalar',
      dataSourceId: testDataSourceId,
    }],
  }, {
    id: 'rootTypeId2',
    label: 'test',
    dataSourceId: testDataSourceId,
    fields: [{
      id: 'childField2',
      label: 'test',
      typeIds: ['childTypeId2'],
      kind: 'scalar',
      dataSourceId: testDataSourceId,
    }],
  }] as Type[]))

  const dataTree = new DataTree(editor as DataSourceEditor, {filters: [], dataSources: [{
    id: testDataSourceId,
    connect: async () => {},
    isConnected: () => true,
    getTypes: fn,
    getQueryables: () => [],
    getQuery: () => '',
  }]})

  // Simple expression with relative child expression
  const expression1 = [
    testTokens.rootField1,
    {
      ...testTokens.filter,
      options: {
        childExpressionRelative: JSON.stringify([testTokens.childField1]),
      },
    } as Filter,
  ] as Expression
  const component = editor.getComponents().first()
  expect(() => dataTree.getTrees({expression: expression1, component}, testDataSourceId)).not.toThrow()
  expect(dataTree.getTrees({expression: expression1, component}, testDataSourceId))
    .toEqual([{
      token: testTokens.rootField1,
      children: [{
        token: testTokens.childField1,
        children: [],
      }],
    }])
  // More complex expression with absolute child expression
  const expression2: Expression = [{
    ...testTokens.rootField1,
  }, {
    ...testTokens.filter,
    options: {
      id: 1,
      childExpressionAbsolute: JSON.stringify([testTokens.rootField2, testTokens.childField2]),
      childExpressionRelative: JSON.stringify([testTokens.childField1]),
    },
  } as Filter,
  testTokens.childField3
  ]
  expect(() => dataTree.getTrees({expression: expression2, component}, testDataSourceId)).not.toThrow()
  expect(dataTree.getTrees({expression: expression2, component}, testDataSourceId))
    .toEqual([{
      token: testTokens.rootField1,
      children: [{
        token: testTokens.childField3,
        children: [],
      }],
    }, {
      token: testTokens.rootField1,
      children: [],
    }, {
      token: testTokens.rootField2,
      children: [{
        token: testTokens.childField2,
        children: [],
      }],
    }, {
      token: testTokens.rootField1,
      children: [{
        token: testTokens.childField1,
        children: [],
      }],
    }])
})

test('get types map', () => {
  const dataTree = new DataTree(editor as DataSourceEditor, {
    filters: [],
    dataSources: [{
      id: testDataSourceId,
      connect: async () => {},
      isConnected: () => true,
      getTypes: () => simpleTypes,
      getQueryables: () => simpleTypes[0].fields,
      getQuery: () => '',
    }],
  })
  const types = dataTree.getAllTypes()
  expect(types).toBeDefined()
  expect(types).toHaveLength(2)
  expect(types[0].id).toBe('testTypeId')
})

// TODO: Value tests
// const simpleExpression: Context = [
//   {
//     type: 'property',
//     propType: 'type',
//     typeId: 'testTypeId',
//     dataSourceId: DataSourceId,
//   }, {
//     type: 'property',
//     propType: 'field',
//     fieldId: 'testFieldId',
//     typeId: 'testTypeId',
//     dataSourceId: DataSourceId,
//   }
// ]
// test('get value with simple context', () => {
//   const dataTree = new DataTree({
//     filters: [],
//     dataSources: [{
//       id: DataSourceId,
//       connect: async () => { },
//       getTypes: () => simpleTypes,
//     }],
//   })
// 
//   // Empty value
//   expect(dataTree.getValue(simpleExpression, [])).toBeNull()
// 
//   // 1 level value
//   const value = dataTree.getValue(simpleExpression, [{
//     type: 'property',
//     propType: 'type',
//     typeId: 'testTypeId',
//     dataSourceId: DataSourceId,
//   }])
//   expect(value).not.toBeNull()
//   // TODO: test value
// 
//   // 2 levels value
//   const value2 = dataTree.getValue(simpleExpression, [{
//     type: 'property',
//     propType: 'type',
//     typeId: 'testTypeId',
//     dataSourceId: DataSourceId,
//   }, {
//     type: 'property',
//     propType: 'field',
//     fieldId: 'testFieldId',
//     typeId: 'testTypeId',
//     dataSourceId: DataSourceId,
//   }])
//   expect(value2).not.toBeNull()
//   // TODO: test value
// })

test('Get experessions used by a component', () => {
  const dataTree = new DataTree(editor as DataSourceEditor, {
    filters: simpleFilters,
    dataSources: [{
      id: testDataSourceId,
      connect: async () => { },
      isConnected: () => true,
      getTypes: () => simpleTypes,
      getQueryables: () => simpleQueryables,
      getQuery: () => '',
    }],
  })
  const component = editor.getComponents().first()
  const expression = [{
    type: 'property',
    propType: 'field',
    fieldId: 'testFieldId',
    label: 'test field name',
    typeIds: ['testTypeId'],
    kind: 'object',
    dataSourceId: testDataSourceId,
  }] as Property[]
  ;(getStates as jest.Mock).mockReturnValue([{
    expression,
  }])
  expect(dataTree.getComponentExpressions(component)[0].expression).toBe(expression)
})

test('Get experessions used by a component and its children', () => {
  class DataTreeTest extends DataTree {
    getComponentExpressions = jest.fn(() => [])
  }
  const dataTree = new DataTreeTest(editor as DataSourceEditor, {
    filters: [],
    dataSources: [],
  })
  dataTree.getComponentExpressionsRecursive(firstComponent)
  expect(dataTree.getComponentExpressions).toHaveBeenCalledTimes(1) // 1 per component
})

test('Get experessions used by a page', () => {
  class DataTreeTest extends DataTree {
    getComponentExpressionsRecursive = jest.fn(() => [])
  }
  const dataTree = new DataTreeTest(editor as DataSourceEditor, {
    filters: [],
    dataSources: [],
  })
  dataTree.getPageExpressions((editor.Pages.getAll()[0]))
  expect(dataTree.getComponentExpressionsRecursive).toHaveBeenCalledTimes(1)
})

test('Get experessions used by all pages', () => {
  class DataTreeTest extends DataTree {
    getPageExpressions = jest.fn(() => [])
  }
  const dataTree = new DataTreeTest(editor as DataSourceEditor, {
    filters: [],
    dataSources: [],
  })
  dataTree.getAllPagesExpressions()
  expect(dataTree.getPageExpressions).toHaveBeenCalledTimes(1) // 1 per page
})
