Skip to content

Commit 52dca18

Browse files
committed
migrate to ts, add tests and update configs
1 parent 5c8d6b2 commit 52dca18

15 files changed

+533
-172
lines changed

Diff for: .babelrc

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
{
22
"presets": ["@babel/preset-env", "@babel/preset-react"],
3-
"plugins": ["@babel/plugin-proposal-class-properties"]
3+
"plugins": ["@babel/plugin-proposal-class-properties"],
4+
"overrides": [
5+
{
6+
"test": ["./src/**/*.tsx", "./src/*.tsx", "./src/*.ts", "./src/**/*.ts"],
7+
"presets": ["@babel/preset-typescript"]
8+
}
9+
]
410
}

Diff for: package.json

+4
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,15 @@
3434
"@babel/plugin-proposal-class-properties": "^7.8.3",
3535
"@babel/preset-env": "^7.8.3",
3636
"@babel/preset-react": "^7.8.3",
37+
"@babel/preset-typescript": "^7.8.3",
38+
"@types/react": "^16.9.19",
3739
"babel-loader": "^8.0.6",
3840
"peer-deps-externals-webpack-plugin": "^1.0.4",
3941
"react": "^16.12.0",
42+
"react-docgen-typescript": "^1.16.2",
4043
"react-dom": "^16.12.0",
4144
"react-styleguidist": "^10.6.0",
45+
"typescript": "^3.7.5",
4246
"webpack": "^4.5.0",
4347
"webpack-cli": "^3.2.1"
4448
},

Diff for: src/Test.js renamed to src/Test.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react'
22
import Drop, { EditorState, TreeNode } from '.'
3-
import { functions, staticValues } from './helpers/functions'
3+
import { functions, staticValues } from './utils/helpers'
44

55
const options = [...functions, ...staticValues]
66

@@ -21,9 +21,8 @@ const validationFn = val => {
2121
}
2222

