From cfe4d2f29dcdcfad96ae7ba5a5eb822fbe46a9a7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 23 May 2018 11:44:47 +0100 Subject: [PATCH] added tab component --- app/assets/javascripts/ide/components/ide.vue | 6 +- .../index.vue => panes/right.vue} | 32 ++++++--- .../ide/components/pipelines/jobs.vue | 40 +++++++++++ .../pipelines.vue => pipelines/list.vue} | 31 +++++++-- app/assets/javascripts/ide/constants.js | 4 ++ app/assets/javascripts/ide/stores/actions.js | 4 ++ .../ide/stores/modules/pipelines/getters.js | 2 + .../ide/stores/modules/pipelines/mutations.js | 1 + .../javascripts/ide/stores/mutation_types.js | 2 + .../javascripts/ide/stores/mutations.js | 3 + app/assets/javascripts/ide/stores/state.js | 1 + .../vue_shared/components/tabs/tab.vue | 42 ++++++++++++ .../vue_shared/components/tabs/tabs.js | 62 +++++++++++++++++ .../vue_shared/components/tabs/tab_spec.js | 32 +++++++++ .../vue_shared/components/tabs/tabs_spec.js | 68 +++++++++++++++++++ 15 files changed, 314 insertions(+), 16 deletions(-) rename app/assets/javascripts/ide/components/{right_sidebar/index.vue => panes/right.vue} (60%) create mode 100644 app/assets/javascripts/ide/components/pipelines/jobs.vue rename app/assets/javascripts/ide/components/{right_sidebar/pipelines.vue => pipelines/list.vue} (54%) create mode 100644 app/assets/javascripts/vue_shared/components/tabs/tab.vue create mode 100644 app/assets/javascripts/vue_shared/components/tabs/tabs.js create mode 100644 spec/javascripts/vue_shared/components/tabs/tab_spec.js create mode 100644 spec/javascripts/vue_shared/components/tabs/tabs_spec.js diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index d61ed36a757..318e7aa5716 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -6,7 +6,7 @@ import RepoTabs from './repo_tabs.vue'; import IdeStatusBar from './ide_status_bar.vue'; import RepoEditor from './repo_editor.vue'; import FindFile from './file_finder/index.vue'; -import RightSidebar from './right_sidebar/index.vue'; +import RightPane from './panes/right.vue'; const originalStopCallback = Mousetrap.stopCallback; @@ -17,7 +17,7 @@ export default { IdeStatusBar, RepoEditor, FindFile, - RightSidebar, + RightPane, }, computed: { ...mapState([ @@ -125,7 +125,7 @@ export default { - diff --git a/app/assets/javascripts/ide/components/right_sidebar/index.vue b/app/assets/javascripts/ide/components/panes/right.vue similarity index 60% rename from app/assets/javascripts/ide/components/right_sidebar/index.vue rename to app/assets/javascripts/ide/components/panes/right.vue index 2417e3976aa..7ac79347225 100644 --- a/app/assets/javascripts/ide/components/right_sidebar/index.vue +++ b/app/assets/javascripts/ide/components/panes/right.vue @@ -1,7 +1,9 @@ @@ -18,25 +27,31 @@ export default {
-
- +
+ + +
@@ -55,6 +70,7 @@ export default { .ide-right-sidebar .multi-file-commit-panel-inner { width: 300px; + padding: 8px 16px; background-color: #fff; border-left: 1px solid #eaeaea; } diff --git a/app/assets/javascripts/ide/components/pipelines/jobs.vue b/app/assets/javascripts/ide/components/pipelines/jobs.vue new file mode 100644 index 00000000000..d69945b617c --- /dev/null +++ b/app/assets/javascripts/ide/components/pipelines/jobs.vue @@ -0,0 +1,40 @@ + + + diff --git a/app/assets/javascripts/ide/components/right_sidebar/pipelines.vue b/app/assets/javascripts/ide/components/pipelines/list.vue similarity index 54% rename from app/assets/javascripts/ide/components/right_sidebar/pipelines.vue rename to app/assets/javascripts/ide/components/pipelines/list.vue index 0ff78242e6a..e76ea0b50af 100644 --- a/app/assets/javascripts/ide/components/right_sidebar/pipelines.vue +++ b/app/assets/javascripts/ide/components/pipelines/list.vue @@ -1,14 +1,17 @@ + + diff --git a/app/assets/javascripts/vue_shared/components/tabs/tabs.js b/app/assets/javascripts/vue_shared/components/tabs/tabs.js new file mode 100644 index 00000000000..3dff37b1c84 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/tabs/tabs.js @@ -0,0 +1,62 @@ +export default { + data() { + return { + currentIndex: 0, + tabs: [], + }; + }, + mounted() { + this.updateTabs(); + }, + methods: { + updateTabs() { + this.tabs = this.$children.filter(child => child.isTab); + this.currentIndex = this.tabs.findIndex(tab => tab.localActive); + }, + setTab(index) { + this.tabs[this.currentIndex].localActive = false; + this.tabs[index].localActive = true; + + this.currentIndex = index; + }, + }, + render(h) { + const navItems = this.tabs.map((tab, i) => + h( + 'li', + { + key: i, + class: tab.localActive ? 'active' : null, + }, + [ + h( + 'a', + { + href: '#', + on: { + click: () => this.setTab(i), + }, + }, + tab.$slots.title || tab.title, + ), + ], + ), + ); + const nav = h( + 'ul', + { + class: 'nav-links tab-links', + }, + [navItems], + ); + const content = h( + 'div', + { + class: ['tab-content'], + }, + [this.$slots.default], + ); + + return h('div', {}, [[nav], content]); + }, +}; diff --git a/spec/javascripts/vue_shared/components/tabs/tab_spec.js b/spec/javascripts/vue_shared/components/tabs/tab_spec.js new file mode 100644 index 00000000000..8437fe37738 --- /dev/null +++ b/spec/javascripts/vue_shared/components/tabs/tab_spec.js @@ -0,0 +1,32 @@ +import Vue from 'vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import Tab from '~/vue_shared/components/tabs/tab.vue'; + +describe('Tab component', () => { + const Component = Vue.extend(Tab); + let vm; + + beforeEach(() => { + vm = mountComponent(Component); + }); + + it('sets localActive to equal active', done => { + vm.active = true; + + vm.$nextTick(() => { + expect(vm.localActive).toBe(true); + + done(); + }); + }); + + it('sets active class', done => { + vm.active = true; + + vm.$nextTick(() => { + expect(vm.$el.classList).toContain('active'); + + done(); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/tabs/tabs_spec.js b/spec/javascripts/vue_shared/components/tabs/tabs_spec.js new file mode 100644 index 00000000000..07752329965 --- /dev/null +++ b/spec/javascripts/vue_shared/components/tabs/tabs_spec.js @@ -0,0 +1,68 @@ +import Vue from 'vue'; +import Tabs from '~/vue_shared/components/tabs/tabs'; +import Tab from '~/vue_shared/components/tabs/tab.vue'; + +describe('Tabs component', () => { + let vm; + + beforeEach(done => { + vm = new Vue({ + components: { + Tabs, + Tab, + }, + template: ` +
+ + + First tab + + + + Second tab + + +
+ `, + }).$mount(); + + setTimeout(done); + }); + + describe('tab links', () => { + it('renders links for tabs', () => { + expect(vm.$el.querySelectorAll('a').length).toBe(2); + }); + + it('renders link titles from props', () => { + expect(vm.$el.querySelector('a').textContent).toContain('Testing'); + }); + + it('renders link titles from slot', () => { + expect(vm.$el.querySelectorAll('a')[1].textContent).toContain('Test slot'); + }); + + it('renders active class', () => { + expect(vm.$el.querySelector('li').classList).toContain('active'); + }); + + it('updates active class on click', done => { + vm.$el.querySelectorAll('a')[1].click(); + + setTimeout(() => { + expect(vm.$el.querySelector('li').classList).not.toContain('active'); + expect(vm.$el.querySelectorAll('li')[1].classList).toContain('active'); + + done(); + }); + }); + }); + + describe('content', () => { + it('renders content panes', () => { + expect(vm.$el.querySelectorAll('.tab-pane').length).toBe(2); + expect(vm.$el.querySelectorAll('.tab-pane')[0].textContent).toContain('First tab'); + expect(vm.$el.querySelectorAll('.tab-pane')[1].textContent).toContain('Second tab'); + }); + }); +});