mirror of
https://github.com/lana-k/sqliteviz.git
synced 2026-11-04 00:39:24 +08:00
#140 tests for search panel component
This commit is contained in:
@@ -244,7 +244,6 @@
|
|||||||
v-model:nodesMatchingSearchQuery="nodesMatchingSearchQuery"
|
v-model:nodesMatchingSearchQuery="nodesMatchingSearchQuery"
|
||||||
v-model:focusedFoundNodeId="focusedFoundNodeId"
|
v-model:focusedFoundNodeId="focusedFoundNodeId"
|
||||||
:graph="graph"
|
:graph="graph"
|
||||||
:renderer="renderer"
|
|
||||||
class="search-panel"
|
class="search-panel"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -517,6 +516,23 @@ export default {
|
|||||||
this.renderer.scheduleRender()
|
this.renderer.scheduleRender()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
nodesMatchingSearchQuery() {
|
||||||
|
if (this.renderer) {
|
||||||
|
this.renderer.refresh({
|
||||||
|
skipIndexation: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
focusedFoundNodeId() {
|
||||||
|
if (this.renderer) {
|
||||||
|
this.renderer.refresh({
|
||||||
|
skipIndexation: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (this.focusedFoundNodeId) {
|
||||||
|
this.moveCameraToNode(this.focusedFoundNodeId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -839,6 +855,15 @@ export default {
|
|||||||
return drawOnCanvas(this.renderer, {
|
return drawOnCanvas(this.renderer, {
|
||||||
backgroundColor: this.settings.style.backgroundColor
|
backgroundColor: this.settings.style.backgroundColor
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
moveCameraToNode(nodeId) {
|
||||||
|
if (!this.graph) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const nodePosition = this.renderer.getNodeDisplayData(nodeId)
|
||||||
|
this.renderer.getCamera().animate(nodePosition, {
|
||||||
|
duration: 500
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
:title="panelOpen ? 'Close search panel' : 'Open search panel'"
|
:title="panelOpen ? 'Close search panel' : 'Open search panel'"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
:class="{ 'close-search-panel': panelOpen }"
|
:class="{ 'close-search-panel': panelOpen }"
|
||||||
|
data-test="toggle-search-panel-visibility-btn"
|
||||||
@click="panelOpen = !panelOpen"
|
@click="panelOpen = !panelOpen"
|
||||||
/>
|
/>
|
||||||
<div v-show="panelOpen" class="graph-search-panel--search-controls">
|
<div v-show="panelOpen" class="graph-search-panel--search-controls">
|
||||||
@@ -21,12 +22,14 @@
|
|||||||
icon="myicon myicon-match-case"
|
icon="myicon myicon-match-case"
|
||||||
title="Match case"
|
title="Match case"
|
||||||
:active="matchCase"
|
:active="matchCase"
|
||||||
|
data-test="match-case-btn"
|
||||||
@click="matchCase = !matchCase"
|
@click="matchCase = !matchCase"
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="myicon myicon-use-reg-exp"
|
icon="myicon myicon-use-reg-exp"
|
||||||
title="Use regular expression"
|
title="Use regular expression"
|
||||||
:active="useRegExp"
|
:active="useRegExp"
|
||||||
|
data-test="use-reg-exp-btn"
|
||||||
@click="useRegExp = !useRegExp"
|
@click="useRegExp = !useRegExp"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -42,7 +45,7 @@
|
|||||||
v-if="
|
v-if="
|
||||||
nodesMatchingSearchQuery?.length > 0 && currentFoundNodeIndex !== -1
|
nodesMatchingSearchQuery?.length > 0 && currentFoundNodeIndex !== -1
|
||||||
"
|
"
|
||||||
>{{ currentFoundNodeIndex + 1 }} /
|
>{{ currentFoundNodeIndex + 1 }} of
|
||||||
{{ nodesMatchingSearchQuery.length }}
|
{{ nodesMatchingSearchQuery.length }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -50,6 +53,7 @@
|
|||||||
v-show="
|
v-show="
|
||||||
nodesMatchingSearchQuery && nodesMatchingSearchQuery.length === 0
|
nodesMatchingSearchQuery && nodesMatchingSearchQuery.length === 0
|
||||||
"
|
"
|
||||||
|
data-test="no-results-text"
|
||||||
>
|
>
|
||||||
No results
|
No results
|
||||||
</span>
|
</span>
|
||||||
@@ -58,6 +62,7 @@
|
|||||||
icon="myicon myicon-arrow"
|
icon="myicon myicon-arrow"
|
||||||
title="Previous match"
|
title="Previous match"
|
||||||
:disabled="arrowsDisabled"
|
:disabled="arrowsDisabled"
|
||||||
|
data-test="previous-match-btn"
|
||||||
@click="toNextFoundNode(-1)"
|
@click="toNextFoundNode(-1)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -66,6 +71,7 @@
|
|||||||
title="Next match"
|
title="Next match"
|
||||||
:disabled="arrowsDisabled"
|
:disabled="arrowsDisabled"
|
||||||
class="next-match-btn"
|
class="next-match-btn"
|
||||||
|
data-test="next-match-btn"
|
||||||
@click="toNextFoundNode(1)"
|
@click="toNextFoundNode(1)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,12 +98,7 @@ export default {
|
|||||||
TextField,
|
TextField,
|
||||||
IconButton
|
IconButton
|
||||||
},
|
},
|
||||||
props: [
|
props: ['graph', 'nodesMatchingSearchQuery', 'focusedFoundNodeId'],
|
||||||
'graph',
|
|
||||||
'renderer',
|
|
||||||
'nodesMatchingSearchQuery',
|
|
||||||
'focusedFoundNodeId'
|
|
||||||
],
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
@@ -136,7 +137,7 @@ export default {
|
|||||||
}).format(count)
|
}).format(count)
|
||||||
: count
|
: count
|
||||||
|
|
||||||
return `${formattedCount} nodes found`
|
return `${formattedCount} ${count === 1 ? 'node' : 'nodes'} found`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -150,9 +151,7 @@ export default {
|
|||||||
},
|
},
|
||||||
matchCase() {
|
matchCase() {
|
||||||
this.search()
|
this.search()
|
||||||
},
|
}
|
||||||
nodesMatchingSearchQuery: 'refresh',
|
|
||||||
focusedFoundNodeId: 'refresh'
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onSearchInput: time.debounce(function () {
|
onSearchInput: time.debounce(function () {
|
||||||
@@ -179,9 +178,6 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
toNextFoundNode(direction = 1) {
|
toNextFoundNode(direction = 1) {
|
||||||
if (this.nodesMatchingSearchQuery.length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.currentFoundNodeIndex =
|
this.currentFoundNodeIndex =
|
||||||
(this.currentFoundNodeIndex +
|
(this.currentFoundNodeIndex +
|
||||||
direction +
|
direction +
|
||||||
@@ -189,28 +185,6 @@ export default {
|
|||||||
this.nodesMatchingSearchQuery.length
|
this.nodesMatchingSearchQuery.length
|
||||||
const nodeId = this.nodesMatchingSearchQuery[this.currentFoundNodeIndex]
|
const nodeId = this.nodesMatchingSearchQuery[this.currentFoundNodeIndex]
|
||||||
this.$emit('update:focusedFoundNodeId', nodeId)
|
this.$emit('update:focusedFoundNodeId', nodeId)
|
||||||
if (this.renderer) {
|
|
||||||
this.renderer.refresh({
|
|
||||||
skipIndexation: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.moveCameraToNode(nodeId)
|
|
||||||
},
|
|
||||||
moveCameraToNode(nodeId) {
|
|
||||||
if (!this.graph) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const nodePosition = this.renderer.getNodeDisplayData(nodeId)
|
|
||||||
this.renderer.getCamera().animate(nodePosition, {
|
|
||||||
duration: 500
|
|
||||||
})
|
|
||||||
},
|
|
||||||
refresh() {
|
|
||||||
if (this.renderer) {
|
|
||||||
this.renderer.refresh({
|
|
||||||
skipIndexation: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
import { expect } from 'chai'
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import Graph from 'graphology'
|
||||||
|
import GraphSearchPanel from '@/components/Graph/GraphSearchPanel.vue'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import { nextTick } from 'vue'
|
||||||
|
|
||||||
|
describe('GraphSearchPanel.vue', () => {
|
||||||
|
const buildGraph = () => {
|
||||||
|
const graph = new Graph()
|
||||||
|
graph.addNode('node-1', { label: 'Alpha' })
|
||||||
|
graph.addNode('node-2', { label: 'Beta' })
|
||||||
|
graph.addNode('node-3', { label: 'Gamma' })
|
||||||
|
graph.addNode('node-4', { label: 'Foo' })
|
||||||
|
return graph
|
||||||
|
}
|
||||||
|
|
||||||
|
it('matches labels and emits node ids for navigation', async () => {
|
||||||
|
const clock = sinon.useFakeTimers()
|
||||||
|
const wrapper = mount(GraphSearchPanel, {
|
||||||
|
props: {
|
||||||
|
graph: buildGraph(),
|
||||||
|
nodesMatchingSearchQuery: null,
|
||||||
|
focusedFoundNodeId: null,
|
||||||
|
'onUpdate:nodesMatchingSearchQuery': nodes => {
|
||||||
|
wrapper.setProps({ nodesMatchingSearchQuery: nodes })
|
||||||
|
},
|
||||||
|
'onUpdate:focusedFoundNodeId': nodeId => {
|
||||||
|
wrapper.setProps({ focusedFoundNodeId: nodeId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.find('.query-input input').setValue('a')
|
||||||
|
clock.tick(500)
|
||||||
|
|
||||||
|
expect(wrapper.emitted('update:nodesMatchingSearchQuery')[0][0]).to.eql([
|
||||||
|
'node-1',
|
||||||
|
'node-2',
|
||||||
|
'node-3'
|
||||||
|
])
|
||||||
|
expect(wrapper.emitted('update:focusedFoundNodeId')[0][0]).to.equal(null)
|
||||||
|
await nextTick()
|
||||||
|
expect(wrapper.text()).to.contain('3 nodes found')
|
||||||
|
|
||||||
|
await wrapper.find('[data-test="next-match-btn"]').trigger('click')
|
||||||
|
expect(wrapper.emitted('update:focusedFoundNodeId')[1][0]).to.equal(
|
||||||
|
'node-1'
|
||||||
|
)
|
||||||
|
await nextTick()
|
||||||
|
expect(wrapper.text()).to.contain('1 of 3')
|
||||||
|
|
||||||
|
await wrapper.find('[data-test="next-match-btn"]').trigger('click')
|
||||||
|
expect(wrapper.emitted('update:focusedFoundNodeId')[2][0]).to.equal(
|
||||||
|
'node-2'
|
||||||
|
)
|
||||||
|
await nextTick()
|
||||||
|
expect(wrapper.text()).to.contain('2 of 3')
|
||||||
|
|
||||||
|
await wrapper.find('[data-test="previous-match-btn"]').trigger('click')
|
||||||
|
expect(wrapper.emitted('update:focusedFoundNodeId')[3][0]).to.equal(
|
||||||
|
'node-1'
|
||||||
|
)
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
expect(wrapper.text()).to.contain('1 of 3')
|
||||||
|
|
||||||
|
await wrapper.find('[data-test="previous-match-btn"]').trigger('click')
|
||||||
|
expect(wrapper.emitted('update:focusedFoundNodeId')[4][0]).to.equal(
|
||||||
|
'node-3'
|
||||||
|
)
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
expect(wrapper.text()).to.contain('3 of 3')
|
||||||
|
|
||||||
|
await wrapper.find('[data-test="next-match-btn"]').trigger('click')
|
||||||
|
expect(wrapper.emitted('update:focusedFoundNodeId')[5][0]).to.equal(
|
||||||
|
'node-1'
|
||||||
|
)
|
||||||
|
await nextTick()
|
||||||
|
expect(wrapper.text()).to.contain('1 of 3')
|
||||||
|
|
||||||
|
await wrapper.find('.query-input input').setValue('Foo')
|
||||||
|
clock.tick(500)
|
||||||
|
expect(wrapper.emitted('update:nodesMatchingSearchQuery')[1][0]).to.eql([
|
||||||
|
'node-4'
|
||||||
|
])
|
||||||
|
expect(wrapper.emitted('update:focusedFoundNodeId')[6][0]).to.equal(null)
|
||||||
|
|
||||||
|
await wrapper.find('.query-input input').setValue('')
|
||||||
|
clock.tick(500)
|
||||||
|
expect(wrapper.emitted('update:nodesMatchingSearchQuery')[2][0]).to.eql(
|
||||||
|
null
|
||||||
|
)
|
||||||
|
expect(wrapper.emitted('update:focusedFoundNodeId')[7][0]).to.equal(null)
|
||||||
|
clock.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('matches case and uses reg exp', async () => {
|
||||||
|
const clock = sinon.useFakeTimers()
|
||||||
|
const wrapper = mount(GraphSearchPanel, {
|
||||||
|
props: {
|
||||||
|
graph: buildGraph(),
|
||||||
|
nodesMatchingSearchQuery: null,
|
||||||
|
focusedFoundNodeId: null,
|
||||||
|
'onUpdate:nodesMatchingSearchQuery': nodes => {
|
||||||
|
wrapper.setProps({ nodesMatchingSearchQuery: nodes })
|
||||||
|
},
|
||||||
|
'onUpdate:focusedFoundNodeId': nodeId => {
|
||||||
|
wrapper.setProps({ focusedFoundNodeId: nodeId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.find('.query-input input').setValue('A')
|
||||||
|
clock.tick(500)
|
||||||
|
|
||||||
|
expect(wrapper.emitted('update:nodesMatchingSearchQuery')[0][0]).to.eql([
|
||||||
|
'node-1',
|
||||||
|
'node-2',
|
||||||
|
'node-3'
|
||||||
|
])
|
||||||
|
|
||||||
|
const matchCaseBtn = wrapper.find('[data-test="match-case-btn"]')
|
||||||
|
const useRegExpBtn = wrapper.find('[data-test="use-reg-exp-btn"]')
|
||||||
|
|
||||||
|
await matchCaseBtn.trigger('click')
|
||||||
|
expect(wrapper.emitted('update:nodesMatchingSearchQuery')[1][0]).to.eql([
|
||||||
|
'node-1'
|
||||||
|
])
|
||||||
|
|
||||||
|
await wrapper.find('.query-input input').setValue('a.+a$')
|
||||||
|
clock.tick(500)
|
||||||
|
expect(wrapper.emitted('update:nodesMatchingSearchQuery')[2][0]).to.eql([])
|
||||||
|
|
||||||
|
await useRegExpBtn.trigger('click')
|
||||||
|
expect(wrapper.emitted('update:nodesMatchingSearchQuery')[3][0]).to.eql([
|
||||||
|
'node-3'
|
||||||
|
])
|
||||||
|
|
||||||
|
await matchCaseBtn.trigger('click')
|
||||||
|
expect(wrapper.emitted('update:nodesMatchingSearchQuery')[4][0]).to.eql([
|
||||||
|
'node-1',
|
||||||
|
'node-3'
|
||||||
|
])
|
||||||
|
|
||||||
|
clock.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows No items found and disables navigation arrows', async () => {
|
||||||
|
const clock = sinon.useFakeTimers()
|
||||||
|
const wrapper = mount(GraphSearchPanel, {
|
||||||
|
props: {
|
||||||
|
graph: buildGraph(),
|
||||||
|
nodesMatchingSearchQuery: null,
|
||||||
|
focusedFoundNodeId: null,
|
||||||
|
'onUpdate:nodesMatchingSearchQuery': nodes => {
|
||||||
|
wrapper.setProps({ nodesMatchingSearchQuery: nodes })
|
||||||
|
},
|
||||||
|
'onUpdate:focusedFoundNodeId': nodeId => {
|
||||||
|
wrapper.setProps({ focusedFoundNodeId: nodeId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.find('.query-input input').setValue('baz')
|
||||||
|
clock.tick(500)
|
||||||
|
|
||||||
|
expect(wrapper.emitted('update:nodesMatchingSearchQuery')[0][0]).to.eql([])
|
||||||
|
expect(wrapper.emitted('update:focusedFoundNodeId')[0][0]).to.equal(null)
|
||||||
|
await nextTick()
|
||||||
|
expect(wrapper.find('[data-test="no-results-text"]').isVisible()).to.be.true
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
wrapper.find('[data-test="next-match-btn"]').attributes('disabled')
|
||||||
|
).to.not.equal(undefined)
|
||||||
|
await expect(
|
||||||
|
wrapper.find('[data-test="previous-match-btn"]').attributes('disabled')
|
||||||
|
).to.not.equal(undefined)
|
||||||
|
clock.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles invalid regular expressions by clearing matches', async () => {
|
||||||
|
const clock = sinon.useFakeTimers()
|
||||||
|
const wrapper = mount(GraphSearchPanel, {
|
||||||
|
props: {
|
||||||
|
graph: buildGraph(),
|
||||||
|
nodesMatchingSearchQuery: ['node-1'],
|
||||||
|
focusedFoundNodeId: 'node-1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.find('.query-input input').setValue('[')
|
||||||
|
clock.tick(500)
|
||||||
|
|
||||||
|
expect(wrapper.emitted('update:nodesMatchingSearchQuery')[0][0]).to.eql([])
|
||||||
|
const useRegExpBtn = wrapper.find('[data-test="use-reg-exp-btn"]')
|
||||||
|
|
||||||
|
await useRegExpBtn.trigger('click')
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.emitted('update:nodesMatchingSearchQuery').at(-1)[0]
|
||||||
|
).to.equal(null)
|
||||||
|
expect(wrapper.emitted('update:focusedFoundNodeId').at(-1)[0]).to.equal(
|
||||||
|
null
|
||||||
|
)
|
||||||
|
expect(wrapper.text()).to.contain('Invalid regular expression')
|
||||||
|
|
||||||
|
clock.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toggles the search panel visibility', async () => {
|
||||||
|
const wrapper = mount(GraphSearchPanel, {
|
||||||
|
props: {
|
||||||
|
graph: buildGraph(),
|
||||||
|
nodesMatchingSearchQuery: null,
|
||||||
|
focusedFoundNodeId: null
|
||||||
|
},
|
||||||
|
attachTo: document.body
|
||||||
|
})
|
||||||
|
|
||||||
|
const searchControls = wrapper.find('.graph-search-panel--search-controls')
|
||||||
|
expect(searchControls.isVisible()).to.be.false
|
||||||
|
|
||||||
|
const toggleButton = wrapper.find(
|
||||||
|
'[data-test="toggle-search-panel-visibility-btn"]'
|
||||||
|
)
|
||||||
|
await toggleButton.trigger('click')
|
||||||
|
expect(searchControls.isVisible()).to.be.true
|
||||||
|
|
||||||
|
await toggleButton.trigger('click')
|
||||||
|
expect(wrapper.vm.panelOpen).to.be.false
|
||||||
|
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user