2323
const Root = props => {
24-
const EditorData = new EditorState()
2524
const rootNode = new TreeNode(null)
26-
EditorData.initRoot(rootNode)
25+
const EditorData = new EditorState(rootNode)
2726

2827
return (
2928
<>

Diff for: src/__tests__/Extractor.test.tsx

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import React from 'react'
2+
import Extractor from '../'
3+
import { cleanup, fireEvent, render } from '../../../common/TestUtil'
4+
import { functions, staticValues } from '../helpers'
5+
6+
afterEach(cleanup)
7+
8+
// functions for the extractor
9+
const options = [...functions, ...staticValues]
10+
11+
const concatFunction = functions[1]
12+
const adDimension = staticValues[1]
13+
14+
const onUpdate = editorState => {
15+
return editorState.buildExpression()
16+
}
17+
18+
const extractorProps = {
19+
onChangeFn: onUpdate,
20+
options: options
21+
}
22+
23+
describe('Extractor renders properly', () => {
24+
it('Renders correctly', () => {
25+
const { asFragment } = render(<Extractor {...extractorProps} />)
26+
27+
// Get the options list which should be shown by default
28+
const optionsList = document.querySelector('[data-type="expression-list"]')
29+
expect(optionsList).toBeInTheDocument()
30+
31+
// Check initial focus on render
32+
const { activeElement } = document
33+
expect(activeElement!.parentElement).toHaveAttribute(
34+
'data-type',
35+
'expression-input-root'
36+
)
37+
38+
// Input field should how focus initially
39+
expect(asFragment()).toMatchSnapshot()
40+
})
41+
it('Scaffolds an expression on typing one', () => {
42+
const { asFragment } = render(<Extractor {...extractorProps} />)
43+
// Get the root input element
44+
const { activeElement } = document
45+
fireEvent.input(activeElement as any, {
46+
target: { value: functions[0].key }
47+
})
48+
49+
// Check that all params are scaffolded properly
50+
const expressionInputRoots = document.querySelectorAll(
51+
'[data-type="expression-input-root"]'
52+
)
53+
expect(expressionInputRoots).toHaveLength(3)
54+
55+
// Check that the expression root is created
56+
const expressionRoots = document.querySelectorAll(
57+
'[data-type="expression-root"]'
58+
)
59+
expect(expressionRoots).toHaveLength(1)
60+
61+
expect(asFragment()).toMatchSnapshot()
62+
})
63+
it('Hides options list on blur', () => {
64+
const { asFragment } = render(<Extractor {...extractorProps} />)
65+
// Initially the input has focus
66+
// Get the options list which should be shown by default
67+
const optionsList = document.querySelector('[data-type="expression-list"]')
68+
expect(optionsList).toBeInTheDocument()
69+
70+
// create a new element and transfer focus
71+
const newInput = document.createElement('input')
72+
newInput.focus()
73+
74+
expect(optionsList).not.toBeInTheDocument()
75+
76+
expect(asFragment()).toMatchSnapshot()
77+
})
78+
it('Applies correct data attributes', () => {
79+
const { asFragment } = render(<Extractor {...extractorProps} />)
80+
81+
// Get the root input element
82+
const { activeElement } = document
83+
fireEvent.input(activeElement as any, {
84+
target: { value: staticValues[1].label }
85+
})
86+
87+
// Check for attributes
88+
expect(activeElement).toHaveAttribute('data-value-type', 'dimension')
89+
expect(activeElement).toHaveStyle('background-color: #fdedce')
90+
91+
expect(asFragment()).toMatchSnapshot()
92+
})
93+
})
94+
95+
describe('Extractor builds data structure properly', () => {
96+
it('Fires the onChange function', () => {
97+
const onChangeFn = jest.fn()
98+
const { asFragment } = render(
99+
<Extractor {...extractorProps} onChangeFn={onChangeFn} />
100+
)
101+
102+
// Get the root input element
103+
const { activeElement } = document
104+
fireEvent.change(activeElement as any, {
105+
target: { value: adDimension.label }
106+
})
107+
108+
expect(onChangeFn).toHaveBeenCalled()
109+
110+
expect(asFragment()).toMatchSnapshot()
111+
})
112+
113+
it('Builds correct string on input', () => {
114+
const onChangeFn = jest.fn(editorState => editorState.buildExpression())
115+
const { asFragment } = render(
116+
<Extractor {...extractorProps} onChangeFn={onChangeFn} />
117+
)
118+
119+
// Scaffold and expression
120+
const { activeElement } = document
121+
fireEvent.change(activeElement as any, {
122+
target: { value: concatFunction.label }
123+
})
124+
125+
// Get scaffolded params
126+
const expressionParamsInput = document.querySelectorAll(
127+
'[data-type="expression-input-root"] input'
128+
)
129+
expect(expressionParamsInput).toHaveLength(2)
130+
fireEvent.change(expressionParamsInput[0], {
131+
target: { value: adDimension.label }
132+
})
133+
fireEvent.change(expressionParamsInput[1], { target: { value: '"_"' } })
134+
135+
expect(onChangeFn).toHaveBeenCalledTimes(3)
136+
expect(onChangeFn.mock.results[2].value).toEqual('CONCAT (Ad, "_")')
137+
138+
expect(asFragment()).toMatchSnapshot()
139+
})
140+
})
141+
142+
describe('Keyboard events work properly', () => {
143+
it('Should navigate to the expression root on pressing left on first param', () => {
144+
const { asFragment } = render(<Extractor {...extractorProps} />)
145+
146+
// Get the root input element
147+
let { activeElement } = document
148+
fireEvent.change(activeElement as any, {
149+
target: { value: concatFunction.key }
150+
})
151+
152+
// Active element will be changed to the first param of the expression
153+
activeElement = document.activeElement
154+
155+
// Fire left arrow key
156+
fireEvent.keyDown(activeElement as any, { keyCode: 37 })
157+
158+
// Check that the focus has shifted to the root of the expression
159+
activeElement = document.activeElement
160+
expect(activeElement).toHaveAttribute('data-type', 'expression-root')
161+
162+
expect(asFragment()).toMatchSnapshot()
163+
})
164+
it('Should navigate to the next param on pressing right ', () => {
165+
const { asFragment } = render(<Extractor {...extractorProps} />)
166+
167+
// Get the root input element
168+
let { activeElement } = document
169+
fireEvent.change(activeElement as any, {
170+
target: { value: concatFunction.key }
171+
})
172+
173+
// Active element will be changed to the first param of the expression
174+
activeElement = document.activeElement
175+
176+
// Fire left arrow key
177+
fireEvent.keyDown(activeElement as any, { keyCode: 39 })
178+
179+
// Check that the focus has shifted to the root of the expression
180+
activeElement = document.activeElement
181+
expect(activeElement!.parentElement).toHaveAttribute(
182+
'data-type',
183+
'expression-input-root'
184+
)
185+
const allInputs = document.querySelectorAll(
186+
'[data-type="expression-input-root"]'
187+
)
188+
const lastInput = allInputs[1].firstElementChild
189+
expect(lastInput).toEqual(activeElement)
190+
191+
// Check that on pressing right, the focus stays for last param
192+
fireEvent.keyDown(activeElement as any, { keyCode: 39 })
193+
expect(document.activeElement).toEqual(lastInput)
194+
195+
expect(asFragment()).toMatchSnapshot()
196+
})
197+
it('Should delete the expression root on pressing backspace ', () => {
198+
const { asFragment } = render(<Extractor {...extractorProps} />)
199+
200+
// Get the root input element
201+
let { activeElement } = document
202+
fireEvent.change(activeElement as any, {
203+
target: { value: concatFunction.key }
204+
})
205+
206+
// Active element will be changed to the first param of the expression
207+
activeElement = document.activeElement
208+
209+
// Fire left arrow key
210+
fireEvent.keyDown(activeElement as any, { keyCode: 37 })
211+
212+
// Get the expression root
213+
activeElement = document.activeElement
214+
215+
// Press backspace
216+
fireEvent.keyDown(activeElement as any, { keyCode: 8 })
217+
// Expression root should have been removed by now
218+
expect(activeElement).not.toBeInTheDocument()
219+
220+
expect(asFragment()).toMatchSnapshot()
221+
})
222+
})
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import { NodeType, TreeNodeValueType } from './types'
2+
13
export class TreeNode {
4+
value: TreeNodeValueType
5+
children: NodeType[]
26
constructor(value) {
37
this.value = value
48
this.children = []
59
}
610

7-
addChild(node) {
11+
addChild(node: NodeType) {
812
this.children.push(node)
913
}
1014

@@ -18,26 +22,19 @@ export class TreeNode {
1822
}
1923

2024
export class EditorState {
21-
constructor() {
22-
this.root = null
23-
}
25+
root: NodeType
2426

25-
initRoot(node) {
27+
constructor(node) {
2628
this.root = node
2729
}
2830

29-
buildExpression = (node = this.root) => {
30-
// console.log(node)
31+
buildExpression = (node: NodeType = this.root): any => {
3132
let str = ''
32-
if (node.value.type === 'string') return node.value.data
33+
if (node.value.type !== 'fn') return node.value.data
3334
node.children.forEach((child, idx) => {
3435
str += this.buildExpression(child)
3536
str += idx === node.children.length - 1 ? '' : ', '
3637
})
3738
return `${node.value.data.label} (${str})`
3839
}
39-
40-
addNode(value) {
41-
if (!this.root) this.root = new TreeNode(value)
42-
}
4340
}

0 commit comments

Comments
 (0)