diff --git a/cypress/e2e/nodes_spec.cy.js b/cypress/e2e/nodes_spec.cy.js index 2651879..ed77727 100644 --- a/cypress/e2e/nodes_spec.cy.js +++ b/cypress/e2e/nodes_spec.cy.js @@ -4,9 +4,9 @@ describe('Solution Navigator', () => { const initialState = { nodes: [ { - uuid: '860639dd-92ab-4220-9513-4488329db5dd', + uuid: leafNodeUuid, title: 'my child node level 1', - content: '', + content: 'my content level 1', resolved: false, childNodes: [], parentNode: 'd1d551f6-ff27-4bf5-86be-cc2fb5ca6caf', @@ -19,14 +19,14 @@ describe('Solution Navigator', () => { title: 'my child node', content: '', resolved: false, - childNodes: ['860639dd-92ab-4220-9513-4488329db5dd'], - parentNode: '5b2fa173-b6d4-4e91-8c16-9c5ab76397b8', + childNodes: [leafNodeUuid], + parentNode: rootNodeUuid, pomodoroCount: 0, createdAt: '2024-06-23T20:41:19.297Z', updatedAt: '2024-06-23T20:41:32.314Z', }, { - uuid: '5b2fa173-b6d4-4e91-8c16-9c5ab76397b8', + uuid: rootNodeUuid, title: 'my title', content: 'my content', resolved: false, @@ -144,14 +144,6 @@ describe('Solution Navigator', () => { cy.get('.side-panel').should('not.be.visible'); }); - context('when the node does not exist', () => { - it('displays a not found message', () => { - cy.visit('/nodes/62090d47-3104-4d50-b384-54728a0208dd'); - - cy.get('[data-test-node-not-found-message]').should('have.text', 'Node not found'); - }); - }); - it('go back to the root node', () => { cy.visit(`/nodes/${leafNodeUuid}`, { onBeforeLoad(win) { @@ -163,6 +155,33 @@ describe('Solution Navigator', () => { cy.url().should('include', `/nodes/${rootNodeUuid}`); }); + it('searches for a node', () => { + cy.visit('/', { + onBeforeLoad(win) { + win.localStorage.setItem('NodeStore', JSON.stringify(initialState)); + }, + }); + + cy.get('[data-test-search-input]').type('my content'); + cy.get('[data-test-node-item]').should('have.length', 2); + + cy.get('[data-test-search-input]').clear(); + + cy.get('[data-test-search-input]').type('my content level 1'); + cy.get('[data-test-node-item]').should('have.length', 1); + + cy.get('[data-test-node-item]').eq(0).click(); + cy.url().should('include', `/nodes/${leafNodeUuid}`); + }); + + context('when the node does not exist', () => { + it('displays a not found message', () => { + cy.visit('/nodes/62090d47-3104-4d50-b384-54728a0208dd'); + + cy.get('[data-test-node-not-found-message]').should('have.text', 'Node not found'); + }); + }); + context('when a child node does not exist', () => { it('removes the reference from the parent node', () => { const customInitialState = { diff --git a/specs/stores/NodeStore.spec.js b/specs/stores/NodeStore.spec.js index 9b84dc1..ea5a208 100644 --- a/specs/stores/NodeStore.spec.js +++ b/specs/stores/NodeStore.spec.js @@ -8,7 +8,7 @@ describe('NodeStore', () => { setActivePinia( createTestingPinia({ initialState: { - NodeStore: { nodes }, + NodeStore: { nodes, searchQuery: '' }, }, createSpy: vi.fn, stubActions: false, @@ -20,6 +20,7 @@ describe('NodeStore', () => { setupPinia(); const nodeStore = useNodeStore(); expect(nodeStore.nodes.length).toBe(0); + expect(nodeStore.searchQuery).toEqual(''); }); describe('addNode', () => { @@ -147,6 +148,42 @@ describe('NodeStore', () => { expect(nodeStore.nodesList.length).toBe(2); expect(nodeStore.nodesList.map((node) => node.parentNode)).toEqual([null, null]); }); + + it('filters nodes by search query', () => { + const oldNode = '2022-06-16T21:59:54.858Z'; + const newNode = '2023-06-16T21:59:54.858Z'; + + const initialState = [ + { + uuid: '2947d509-ba89-4be5-a6c2-4c6cf546f6ed', + title: 'example', + content: 'my content', + resolved: false, + parentNode: null, + childNodes: [], + pomodoroCount: 0, + createdAt: oldNode, + updatedAt: oldNode, + }, + { + uuid: '8881bc3f-7497-4440-9f53-7781b7f6bade', + title: 'example 2', + content: 'my content 2', + resolved: false, + parentNode: null, + childNodes: [], + pomodoroCount: 0, + createdAt: newNode, + updatedAt: newNode, + }, + ]; + + setupPinia(initialState); + const nodeStore = useNodeStore(); + nodeStore.searchQuery = 'my content 2'; + + expect(nodeStore.nodesList.length).toBe(1); + }); }); describe('findNode', () => { diff --git a/src/stores/NodeStore.js b/src/stores/NodeStore.js index 5e9b7e1..3825266 100644 --- a/src/stores/NodeStore.js +++ b/src/stores/NodeStore.js @@ -7,11 +7,19 @@ export const useNodeStore = defineStore( () => { // state const nodes = ref([]); + const searchQuery = ref(''); // getters const nodesList = computed(() => { - return nodes.value - .filter((n) => !n.parentNode) + const baseList = + searchQuery.value === '' ? nodes.value.filter((n) => !n.parentNode) : nodes.value; + + return baseList + .filter( + (n) => + n.title.toLowerCase().includes(searchQuery.value.toLowerCase()) || + n.content.toLowerCase().includes(searchQuery.value.toLowerCase()), + ) .sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)); }); @@ -110,6 +118,7 @@ export const useNodeStore = defineStore( return { nodes, + searchQuery, nodesList, addNode, addChildNode, diff --git a/src/views/HomeIndex.vue b/src/views/HomeIndex.vue index b3ccd32..d99aee9 100644 --- a/src/views/HomeIndex.vue +++ b/src/views/HomeIndex.vue @@ -3,6 +3,8 @@ import { ref, computed } from 'vue'; import Button from 'primevue/button'; import NodesArbor from '@/components/NodesArbor.vue'; import ConfirmDialog from 'primevue/confirmdialog'; +import InputText from 'primevue/inputtext'; +import InputGroup from 'primevue/inputgroup'; import { useRouter, useRoute } from 'vue-router'; import { useNodeStore } from '@/stores/NodeStore'; import { useConfirm } from 'primevue/useconfirm'; @@ -13,7 +15,7 @@ const route = useRoute(); const isSidePanelVisible = ref(true); const { addNode, deleteNode } = useNodeStore(); const confirm = useConfirm(); -const { nodes } = storeToRefs(useNodeStore()); +const { nodes, searchQuery } = storeToRefs(useNodeStore()); const handleAddNode = () => { const uuid = addNode(); @@ -56,6 +58,16 @@ const downloadNodesLink = computed(() => {
+ + +