From f0f96ac6635561169a1c1994b1df09a974c56b38 Mon Sep 17 00:00:00 2001 From: lana-k Date: Sun, 5 Oct 2025 20:59:34 +0200 Subject: [PATCH] tests --- tests/App.spec.js | 35 +- tests/lib/tab.spec.js | 5 +- tests/store/actions.spec.js | 150 ++++++-- tests/store/mutations.spec.js | 5 +- tests/views/MainView/MainMenu.spec.js | 516 +++++++++++++++++++++++++- 5 files changed, 665 insertions(+), 46 deletions(-) diff --git a/tests/App.spec.js b/tests/App.spec.js index c16dcea..76f2f60 100644 --- a/tests/App.spec.js +++ b/tests/App.spec.js @@ -2,7 +2,7 @@ import { expect } from 'chai' import sinon from 'sinon' import { shallowMount } from '@vue/test-utils' import { createStore } from 'vuex' -import App from '@/App' +import App from '@/App.vue' import storedInquiries from '@/lib/storedInquiries' import mutations from '@/store/mutations' import { nextTick } from 'vue' @@ -59,4 +59,37 @@ describe('App.vue', () => { { id: 3, name: 'bar' } ]) }) + + it('Updates store when inquirires change in local storage', async () => { + sinon.stub(storedInquiries, 'getStoredInquiries').returns([ + { id: 1, name: 'foo' }, + { id: 2, name: 'baz' }, + { id: 3, name: 'bar' } + ]) + + const state = { + predefinedInquiries: [], + inquiries: [] + } + const store = createStore({ state, mutations }) + shallowMount(App, { + global: { stubs: ['router-view'], plugins: [store] } + }) + + expect(state.inquiries).to.eql([ + { id: 1, name: 'foo' }, + { id: 2, name: 'baz' }, + { id: 3, name: 'bar' } + ]) + + storedInquiries.getStoredInquiries.returns([ + { id: 1, name: 'foo' }, + { id: 3, name: 'bar' } + ]) + window.dispatchEvent(new StorageEvent('storage', { key: 'myInquiries' })) + expect(state.inquiries).to.eql([ + { id: 1, name: 'foo' }, + { id: 3, name: 'bar' } + ]) + }) }) diff --git a/tests/lib/tab.spec.js b/tests/lib/tab.spec.js index 0caed9f..c77e4c5 100644 --- a/tests/lib/tab.spec.js +++ b/tests/lib/tab.spec.js @@ -15,6 +15,7 @@ describe('tab.js', () => { query: undefined, viewOptions: undefined, isPredefined: undefined, + updatedAt: undefined, viewType: 'chart', result: null, isGettingResults: false, @@ -42,7 +43,8 @@ describe('tab.js', () => { viewType: 'pivot', viewOptions: 'this is view options object', name: 'Foo inquiry', - createdAt: '2022-12-05T18:30:30' + createdAt: '2022-12-05T18:30:30', + updatedAt: '2022-12-06T18:30:30' } const newTab = new Tab(state, inquiry) @@ -53,6 +55,7 @@ describe('tab.js', () => { query: 'SELECT * from foo', viewOptions: 'this is view options object', isPredefined: undefined, + updatedAt: '2022-12-06T18:30:30', viewType: 'pivot', result: null, isGettingResults: false, diff --git a/tests/store/actions.spec.js b/tests/store/actions.spec.js index 8dea360..a61c6af 100644 --- a/tests/store/actions.spec.js +++ b/tests/store/actions.spec.js @@ -19,7 +19,8 @@ describe('actions', () => { tempName: 'Untitled', viewType: 'chart', viewOptions: undefined, - isSaved: false + isSaved: false, + updatedAt: undefined }) expect(state.untitledLastIndex).to.equal(1) @@ -30,7 +31,8 @@ describe('actions', () => { tempName: 'Untitled 1', viewType: 'chart', viewOptions: undefined, - isSaved: false + isSaved: false, + updatedAt: undefined }) expect(state.untitledLastIndex).to.equal(2) }) @@ -40,16 +42,16 @@ describe('actions', () => { tabs: [], untitledLastIndex: 0 } - const tab = { + const inquiry = { id: 1, name: 'test', query: 'SELECT * from foo', viewType: 'chart', viewOptions: 'an object with view options', - isSaved: true + updatedAt: '2025-05-16T20:15:00Z' } - await addTab({ state }, tab) - expect(state.tabs[0]).to.include(tab) + await addTab({ state }, inquiry) + expect(state.tabs[0]).to.include(inquiry) expect(state.untitledLastIndex).to.equal(0) }) @@ -172,16 +174,20 @@ describe('actions', () => { expect(value.viewOptions).to.eql(['chart']) expect(value).to.have.property('createdAt') expect(new Date(value.createdAt)).within(now, nowPlusMinute) + expect(new Date(value.updatedAt)).within(now, nowPlusMinute) expect(state.inquiries).to.eql([value]) }) - it('save updates existing inquiry in the storage', async () => { + it('saveInquiry updates existing inquiry in the storage', async () => { + const now = new Date() + const nowPlusMinute = new Date(now.getTime() + 60 * 1000) const tab = { id: 1, query: 'select * from foo', viewType: 'chart', viewOptions: [], - name: null, + name: 'foo', + updatedAt: '2025-05-16T20:15:00Z', dataView: { getOptionsForSave() { return ['chart'] @@ -190,33 +196,34 @@ describe('actions', () => { } const state = { - inquiries: [], + inquiries: [ + { + id: 1, + query: 'select * from foo', + viewType: 'chart', + viewOptions: [], + name: 'foo', + createdAt: '2025-05-15T16:30:00Z', + updatedAt: '2025-05-16T20:15:00Z' + } + ], tabs: [tab] } - const first = await saveInquiry( - { state }, - { - inquiryTab: tab, - newName: 'foo' - } - ) - - tab.name = 'foo' tab.query = 'select * from bar' - tab.id = first.id - await saveInquiry({ state }, { inquiryTab: tab }) + await saveInquiry({ state }, { inquiryTab: tab, newName: '' }) const inquiries = state.inquiries - const second = inquiries[0] + const updatedTab = inquiries[0] expect(inquiries).has.lengthOf(1) - expect(second.id).to.equal(first.id) - expect(second.name).to.equal(first.name) - expect(second.query).to.equal(tab.query) - expect(second.viewOptions).to.eql(['chart']) - expect(second.createdAt).to.equal(first.createdAt) + expect(updatedTab.id).to.equal(updatedTab.id) + expect(updatedTab.name).to.equal(updatedTab.name) + expect(updatedTab.query).to.equal(tab.query) + expect(updatedTab.viewOptions).to.eql(['chart']) + expect(updatedTab.createdAt).to.equal('2025-05-15T16:30:00Z') + expect(new Date(updatedTab.updatedAt)).to.be.within(now, nowPlusMinute) }) - it("save adds a new inquiry with new id if it's based on predefined inquiry", async () => { + it("saveInquiry adds a new inquiry with new id if it's based on predefined inquiry", async () => { const now = new Date() const nowPlusMinute = new Date(now.getTime() + 60 * 1000) const tab = { @@ -252,6 +259,95 @@ describe('actions', () => { expect(inquiries[0].name).to.equal('foo') expect(inquiries[0].query).to.equal(tab.query) expect(inquiries[0].viewOptions).to.eql(['chart']) + expect(new Date(inquiries[0].updatedAt)).to.be.within(now, nowPlusMinute) expect(new Date(inquiries[0].createdAt)).to.be.within(now, nowPlusMinute) }) + + it('saveInquiry adds new inquiry if newName is provided', async () => { + const now = new Date() + const nowPlusMinute = new Date(now.getTime() + 60 * 1000) + + const tab = { + id: 1, + query: 'select * from foo', + viewType: 'chart', + viewOptions: [], + name: 'foo', + updatedAt: '2025-05-16T20:15:00Z', + dataView: { + getOptionsForSave() { + return ['chart'] + } + } + } + const inquiry = { + id: 1, + query: 'select * from foo', + viewType: 'chart', + viewOptions: [], + name: 'foo', + createdAt: '2025-05-15T16:30:00Z', + updatedAt: '2025-05-16T20:15:00Z' + } + const state = { + inquiries: [inquiry], + tabs: [tab] + } + + const value = await saveInquiry( + { state }, + { + inquiryTab: tab, + newName: 'foo_new' + } + ) + expect(value.id).not.to.equal(tab.id) + expect(value.name).to.equal('foo_new') + expect(value.query).to.equal(tab.query) + expect(value.viewOptions).to.eql(['chart']) + expect(value).to.have.property('createdAt') + expect(new Date(value.createdAt)).within(now, nowPlusMinute) + expect(new Date(value.updatedAt)).within(now, nowPlusMinute) + expect(state.inquiries).to.eql([inquiry, value]) + }) + + it('saveInquiry adds new inquiry if the inquiry is not in the storeage anymore', async () => { + const now = new Date() + const nowPlusMinute = new Date(now.getTime() + 60 * 1000) + + const tab = { + id: 1, + query: 'select * from foo', + viewType: 'chart', + viewOptions: [], + name: 'foo', + updatedAt: '2025-05-16T20:15:00Z', + dataView: { + getOptionsForSave() { + return ['chart'] + } + } + } + + const state = { + inquiries: [], + tabs: [tab] + } + + const value = await saveInquiry( + { state }, + { + inquiryTab: tab, + newName: '' + } + ) + expect(value.id).to.equal(tab.id) + expect(value.name).to.equal('foo') + expect(value.query).to.equal(tab.query) + expect(value.viewOptions).to.eql(['chart']) + expect(value).to.have.property('createdAt') + expect(new Date(value.createdAt)).within(now, nowPlusMinute) + expect(new Date(value.updatedAt)).within(now, nowPlusMinute) + expect(state.inquiries).to.eql([value]) + }) }) diff --git a/tests/store/mutations.spec.js b/tests/store/mutations.spec.js index d0e7313..bc532f4 100644 --- a/tests/store/mutations.spec.js +++ b/tests/store/mutations.spec.js @@ -34,7 +34,8 @@ describe('mutations', () => { viewType: 'chart', viewOptions: { here_are: 'chart settings' }, isSaved: false, - isPredefined: false + isPredefined: false, + updatedAt: '2025-05-15T15:30:00Z' } const newValues = { @@ -43,6 +44,7 @@ describe('mutations', () => { query: 'SELECT * from bar', viewType: 'pivot', viewOptions: { here_are: 'pivot settings' }, + updatedAt: '2025-05-15T16:30:00Z', isSaved: true } @@ -58,6 +60,7 @@ describe('mutations', () => { query: 'SELECT * from bar', viewType: 'pivot', viewOptions: { here_are: 'pivot settings' }, + updatedAt: '2025-05-15T16:30:00Z', isSaved: true }) }) diff --git a/tests/views/MainView/MainMenu.spec.js b/tests/views/MainView/MainMenu.spec.js index aac67e3..586b24d 100644 --- a/tests/views/MainView/MainMenu.spec.js +++ b/tests/views/MainView/MainMenu.spec.js @@ -26,7 +26,7 @@ describe('MainMenu.vue', () => { wrapper.unmount() }) - it('Create and Save are visible only on /workspace page', async () => { + it('Create, Save and Save as are visible only on /workspace page', async () => { const state = { currentTab: { query: '', execute: sinon.stub() }, tabs: [{}], @@ -45,6 +45,8 @@ describe('MainMenu.vue', () => { }) expect(wrapper.find('#save-btn').exists()).to.equal(true) expect(wrapper.find('#save-btn').isVisible()).to.equal(true) + expect(wrapper.find('#save-as-btn').exists()).to.equal(true) + expect(wrapper.find('#save-as-btn').isVisible()).to.equal(true) expect(wrapper.find('#create-btn').exists()).to.equal(true) expect(wrapper.find('#create-btn').isVisible()).to.equal(true) wrapper.unmount() @@ -65,7 +67,7 @@ describe('MainMenu.vue', () => { expect(wrapper.find('#create-btn').isVisible()).to.equal(true) }) - it('Save is not visible if there is no tabs', () => { + it('Save and Save as are not visible if there is no tabs', () => { const state = { currentTab: null, tabs: [], @@ -83,6 +85,8 @@ describe('MainMenu.vue', () => { }) expect(wrapper.find('#save-btn').exists()).to.equal(true) expect(wrapper.find('#save-btn').isVisible()).to.equal(false) + expect(wrapper.find('#save-as-btn').exists()).to.equal(true) + expect(wrapper.find('#save-as-btn').isVisible()).to.equal(false) expect(wrapper.find('#create-btn').exists()).to.equal(true) expect(wrapper.find('#create-btn').isVisible()).to.equal(true) }) @@ -111,10 +115,12 @@ describe('MainMenu.vue', () => { }) const vm = wrapper.vm expect(wrapper.find('#save-btn').element.disabled).to.equal(false) + expect(wrapper.find('#save-as-btn').element.disabled).to.equal(false) store.state.tabs[0].isSaved = true await vm.$nextTick() expect(wrapper.find('#save-btn').element.disabled).to.equal(true) + expect(wrapper.find('#save-as-btn').element.disabled).to.equal(false) }) it('Creates a tab', async () => { @@ -379,7 +385,70 @@ describe('MainMenu.vue', () => { expect(wrapper.vm.onSave.calledTwice).to.equal(true) }) - it('Saves the inquiry when no need the new name', async () => { + it('Ctrl Shift S calls onSaveAs if route path is /workspace', async () => { + const tab = { + query: 'SELECT * FROM foo', + execute: sinon.stub(), + isSaved: false + } + const state = { + currentTab: tab, + tabs: [tab], + db: {} + } + const store = createStore({ state }) + const $route = { path: '/workspace' } + + wrapper = shallowMount(MainMenu, { + global: { + mocks: { $route }, + stubs: ['router-link'], + plugins: [store] + } + }) + sinon.stub(wrapper.vm, 'onSaveAs') + + const ctrlS = new KeyboardEvent('keydown', { + key: 'S', + ctrlKey: true, + shiftKey: true + }) + const metaS = new KeyboardEvent('keydown', { + key: 'S', + metaKey: true, + shiftKey: true + }) + // tab is unsaved and route is /workspace + document.dispatchEvent(ctrlS) + expect(wrapper.vm.onSaveAs.calledOnce).to.equal(true) + document.dispatchEvent(metaS) + expect(wrapper.vm.onSaveAs.calledTwice).to.equal(true) + + // tab is saved and route is /workspace + store.state.tabs[0].isSaved = true + document.dispatchEvent(ctrlS) + expect(wrapper.vm.onSaveAs.calledThrice).to.equal(true) + document.dispatchEvent(metaS) + expect(wrapper.vm.onSaveAs.callCount).to.equal(4) + + // tab is unsaved and route is not /workspace + wrapper.vm.$route.path = '/inquiries' + store.state.tabs[0].isSaved = false + document.dispatchEvent(ctrlS) + expect(wrapper.vm.onSaveAs.callCount).to.equal(4) + document.dispatchEvent(metaS) + expect(wrapper.vm.onSaveAs.callCount).to.equal(4) + + // tab is saved and route is not /workspace + wrapper.vm.$route.path = '/inquiries' + store.state.tabs[0].isSaved = true + document.dispatchEvent(ctrlS) + expect(wrapper.vm.onSaveAs.callCount).to.equal(4) + document.dispatchEvent(metaS) + expect(wrapper.vm.onSaveAs.callCount).to.equal(4) + }) + + it('Saves the inquiry when no need the new name and no update conflict', async () => { const tab = { id: 1, name: 'foo', @@ -395,7 +464,8 @@ describe('MainMenu.vue', () => { id: 1, name: 'foo', query: 'SELECT * FROM foo', - updatedAt: '2025-05-15T15:30:00Z' + updatedAt: '2025-05-15T15:30:00Z', + createdAt: '2025-05-14T15:30:00Z' } ], tabs: [tab], @@ -410,7 +480,8 @@ describe('MainMenu.vue', () => { id: 1, query: 'SELECT * FROM foo', viewType: 'chart', - viewOptions: [] + viewOptions: [], + updatedAt: '2025-05-16T15:30:00Z' }) } const store = createStore({ state, mutations, actions }) @@ -455,7 +526,8 @@ describe('MainMenu.vue', () => { query: 'SELECT * FROM foo', viewType: 'chart', viewOptions: [], - isSaved: true + isSaved: true, + updatedAt: '2025-05-16T15:30:00Z' } }) ) @@ -465,6 +537,303 @@ describe('MainMenu.vue', () => { expect(eventBus.$emit.calledOnceWith('inquirySaved')).to.equal(true) }) + it('Inquiry conflict: overwrite', async () => { + const tab = { + id: 1, + name: 'foo', + query: 'SELECT * FROM foo', + updatedAt: '2025-05-15T15:30:00Z', + execute: sinon.stub(), + isSaved: false + } + const state = { + currentTab: tab, + inquiries: [ + { + id: 1, + name: 'foo', + query: 'SELECT * FROM bar', + updatedAt: '2025-05-15T16:30:00Z', + createdAt: '2025-05-14T15:30:00Z' + } + ], + tabs: [tab], + db: {} + } + const mutations = { + updateTab: sinon.stub() + } + const actions = { + saveInquiry: sinon.stub().returns({ + name: 'foo', + id: 1, + query: 'SELECT * FROM foo', + viewType: 'chart', + viewOptions: [], + updatedAt: '2025-05-16T17:30:00Z', + createdAt: '2025-05-14T15:30:00Z' + }) + } + const store = createStore({ state, mutations, actions }) + const $route = { path: '/workspace' } + sinon.stub(storedInquiries, 'isTabNeedName').returns(false) + + wrapper = mount(MainMenu, { + attachTo: document.body, + global: { + mocks: { $route }, + stubs: { + 'router-link': true, + 'app-diagnostic-info': true, + teleport: true, + transition: false + }, + plugins: [store] + } + }) + + await wrapper.find('#save-btn').trigger('click') + + // check that the conflict dialog is open + expect(wrapper.find('.dialog.vfm').exists()).to.equal(true) + expect(wrapper.find('.dialog.vfm .dialog-header').text()).to.contain( + 'Inquiry saving conflict' + ) + + // find Overwrite in the dialog and click + await wrapper + .findAll('.dialog-buttons-container button') + .find(button => button.text() === 'Overwrite') + .trigger('click') + + await nextTick() + + // check that the dialog is closed + await clock.tick(100) + expect(wrapper.find('.dialog.vfm').exists()).to.equal(false) + + // check that the inquiry was saved via saveInquiry (newName='') + expect(actions.saveInquiry.calledOnce).to.equal(true) + expect(actions.saveInquiry.args[0][1]).to.eql({ + inquiryTab: state.currentTab, + newName: '' + }) + + // check that the tab was updated + expect( + mutations.updateTab.calledOnceWith( + state, + sinon.match({ + tab, + newValues: { + name: 'foo', + id: 1, + query: 'SELECT * FROM foo', + viewType: 'chart', + viewOptions: [], + isSaved: true, + updatedAt: '2025-05-16T17:30:00Z' + } + }) + ) + ).to.equal(true) + + // check that 'inquirySaved' event was triggered on eventBus + expect(eventBus.$emit.calledOnceWith('inquirySaved')).to.equal(true) + }) + + it('Inquiry conflict: save as new', async () => { + const tab = { + id: 1, + name: 'foo', + query: 'SELECT * FROM foo', + updatedAt: '2025-05-15T15:30:00Z', + execute: sinon.stub(), + isSaved: false + } + const state = { + currentTab: tab, + inquiries: [ + { + id: 1, + name: 'foo', + query: 'SELECT * FROM bar', + updatedAt: '2025-05-15T16:30:00Z', + createdAt: '2025-05-14T15:30:00Z' + } + ], + tabs: [tab], + db: {} + } + const mutations = { + updateTab: sinon.stub() + } + const actions = { + saveInquiry: sinon.stub().returns({ + name: 'foo_new', + id: 2, + query: 'SELECT * FROM foo', + viewType: 'chart', + viewOptions: [], + updatedAt: '2025-05-16T17:30:00Z', + createdAt: '2025-05-16T17:30:00Z' + }) + } + const store = createStore({ state, mutations, actions }) + const $route = { path: '/workspace' } + sinon.stub(storedInquiries, 'isTabNeedName').returns(false) + + wrapper = mount(MainMenu, { + attachTo: document.body, + global: { + mocks: { $route }, + stubs: { + 'router-link': true, + 'app-diagnostic-info': true, + teleport: true, + transition: false + }, + plugins: [store] + } + }) + + await wrapper.find('#save-btn').trigger('click') + + // check that the conflict dialog is open + expect(wrapper.find('.dialog.vfm').exists()).to.equal(true) + expect(wrapper.find('.dialog.vfm .dialog-header').text()).to.contain( + 'Inquiry saving conflict' + ) + + // find "Save as new" in the dialog and click + await wrapper + .findAll('.dialog-buttons-container button') + .find(button => button.text() === 'Save as new') + .trigger('click') + + await nextTick() + await clock.tick(100) + + // enter the new name + await wrapper.find('.dialog-body input').setValue('foo_new') + + // find Save in the dialog and click + await wrapper + .findAll('.dialog-buttons-container button') + .find(button => button.text() === 'Save') + .trigger('click') + + await nextTick() + + // check that the dialog is closed + await clock.tick(100) + expect(wrapper.find('.dialog.vfm').exists()).to.equal(false) + + // check that the inquiry was saved via saveInquiry (newName='foo_new') + expect(actions.saveInquiry.calledOnce).to.equal(true) + expect(actions.saveInquiry.args[0][1]).to.eql({ + inquiryTab: state.currentTab, + newName: 'foo_new' + }) + + // check that the tab was updated + expect( + mutations.updateTab.calledOnceWith( + state, + sinon.match({ + tab, + newValues: { + name: 'foo_new', + id: 2, + query: 'SELECT * FROM foo', + viewType: 'chart', + viewOptions: [], + isSaved: true, + updatedAt: '2025-05-16T17:30:00Z' + } + }) + ) + ).to.equal(true) + + // check that 'inquirySaved' event was triggered on eventBus + expect(eventBus.$emit.calledOnceWith('inquirySaved')).to.equal(true) + }) + + it('Inquiry conflict: cancel', async () => { + const tab = { + id: 1, + name: 'foo', + query: 'SELECT * FROM foo', + updatedAt: '2025-05-15T15:30:00Z', + execute: sinon.stub(), + isSaved: false + } + const state = { + currentTab: tab, + inquiries: [ + { + id: 1, + name: 'foo', + query: 'SELECT * FROM bar', + updatedAt: '2025-05-15T16:30:00Z', + createdAt: '2025-05-14T15:30:00Z' + } + ], + tabs: [tab], + db: {} + } + const mutations = { + updateTab: sinon.stub() + } + const actions = { + saveInquiry: sinon.stub() + } + const store = createStore({ state, mutations, actions }) + const $route = { path: '/workspace' } + sinon.stub(storedInquiries, 'isTabNeedName').returns(false) + + wrapper = mount(MainMenu, { + attachTo: document.body, + global: { + mocks: { $route }, + stubs: { + 'router-link': true, + 'app-diagnostic-info': true, + teleport: true, + transition: false + }, + plugins: [store] + } + }) + + await wrapper.find('#save-btn').trigger('click') + + // check that the conflict dialog is open + expect(wrapper.find('.dialog.vfm').exists()).to.equal(true) + expect(wrapper.find('.dialog.vfm .dialog-header').text()).to.contain( + 'Inquiry saving conflict' + ) + + // find Cancel in the dialog and click + await wrapper + .findAll('.dialog-buttons-container button') + .find(button => button.text() === 'Cancel') + .trigger('click') + + // check that the dialog is closed + await clock.tick(100) + expect(wrapper.find('.dialog.vfm').exists()).to.equal(false) + + // check that the inquiry was not saved via storedInquiries.save + expect(actions.saveInquiry.called).to.equal(false) + + // check that the tab was not updated + expect(mutations.updateTab.called).to.equal(false) + + // check that 'inquirySaved' event is not listened on eventBus + expect(eventBus.$off.calledOnceWith('inquirySaved')).to.equal(true) + }) + it('Shows en error when the new name is needed but not specifyied', async () => { const tab = { id: 1, @@ -472,7 +841,8 @@ describe('MainMenu.vue', () => { tempName: 'Untitled', query: 'SELECT * FROM foo', execute: sinon.stub(), - isSaved: false + isSaved: false, + updatedAt: '2025-05-15T15:30:00Z' } const state = { currentTab: tab, @@ -488,7 +858,8 @@ describe('MainMenu.vue', () => { id: 1, query: 'SELECT * FROM foo', viewType: 'chart', - viewOptions: [] + viewOptions: [], + updatedAt: '2025-05-16T15:30:00Z' }) } const store = createStore({ state, mutations, actions }) @@ -531,14 +902,15 @@ describe('MainMenu.vue', () => { expect(wrapper.find('.dialog.vfm').exists()).to.equal(true) }) - it('Saves the inquiry with a new name', async () => { + it('Saves the new inquiry with a new name', async () => { const tab = { id: 1, name: null, tempName: 'Untitled', query: 'SELECT * FROM foo', execute: sinon.stub(), - isSaved: false + isSaved: false, + updatedAt: undefined } const state = { currentTab: tab, @@ -551,10 +923,11 @@ describe('MainMenu.vue', () => { const actions = { saveInquiry: sinon.stub().returns({ name: 'foo', - id: 1, + id: 2, query: 'SELECT * FROM foo', viewType: 'chart', - viewOptions: [] + viewOptions: [], + updatedAt: '2025-05-15T15:30:00Z' }) } const store = createStore({ state, mutations, actions }) @@ -613,11 +986,12 @@ describe('MainMenu.vue', () => { tab, newValues: { name: 'foo', - id: 1, + id: 2, query: 'SELECT * FROM foo', viewType: 'chart', viewOptions: [], - isSaved: true + isSaved: true, + updatedAt: '2025-05-15T15:30:00Z' } }) ) @@ -659,7 +1033,8 @@ describe('MainMenu.vue', () => { id: 2, query: 'SELECT * FROM foo', viewType: 'chart', - viewOptions: [] + viewOptions: [], + updatedAt: '2025-05-15T15:30:00Z' }) } const store = createStore({ state, mutations, actions }) @@ -725,7 +1100,8 @@ describe('MainMenu.vue', () => { query: 'SELECT * FROM foo', viewType: 'chart', viewOptions: [], - isSaved: true + isSaved: true, + updatedAt: '2025-05-15T15:30:00Z' } }) ) @@ -818,4 +1194,112 @@ describe('MainMenu.vue', () => { // check that 'inquirySaved' event is not listened on eventBus expect(eventBus.$off.calledOnceWith('inquirySaved')).to.equal(true) }) + + it('Save the inquiry as new', async () => { + const tab = { + id: 1, + name: 'foo', + query: 'SELECT * FROM foo', + updatedAt: '2025-05-15T15:30:00Z', + execute: sinon.stub(), + isSaved: true + } + const state = { + currentTab: tab, + inquiries: [ + { + id: 1, + name: 'foo', + query: 'SELECT * FROM bar', + updatedAt: '2025-05-15T16:30:00Z', + createdAt: '2025-05-14T15:30:00Z' + } + ], + tabs: [tab], + db: {} + } + const mutations = { + updateTab: sinon.stub() + } + const actions = { + saveInquiry: sinon.stub().returns({ + name: 'foo_new', + id: 2, + query: 'SELECT * FROM foo', + viewType: 'chart', + viewOptions: [], + updatedAt: '2025-05-16T17:30:00Z', + createdAt: '2025-05-16T17:30:00Z' + }) + } + const store = createStore({ state, mutations, actions }) + const $route = { path: '/workspace' } + sinon.stub(storedInquiries, 'isTabNeedName').returns(false) + + wrapper = mount(MainMenu, { + attachTo: document.body, + global: { + mocks: { $route }, + stubs: { + 'router-link': true, + 'app-diagnostic-info': true, + teleport: true, + transition: false + }, + plugins: [store] + } + }) + + await wrapper.find('#save-as-btn').trigger('click') + + // check that Save dialog is open + expect(wrapper.find('.dialog.vfm').exists()).to.equal(true) + expect(wrapper.find('.dialog.vfm .dialog-header').text()).to.contain( + 'Save inquiry' + ) + + // enter the new name + await wrapper.find('.dialog-body input').setValue('foo_new') + + // find Save in the dialog and click + await wrapper + .findAll('.dialog-buttons-container button') + .find(button => button.text() === 'Save') + .trigger('click') + + await nextTick() + + // check that the dialog is closed + await clock.tick(100) + expect(wrapper.find('.dialog.vfm').exists()).to.equal(false) + + // check that the inquiry was saved via saveInquiry (newName='foo_new') + expect(actions.saveInquiry.calledOnce).to.equal(true) + expect(actions.saveInquiry.args[0][1]).to.eql({ + inquiryTab: state.currentTab, + newName: 'foo_new' + }) + + // check that the tab was updated + expect( + mutations.updateTab.calledOnceWith( + state, + sinon.match({ + tab, + newValues: { + name: 'foo_new', + id: 2, + query: 'SELECT * FROM foo', + viewType: 'chart', + viewOptions: [], + isSaved: true, + updatedAt: '2025-05-16T17:30:00Z' + } + }) + ) + ).to.equal(true) + + // check that 'inquirySaved' event was triggered on eventBus + expect(eventBus.$emit.calledOnceWith('inquirySaved')).to.equal(true) + }) })