@silexlabs/grapesjs-data-source
Version:
Grapesjs Data Source
313 lines (290 loc) • 9.27 kB
text/typescript
/**
* @jest-environment jsdom
*/
import grapesjs, { Component, Editor } from 'grapesjs'
import { DataTree } from './DataTree'
import { getCompletion, getContext } from './completion'
import { simpleFilters, simpleQueryables, simpleTypes, testDataSourceId } from '../test-data'
import { DataSourceEditor, Filter, Property, State, Token } from '../types'
import { getOrCreatePersistantId, getStateIds } from './state'
// FIXME: Workaround to avoid import of lit-html which breakes unit tests
jest.mock('lit', () => ({
html: jest.fn(),
render: jest.fn(),
}))
// Mock only getState
jest.mock('./state', () => ({
...jest.requireActual('./state'),
getState: jest.fn(),
getStateIds: jest.fn(),
getOrCreatePersistantId: jest.fn(),
getParentByPersistentId: jest.fn(),
}))
/**
* Remove the optionsForm from the token
* This is how tokens are stored
*/
function asStored(val: Token) {
delete (val as Property | Filter).optionsForm
return val
}
const simpleQueryableTokens: Property[] = [{
fieldId: 'testSimpleQueryableId',
label: 'test queryable',
type: 'property',
propType: 'field',
typeIds: ['testTypeId'],
kind: 'scalar',
dataSourceId: testDataSourceId,
}]
const fixedToken = {
fieldId: 'fixed',
label: 'Fixed value',
type: 'property',
propType: 'field',
kind: 'scalar',
typeIds: ['String'],
options: { value: '' },
}
let editor: Editor
beforeEach(async () => {
jest.resetAllMocks()
;(getStateIds as jest.Mock).mockReturnValue([]) // Default for getStateIds
editor = grapesjs.init({
container: document.createElement('div'),
components: '<div></div>',
})
})
test('get empty context', () => {
const editor = grapesjs.init({
container: document.createElement('div'),
components: '<div></div>',
})
const component = editor.getComponents().first()
const dataTree = new DataTree(editor as DataSourceEditor, {filters: [], dataSources: []})
const context = getContext(component, dataTree)
expect(context).toBeDefined()
expect(context).toHaveLength(1) // 1 Fixed value
})
test('get context with filters', () => {
const component = editor.getComponents().first()
const dataTree = new DataTree(editor as DataSourceEditor, {
dataSources: [],
filters: simpleFilters,
})
const context = getContext(component, dataTree)
expect(context).toBeDefined()
expect(context).toHaveLength(2) // 1 Filter only for no input + 1 Fixed value
const filter = context[0] as Filter
expect(filter.id).toBe('testFilterAnyInput')
})
test('get context with parent compontent states', () => {
const component = editor.getComponents().first()
const child: Component = component.append('<div></div>')[0]
expect(component.get('components')).toHaveLength(1)
expect(child).toBeDefined()
// Define mock values
;(getStateIds as jest.Mock).mockImplementation((c: unknown) => {
if(c === component) return ['testStateId']
else return []
})
;(getOrCreatePersistantId as jest.Mock).mockReturnValue('testPersistentId')
const dataTree = new DataTree(editor as DataSourceEditor, { filters: [], dataSources: [] })
const context = getContext(child, dataTree)
expect(context).toBeDefined()
expect(context).toHaveLength(2) // 1 State + 1 Fixed value
const typeProp = context[0] as State
expect(typeProp.type).toBe('state')
expect(typeProp.componentId).toBe('testPersistentId')
expect(typeProp.storedStateId).toBe('testStateId')
})
// Don't get the states which are not yet defined
test('get context with available states only', () => {
const component = editor.getComponents().first()
const child: Component = component.append('<div></div>')[0]
// Define mock values
;(getStateIds as jest.Mock).mockImplementation((c: unknown) => {
if(c === component) return ['testStateId0', 'testStateId1', 'testStateId2']
else if(c === child) return ['childStateId0', 'childStateId1', 'childStateId2']
else return []
})
const dataTree = new DataTree(editor as DataSourceEditor, { filters: [], dataSources: [] })
// Case of an attribute, all states are available
const context = getContext(component, dataTree)
expect(context).toBeDefined()
expect(getStateIds).toHaveBeenCalledTimes(2) // The component and the body, not the child
expect(getStateIds).toHaveBeenNthCalledWith(1, component, true, undefined)
expect(getStateIds).toHaveBeenNthCalledWith(2, component.parent(), true, undefined)
// Case of the first state, no states are available
;(getStateIds as jest.Mock).mockClear()
const context2 = getContext(child, dataTree, 'childStateId0')
expect(context2).toBeDefined()
expect(getStateIds).toHaveBeenCalledTimes(3)
expect(getStateIds).toHaveBeenNthCalledWith(1, child, true, 'childStateId0')
expect(getStateIds).toHaveBeenNthCalledWith(2, child.parent(), true, undefined)
})
test('get context with data source queryable values', () => {
const component = editor.getComponents().first()
const dataTree = new DataTree(editor as DataSourceEditor, {
filters: [],
dataSources: [{
id: testDataSourceId,
connect: async () => {},
isConnected: () => true,
getTypes: () => [{
id: 'testTypeId1',
label: 'test type name 1',
kind: 'scalar',
fields: [],
dataSourceId: testDataSourceId,
}, {
id: 'testTypeId2',
label: 'test type name 2',
kind: 'scalar',
fields: [],
dataSourceId: testDataSourceId,
}],
getQueryables: () => [{
id: 'testFieldId2',
label: 'test field name 2',
typeIds: ['testTypeId2'],
kind: 'scalar',
dataSourceId: testDataSourceId,
}],
getQuery: () => '',
}],
})
const context = getContext(component, dataTree)
expect(context).toBeDefined()
expect(context).toHaveLength(2) // 1 Queryable + 1 Fixed value
const typeProp = context[0] as Property
expect(typeProp.fieldId).toContain('testFieldId2')
})
test('get completion with simple context', () => {
const dataTree = new DataTree(editor as DataSourceEditor, {
filters: [],
dataSources: [{
id: testDataSourceId,
connect: async () => { },
isConnected: () => true,
getTypes: () => simpleTypes,
getQueryables: () => simpleQueryables,
getQuery: () => '',
}],
})
const component = editor.getComponents().first()
// Empty value
// The context is the queryables here
const completion1 = getCompletion({
component,
expression: [],
dataTree,
})
expect(completion1).toHaveLength(2)
expect(completion1.map(asStored)).toEqual([
...simpleQueryableTokens,
fixedToken,
])
// 1 level value
const completion2 = getCompletion({
component,
expression: [{
type: 'property',
propType: 'field',
fieldId: 'testFieldId',
label: 'test field name',
typeIds: ['testTypeId'],
kind: 'object',
dataSourceId: testDataSourceId,
}],
dataTree,
})
expect(completion2).toHaveLength(1)
const typeProp = completion2[0] as Property
expect(typeProp.typeIds).toContain('testFieldTypeId')
// 2 levels value
const completion3 = getCompletion({
component,
expression: [{
type: 'property',
propType: 'field',
fieldId: 'testFieldId',
label: 'test field name',
typeIds: ['testTypeId'],
kind: 'object',
dataSourceId: testDataSourceId,
}, {
type: 'property',
propType: 'field',
fieldId: 'testFieldId',
label: 'test field name',
typeIds: ['testFieldTypeId'],
kind: 'object',
dataSourceId: testDataSourceId,
}],
dataTree,
})
expect(completion3).toHaveLength(0)
})
test('get completion with filters', () => {
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()
// Empty value
expect(getCompletion({
component,
expression: [],
dataTree,
}).map(asStored)).toEqual([
simpleQueryableTokens[0],
simpleFilters[0],
fixedToken,
])
// 1 level value
const completion = getCompletion({
component,
expression: [{
type: 'property',
propType: 'field',
fieldId: 'testFieldId',
label: 'test field name',
typeIds: ['testTypeId'],
kind: 'object',
dataSourceId: testDataSourceId,
}],
dataTree,
})
expect(completion).toHaveLength(2) // 1 field and 1 filters
// 2 levels value
const completion2 = getCompletion({
component,
expression: [{
type: 'property',
propType: 'field',
fieldId: 'testFieldId',
label: 'test field name',
typeIds: ['testTypeId'],
kind: 'object',
dataSourceId: testDataSourceId,
}, {
type: 'property',
propType: 'field',
fieldId: 'testFieldId',
label: 'test field name',
typeIds: ['testFieldTypeId'],
kind: 'object',
dataSourceId: testDataSourceId,
}],
dataTree,
})
expect(completion2).toHaveLength(1) // 1 filter
})