import { reactive } from 'vue'; import { flushPromises, mount, shallowMount } from '@vue/test-utils'; import { RcButton } from '@components/RcButton'; import NovncConsoleCustomKeys from '../NovncConsoleCustomKeys.vue'; import { SHORTCUTS_PREFERENCE, SHORTCUT_NAMES_PREFERENCE } from '../shortcuts'; const key = (value, code) => ({ key: value, code }); const CTRL_F2 = [key('Control', 'ControlLeft'), key('F2', 'F2')]; const CTRL_SPACE = [key('Control', 'ControlLeft'), key(' ', 'Space')]; const ALT_T = [key('Alt', 'AltLeft'), key('t', 'KeyT')]; // `rendered` mounts the buttons and the recorder for real, in the document, so the focus can be checked. const mountKeys = (saved = [], { rendered = false } = {}) => { // Reactive like the store's, so the list follows what is saved. const preference = reactive({ data: { [SHORTCUTS_PREFERENCE]: JSON.stringify(saved) }, save: jest.fn().mockResolvedValue(), }); const wrapper = (rendered ? mount : shallowMount)(NovncConsoleCustomKeys, { props: { show: true }, attachTo: rendered ? document.body : undefined, global: { mocks: { $store: { getters: { 'management/all': () => [preference] } } }, stubs: { RcModal: { template: '
' }, Banner: { template: '
' }, LabeledInput: true }, }, }); return { wrapper, preference }; }; const listed = (wrapper) => wrapper.findAll('[data-testid="vnc-custom-shortcut"]'); const removeShortcut = async(wrapper, index) => { listed(wrapper)[index].findComponent(RcButton).vm.$emit('click'); await flushPromises(); }; const savedKeys = (preference) => JSON.parse(preference.data[SHORTCUTS_PREFERENCE]); describe('component: NovncConsoleCustomKeys', () => { it('should offer to remove every saved shortcut', () => { const { wrapper } = mountKeys([CTRL_F2, ALT_T]); expect(listed(wrapper).map((item) => item.findComponent(RcButton).exists())).toStrictEqual([true, true]); }); it('should save the list without the shortcut that was removed', async() => { const { wrapper, preference } = mountKeys([CTRL_F2, ALT_T]); await removeShortcut(wrapper, 0); expect(preference.save).toHaveBeenCalledTimes(1); expect(savedKeys(preference)).toStrictEqual([ALT_T]); expect(listed(wrapper)).toHaveLength(1); }); it('should remove a shortcut with the Space key in it', async() => { const { wrapper, preference } = mountKeys([CTRL_SPACE, ALT_T]); await removeShortcut(wrapper, 0); expect(savedKeys(preference)).toStrictEqual([ALT_T]); }); it('should keep the shortcut when removing it cannot be saved, and say so in the dialog', async() => { const { wrapper, preference } = mountKeys([CTRL_F2, ALT_T]); preference.save.mockRejectedValue(new Error('forbidden')); await removeShortcut(wrapper, 0); expect(savedKeys(preference)).toStrictEqual([CTRL_F2, ALT_T]); expect(listed(wrapper)).toHaveLength(2); expect(wrapper.find('[data-testid="vnc-custom-keys-error"]').text()).toBe('%harvester.virtualMachine.detail.console.customKeys.saveError%: forbidden'); await wrapper.setProps({ show: false }); expect(wrapper.find('[data-testid="vnc-custom-keys-error"]').exists()).toBe(false); }); it('should not start a second save while one is running', async() => { const { wrapper, preference } = mountKeys([CTRL_F2, ALT_T]); wrapper.vm.remove(0); wrapper.vm.remove(1); await flushPromises(); expect(preference.save).toHaveBeenCalledTimes(1); }); it('should keep the focus on a delete button after a removal, and hand it to the recorder once the list is empty', async() => { const { wrapper } = mountKeys([CTRL_F2, ALT_T], { rendered: true }); const deleteButtons = () => wrapper.findAll('[data-testid="vnc-custom-shortcut"] button'); deleteButtons()[1].element.focus(); await deleteButtons()[1].trigger('click'); await flushPromises(); expect(document.activeElement).toBe(deleteButtons()[0].element); await deleteButtons()[0].trigger('click'); await flushPromises(); expect(document.activeElement).toBe(wrapper.find('[data-testid="vnc-key-recorder"]').element); wrapper.unmount(); }); it('should give the recorder the focus after a shortcut is added', async() => { const { wrapper } = mountKeys([CTRL_F2], { rendered: true }); wrapper.vm.keys = ALT_T; await wrapper.vm.$nextTick(); wrapper.find('[data-testid="vnc-add-shortcut"]').element.focus(); await wrapper.find('[data-testid="vnc-add-shortcut"]').trigger('click'); await flushPromises(); expect(document.activeElement).toBe(wrapper.find('[data-testid="vnc-key-recorder"]').element); wrapper.unmount(); }); it('should empty the form as soon as a shortcut is added, so it does not say it is already saved while it saves', async() => { const { wrapper, preference } = mountKeys([CTRL_F2]); preference.save.mockReturnValue(new Promise(() => {})); wrapper.vm.keys = ALT_T; wrapper.vm.add(); await wrapper.vm.$nextTick(); expect(listed(wrapper)).toHaveLength(2); expect(wrapper.vm.keys).toStrictEqual([]); expect(wrapper.find('.custom-keys__error').exists()).toBe(false); }); it('should put the shortcut back in the form when it cannot be saved', async() => { const { wrapper, preference } = mountKeys([CTRL_F2]); preference.save.mockRejectedValue(new Error('forbidden')); wrapper.vm.keys = ALT_T; wrapper.vm.name = 'Task manager'; await wrapper.vm.add(); expect(listed(wrapper)).toHaveLength(1); expect(wrapper.vm.keys).toStrictEqual(ALT_T); expect(wrapper.vm.name).toBe('Task manager'); }); it('should save a new shortcut straight away, with its name', async() => { const { wrapper, preference } = mountKeys([CTRL_F2]); wrapper.vm.keys = ALT_T; wrapper.vm.name = ' Task manager '; await wrapper.vm.add(); expect(savedKeys(preference)).toStrictEqual([CTRL_F2, ALT_T]); expect(JSON.parse(preference.data[SHORTCUT_NAMES_PREFERENCE])).toStrictEqual({ 'AltLeft+KeyT': 'Task manager' }); expect(wrapper.vm.keys).toStrictEqual([]); }); });