diff options
Diffstat (limited to 'spec/frontend/ide/components/terminal')
5 files changed, 584 insertions, 0 deletions
diff --git a/spec/frontend/ide/components/terminal/empty_state_spec.js b/spec/frontend/ide/components/terminal/empty_state_spec.js new file mode 100644 index 00000000000..a3f2089608d --- /dev/null +++ b/spec/frontend/ide/components/terminal/empty_state_spec.js @@ -0,0 +1,107 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLoadingIcon } from '@gitlab/ui'; +import { TEST_HOST } from 'spec/test_constants'; +import TerminalEmptyState from '~/ide/components/terminal/empty_state.vue'; + +const TEST_HELP_PATH = `${TEST_HOST}/help/test`; +const TEST_PATH = `${TEST_HOST}/home.png`; +const TEST_HTML_MESSAGE = 'lorem <strong>ipsum</strong>'; + +describe('IDE TerminalEmptyState', () => { + let wrapper; + + const factory = (options = {}) => { + wrapper = shallowMount(TerminalEmptyState, { + ...options, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('does not show illustration, if no path specified', () => { + factory(); + + expect(wrapper.find('.svg-content').exists()).toBe(false); + }); + + it('shows illustration with path', () => { + factory({ + propsData: { + illustrationPath: TEST_PATH, + }, + }); + + const img = wrapper.find('.svg-content img'); + + expect(img.exists()).toBe(true); + expect(img.attributes('src')).toEqual(TEST_PATH); + }); + + it('when loading, shows loading icon', () => { + factory({ + propsData: { + isLoading: true, + }, + }); + + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + }); + + it('when not loading, does not show loading icon', () => { + factory({ + propsData: { + isLoading: false, + }, + }); + + expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + }); + + describe('when valid', () => { + let button; + + beforeEach(() => { + factory({ + propsData: { + isLoading: false, + isValid: true, + helpPath: TEST_HELP_PATH, + }, + }); + + button = wrapper.find('button'); + }); + + it('shows button', () => { + expect(button.text()).toEqual('Start Web Terminal'); + expect(button.attributes('disabled')).toBeFalsy(); + }); + + it('emits start when button is clicked', () => { + expect(wrapper.emitted().start).toBeFalsy(); + + button.trigger('click'); + + expect(wrapper.emitted().start).toHaveLength(1); + }); + + it('shows help path link', () => { + expect(wrapper.find('a').attributes('href')).toEqual(TEST_HELP_PATH); + }); + }); + + it('when not valid, shows disabled button and message', () => { + factory({ + propsData: { + isLoading: false, + isValid: false, + message: TEST_HTML_MESSAGE, + }, + }); + + expect(wrapper.find('button').attributes('disabled')).not.toBe(null); + expect(wrapper.find('.bs-callout').element.innerHTML).toEqual(TEST_HTML_MESSAGE); + }); +}); diff --git a/spec/frontend/ide/components/terminal/session_spec.js b/spec/frontend/ide/components/terminal/session_spec.js new file mode 100644 index 00000000000..2399446ed15 --- /dev/null +++ b/spec/frontend/ide/components/terminal/session_spec.js @@ -0,0 +1,96 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import TerminalSession from '~/ide/components/terminal/session.vue'; +import Terminal from '~/ide/components/terminal/terminal.vue'; +import { + STARTING, + PENDING, + RUNNING, + STOPPING, + STOPPED, +} from '~/ide/stores/modules/terminal/constants'; + +const TEST_TERMINAL_PATH = 'terminal/path'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('IDE TerminalSession', () => { + let wrapper; + let actions; + let state; + + const factory = (options = {}) => { + const store = new Vuex.Store({ + modules: { + terminal: { + namespaced: true, + actions, + state, + }, + }, + }); + + wrapper = shallowMount(TerminalSession, { + localVue, + store, + ...options, + }); + }; + + beforeEach(() => { + state = { + session: { status: RUNNING, terminalPath: TEST_TERMINAL_PATH }, + }; + actions = { + restartSession: jest.fn(), + stopSession: jest.fn(), + }; + }); + + it('is empty if session is falsey', () => { + state.session = null; + factory(); + + expect(wrapper.isEmpty()).toBe(true); + }); + + it('shows terminal', () => { + factory(); + + expect(wrapper.find(Terminal).props()).toEqual({ + terminalPath: TEST_TERMINAL_PATH, + status: RUNNING, + }); + }); + + [STARTING, PENDING, RUNNING].forEach(status => { + it(`show stop button when status is ${status}`, () => { + state.session = { status }; + factory(); + + const button = wrapper.find('button'); + button.trigger('click'); + + return wrapper.vm.$nextTick().then(() => { + expect(button.text()).toEqual('Stop Terminal'); + expect(actions.stopSession).toHaveBeenCalled(); + }); + }); + }); + + [STOPPING, STOPPED].forEach(status => { + it(`show stop button when status is ${status}`, () => { + state.session = { status }; + factory(); + + const button = wrapper.find('button'); + button.trigger('click'); + + return wrapper.vm.$nextTick().then(() => { + expect(button.text()).toEqual('Restart Terminal'); + expect(actions.restartSession).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/spec/frontend/ide/components/terminal/terminal_controls_spec.js b/spec/frontend/ide/components/terminal/terminal_controls_spec.js new file mode 100644 index 00000000000..6c2871abb46 --- /dev/null +++ b/spec/frontend/ide/components/terminal/terminal_controls_spec.js @@ -0,0 +1,65 @@ +import { shallowMount } from '@vue/test-utils'; +import TerminalControls from '~/ide/components/terminal/terminal_controls.vue'; +import ScrollButton from '~/ide/components/jobs/detail/scroll_button.vue'; + +describe('IDE TerminalControls', () => { + let wrapper; + let buttons; + + const factory = (options = {}) => { + wrapper = shallowMount(TerminalControls, { + ...options, + }); + + buttons = wrapper.findAll(ScrollButton); + }; + + it('shows an up and down scroll button', () => { + factory(); + + expect(buttons.wrappers.map(x => x.props())).toEqual([ + expect.objectContaining({ direction: 'up', disabled: true }), + expect.objectContaining({ direction: 'down', disabled: true }), + ]); + }); + + it('enables up button with prop', () => { + factory({ propsData: { canScrollUp: true } }); + + expect(buttons.at(0).props()).toEqual( + expect.objectContaining({ direction: 'up', disabled: false }), + ); + }); + + it('enables down button with prop', () => { + factory({ propsData: { canScrollDown: true } }); + + expect(buttons.at(1).props()).toEqual( + expect.objectContaining({ direction: 'down', disabled: false }), + ); + }); + + it('emits "scroll-up" when click up button', () => { + factory({ propsData: { canScrollUp: true } }); + + expect(wrapper.emittedByOrder()).toEqual([]); + + buttons.at(0).vm.$emit('click'); + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.emittedByOrder()).toEqual([{ name: 'scroll-up', args: [] }]); + }); + }); + + it('emits "scroll-down" when click down button', () => { + factory({ propsData: { canScrollDown: true } }); + + expect(wrapper.emittedByOrder()).toEqual([]); + + buttons.at(1).vm.$emit('click'); + + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.emittedByOrder()).toEqual([{ name: 'scroll-down', args: [] }]); + }); + }); +}); diff --git a/spec/frontend/ide/components/terminal/terminal_spec.js b/spec/frontend/ide/components/terminal/terminal_spec.js new file mode 100644 index 00000000000..3095288bb28 --- /dev/null +++ b/spec/frontend/ide/components/terminal/terminal_spec.js @@ -0,0 +1,225 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { GlLoadingIcon } from '@gitlab/ui'; +import Terminal from '~/ide/components/terminal/terminal.vue'; +import TerminalControls from '~/ide/components/terminal/terminal_controls.vue'; +import { + STARTING, + PENDING, + RUNNING, + STOPPING, + STOPPED, +} from '~/ide/stores/modules/terminal/constants'; +import GLTerminal from '~/terminal/terminal'; + +const TEST_TERMINAL_PATH = 'terminal/path'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +jest.mock('~/terminal/terminal', () => + jest.fn().mockImplementation(() => ({ + dispose: jest.fn(), + disable: jest.fn(), + addScrollListener: jest.fn(), + scrollToTop: jest.fn(), + scrollToBottom: jest.fn(), + })), +); + +describe('IDE Terminal', () => { + let wrapper; + let state; + + const factory = propsData => { + const store = new Vuex.Store({ + state, + mutations: { + set(prevState, newState) { + Object.assign(prevState, newState); + }, + }, + }); + + wrapper = shallowMount(localVue.extend(Terminal), { + propsData: { + status: RUNNING, + terminalPath: TEST_TERMINAL_PATH, + ...propsData, + }, + localVue, + store, + }); + }; + + beforeEach(() => { + state = { + panelResizing: false, + }; + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('loading text', () => { + [STARTING, PENDING].forEach(status => { + it(`shows when starting (${status})`, () => { + factory({ status }); + + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.find('.top-bar').text()).toBe('Starting...'); + }); + }); + + it(`shows when stopping`, () => { + factory({ status: STOPPING }); + + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.find('.top-bar').text()).toBe('Stopping...'); + }); + + [RUNNING, STOPPED].forEach(status => { + it('hides when not loading', () => { + factory({ status }); + + expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.find('.top-bar').text()).toBe(''); + }); + }); + }); + + describe('refs.terminal', () => { + it('has terminal path in data', () => { + factory(); + + expect(wrapper.vm.$refs.terminal.dataset.projectPath).toBe(TEST_TERMINAL_PATH); + }); + }); + + describe('terminal controls', () => { + beforeEach(() => { + factory(); + wrapper.vm.createTerminal(); + + return localVue.nextTick(); + }); + + it('is visible if terminal is created', () => { + expect(wrapper.find(TerminalControls).exists()).toBe(true); + }); + + it('scrolls glterminal on scroll-up', () => { + wrapper.find(TerminalControls).vm.$emit('scroll-up'); + + expect(wrapper.vm.glterminal.scrollToTop).toHaveBeenCalled(); + }); + + it('scrolls glterminal on scroll-down', () => { + wrapper.find(TerminalControls).vm.$emit('scroll-down'); + + expect(wrapper.vm.glterminal.scrollToBottom).toHaveBeenCalled(); + }); + + it('has props set', () => { + expect(wrapper.find(TerminalControls).props()).toEqual({ + canScrollUp: false, + canScrollDown: false, + }); + + wrapper.setData({ canScrollUp: true, canScrollDown: true }); + + return localVue.nextTick().then(() => { + expect(wrapper.find(TerminalControls).props()).toEqual({ + canScrollUp: true, + canScrollDown: true, + }); + }); + }); + }); + + describe('refresh', () => { + let createTerminal; + let stopTerminal; + + beforeEach(() => { + createTerminal = jest.fn().mockName('createTerminal'); + stopTerminal = jest.fn().mockName('stopTerminal'); + }); + + it('creates the terminal if running', () => { + factory({ status: RUNNING, terminalPath: TEST_TERMINAL_PATH }); + + wrapper.setMethods({ createTerminal }); + wrapper.vm.refresh(); + + expect(createTerminal).toHaveBeenCalled(); + }); + + it('stops the terminal if stopping', () => { + factory({ status: STOPPING }); + + wrapper.setMethods({ stopTerminal }); + wrapper.vm.refresh(); + + expect(stopTerminal).toHaveBeenCalled(); + }); + }); + + describe('createTerminal', () => { + beforeEach(() => { + factory(); + wrapper.vm.createTerminal(); + }); + + it('creates the terminal', () => { + expect(GLTerminal).toHaveBeenCalledWith(wrapper.vm.$refs.terminal); + expect(wrapper.vm.glterminal).toBeTruthy(); + }); + + describe('scroll listener', () => { + it('has been called', () => { + expect(wrapper.vm.glterminal.addScrollListener).toHaveBeenCalled(); + }); + + it('updates scroll data when called', () => { + expect(wrapper.vm.canScrollUp).toBe(false); + expect(wrapper.vm.canScrollDown).toBe(false); + + const listener = wrapper.vm.glterminal.addScrollListener.mock.calls[0][0]; + listener({ canScrollUp: true, canScrollDown: true }); + + expect(wrapper.vm.canScrollUp).toBe(true); + expect(wrapper.vm.canScrollDown).toBe(true); + }); + }); + }); + + describe('destroyTerminal', () => { + it('calls dispose', () => { + factory(); + wrapper.vm.createTerminal(); + const disposeSpy = wrapper.vm.glterminal.dispose; + + expect(disposeSpy).not.toHaveBeenCalled(); + + wrapper.vm.destroyTerminal(); + + expect(disposeSpy).toHaveBeenCalled(); + expect(wrapper.vm.glterminal).toBe(null); + }); + }); + + describe('stopTerminal', () => { + it('calls disable', () => { + factory(); + wrapper.vm.createTerminal(); + + expect(wrapper.vm.glterminal.disable).not.toHaveBeenCalled(); + + wrapper.vm.stopTerminal(); + + expect(wrapper.vm.glterminal.disable).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/ide/components/terminal/view_spec.js b/spec/frontend/ide/components/terminal/view_spec.js new file mode 100644 index 00000000000..eff200550da --- /dev/null +++ b/spec/frontend/ide/components/terminal/view_spec.js @@ -0,0 +1,91 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { TEST_HOST } from 'spec/test_constants'; +import TerminalEmptyState from '~/ide/components/terminal/empty_state.vue'; +import TerminalView from '~/ide/components/terminal/view.vue'; +import TerminalSession from '~/ide/components/terminal/session.vue'; + +const TEST_HELP_PATH = `${TEST_HOST}/help`; +const TEST_SVG_PATH = `${TEST_HOST}/illustration.svg`; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('IDE TerminalView', () => { + let state; + let actions; + let getters; + let wrapper; + + const factory = () => { + const store = new Vuex.Store({ + modules: { + terminal: { + namespaced: true, + state, + actions, + getters, + }, + }, + }); + + wrapper = shallowMount(TerminalView, { localVue, store }); + }; + + beforeEach(() => { + state = { + isShowSplash: true, + paths: { + webTerminalHelpPath: TEST_HELP_PATH, + webTerminalSvgPath: TEST_SVG_PATH, + }, + }; + + actions = { + hideSplash: jest.fn().mockName('hideSplash'), + startSession: jest.fn().mockName('startSession'), + }; + + getters = { + allCheck: () => ({ + isLoading: false, + isValid: false, + message: 'bad', + }), + }; + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders empty state', () => { + factory(); + + expect(wrapper.find(TerminalEmptyState).props()).toEqual({ + helpPath: TEST_HELP_PATH, + illustrationPath: TEST_SVG_PATH, + ...getters.allCheck(), + }); + }); + + it('hides splash and starts, when started', () => { + factory(); + + expect(actions.startSession).not.toHaveBeenCalled(); + expect(actions.hideSplash).not.toHaveBeenCalled(); + + wrapper.find(TerminalEmptyState).vm.$emit('start'); + + expect(actions.startSession).toHaveBeenCalled(); + expect(actions.hideSplash).toHaveBeenCalled(); + }); + + it('shows Web Terminal when started', () => { + state.isShowSplash = false; + factory(); + + expect(wrapper.find(TerminalEmptyState).exists()).toBe(false); + expect(wrapper.find(TerminalSession).exists()).toBe(true); + }); +}); |