Przeglądaj źródła

HUE-9485 [frontend] Add a Vue tabs component

Johan Ahlen 5 lat temu
rodzic
commit
35edb17e9f

+ 40 - 0
desktop/core/src/desktop/js/components/Tab.test.ts

@@ -0,0 +1,40 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// 'License'); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { shallowMount } from '@vue/test-utils'
+import Tab from './Tab.vue'
+
+describe('Tab.vue', () => {
+  it('should render', () => {
+    let reportedTab: Tab | undefined;
+    const wrapper = shallowMount(Tab, {
+      propsData: { title: 'Tab title' },
+      provide: {
+        addTab: (tab: Tab): void => { reportedTab = tab },
+        removeTab: () => {}
+      },
+      slots: {
+        default: '<div>Some tab content</div>'
+      }
+    });
+    expect(wrapper.element).toMatchSnapshot();
+    if (reportedTab) {
+      expect(reportedTab.title).toEqual('Tab title');
+    } else {
+      fail('provided addTab not called with the tab');
+    }
+  })
+})

+ 56 - 0
desktop/core/src/desktop/js/components/Tab.vue

@@ -0,0 +1,56 @@
+<!--
+  Licensed to Cloudera, Inc. under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  Cloudera, Inc. licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<template>
+  <div v-show="isActive">
+    <slot />
+  </div>
+</template>
+
+<script lang="ts">
+  import Vue from 'vue';
+  import Component from 'vue-class-component';
+  import { Inject, Prop } from 'vue-property-decorator';
+
+  @Component
+  export default class Tab extends Vue {
+    @Inject()
+    addTab?: (tab: Tab) => void;
+    @Inject()
+    removeTab?: (tab: Tab) => void;
+
+    @Prop({ required: true })
+    title!: string;
+
+    isActive: boolean = false;
+
+    mounted() {
+      if (this.addTab) {
+        this.addTab(this)
+      }
+    }
+
+    destroyed() {
+      if (this.removeTab) {
+        this.removeTab(this);
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped></style>

+ 40 - 0
desktop/core/src/desktop/js/components/Tabs.test.ts

@@ -0,0 +1,40 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// 'License'); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Vue from 'vue';
+import { mount, shallowMount } from '@vue/test-utils'
+import Tabs from './Tabs.vue'
+import Tab from './Tab.vue'
+
+describe('Tabs.vue', () => {
+  it('should render empty tabs', () => {
+    const wrapper = shallowMount(Tabs);
+    expect(wrapper.element).toMatchSnapshot();
+  })
+
+  it('should render tabs', async () => {
+    const wrapper = mount(Tabs, {
+      slots: {
+        default: '<tab title="foo">foo</tab><tab title="bar">bar</tab>'
+      },
+      stubs: {
+        'tab': Tab
+      }
+    });
+    await Vue.nextTick();
+    expect(wrapper.element).toMatchSnapshot();
+  })
+})

+ 64 - 0
desktop/core/src/desktop/js/components/Tabs.vue

@@ -0,0 +1,64 @@
+<!--
+  Licensed to Cloudera, Inc. under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  Cloudera, Inc. licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<template>
+  <div>
+    <ul>
+      <li v-for='tab in tabs' :key='tab.title' @click='selectTab(tab)'>{{ tab.title }}</li>
+    </ul>
+    <slot></slot>
+  </div>
+</template>
+
+<script lang="ts">
+  import Vue from 'vue';
+  import Component from 'vue-class-component';
+  import { Provide } from 'vue-property-decorator';
+  import Tab from './Tab.vue';
+
+  @Component
+  export default class Tabs extends Vue {
+    tabs: Tab[] = [];
+
+    @Provide()
+    addTab(tab: Tab) {
+      this.tabs.push(tab);
+      if (this.tabs.length === 1) {
+        this.selectTab(this.tabs[0]);
+      }
+    }
+
+    @Provide()
+    removeTab(tab: Tab) {
+      const index = this.tabs.indexOf(tab);
+      if (index !== -1) {
+        this.$delete(this.tabs, index);
+        if (tab.isActive && this.tabs.length) {
+          this.tabs[Math.max(0, index - 1)].isActive = true;
+        }
+      }
+    }
+
+    selectTab(tab: Tab) {
+      this.tabs.forEach(other => { other.isActive = other === tab });
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 11 - 0
desktop/core/src/desktop/js/components/__snapshots__/Tab.test.ts.snap

@@ -0,0 +1,11 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Tab.vue should render 1`] = `
+<div
+  style="display: none;"
+>
+  <div>
+    Some tab content
+  </div>
+</div>
+`;

+ 32 - 0
desktop/core/src/desktop/js/components/__snapshots__/Tabs.test.ts.snap

@@ -0,0 +1,32 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Tabs.vue should render empty tabs 1`] = `
+<div>
+  <ul />
+   
+</div>
+`;
+
+exports[`Tabs.vue should render tabs 1`] = `
+<div>
+  <ul>
+    <li>
+      foo
+    </li>
+    <li>
+      bar
+    </li>
+  </ul>
+   
+  <div
+    style=""
+  >
+    foo
+  </div>
+  <div
+    style="display: none;"
+  >
+    bar
+  </div>
+</div>
+`;