Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/nav')
-rw-r--r--spec/frontend/nav/components/top_nav_app_spec.js68
-rw-r--r--spec/frontend/nav/components/top_nav_container_view_spec.js114
-rw-r--r--spec/frontend/nav/components/top_nav_dropdown_menu_spec.js157
-rw-r--r--spec/frontend/nav/components/top_nav_menu_item_spec.js74
-rw-r--r--spec/frontend/nav/mock_data.js35
5 files changed, 448 insertions, 0 deletions
diff --git a/spec/frontend/nav/components/top_nav_app_spec.js b/spec/frontend/nav/components/top_nav_app_spec.js
new file mode 100644
index 00000000000..06700ce748e
--- /dev/null
+++ b/spec/frontend/nav/components/top_nav_app_spec.js
@@ -0,0 +1,68 @@
+import { GlNavItemDropdown, GlTooltip } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
+import TopNavApp from '~/nav/components/top_nav_app.vue';
+import TopNavDropdownMenu from '~/nav/components/top_nav_dropdown_menu.vue';
+import { TEST_NAV_DATA } from '../mock_data';
+
+describe('~/nav/components/top_nav_app.vue', () => {
+ let wrapper;
+
+ const createComponent = (mountFn = shallowMount) => {
+ wrapper = mountFn(TopNavApp, {
+ propsData: {
+ navData: TEST_NAV_DATA,
+ },
+ });
+ };
+
+ const findNavItemDropdown = () => wrapper.findComponent(GlNavItemDropdown);
+ const findMenu = () => wrapper.findComponent(TopNavDropdownMenu);
+ const findTooltip = () => wrapper.findComponent(GlTooltip);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders nav item dropdown', () => {
+ expect(findNavItemDropdown().attributes('href')).toBeUndefined();
+ expect(findNavItemDropdown().attributes()).toMatchObject({
+ icon: 'dot-grid',
+ text: TEST_NAV_DATA.activeTitle,
+ 'no-flip': '',
+ });
+ });
+
+ it('renders top nav dropdown menu', () => {
+ expect(findMenu().props()).toStrictEqual({
+ primary: TEST_NAV_DATA.primary,
+ secondary: TEST_NAV_DATA.secondary,
+ views: TEST_NAV_DATA.views,
+ });
+ });
+
+ it('renders tooltip', () => {
+ expect(findTooltip().attributes()).toMatchObject({
+ 'boundary-padding': '0',
+ placement: 'right',
+ title: TopNavApp.TOOLTIP,
+ });
+ });
+ });
+
+ describe('when full mounted', () => {
+ beforeEach(() => {
+ createComponent(mount);
+ });
+
+ it('has dropdown toggle as tooltip target', () => {
+ const targetFn = findTooltip().props('target');
+
+ expect(targetFn()).toBe(wrapper.find('.js-top-nav-dropdown-toggle').element);
+ });
+ });
+});
diff --git a/spec/frontend/nav/components/top_nav_container_view_spec.js b/spec/frontend/nav/components/top_nav_container_view_spec.js
new file mode 100644
index 00000000000..b08d75f36ce
--- /dev/null
+++ b/spec/frontend/nav/components/top_nav_container_view_spec.js
@@ -0,0 +1,114 @@
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import FrequentItemsApp from '~/frequent_items/components/app.vue';
+import { FREQUENT_ITEMS_PROJECTS } from '~/frequent_items/constants';
+import eventHub from '~/frequent_items/event_hub';
+import TopNavContainerView from '~/nav/components/top_nav_container_view.vue';
+import TopNavMenuItem from '~/nav/components/top_nav_menu_item.vue';
+import VuexModuleProvider from '~/vue_shared/components/vuex_module_provider.vue';
+import { TEST_NAV_DATA } from '../mock_data';
+
+const DEFAULT_PROPS = {
+ frequentItemsDropdownType: FREQUENT_ITEMS_PROJECTS.namespace,
+ frequentItemsVuexModule: FREQUENT_ITEMS_PROJECTS.vuexModule,
+ linksPrimary: TEST_NAV_DATA.primary,
+ linksSecondary: TEST_NAV_DATA.secondary,
+};
+const TEST_OTHER_PROPS = {
+ namespace: 'projects',
+ currentUserName: '',
+ currentItem: {},
+};
+
+describe('~/nav/components/top_nav_container_view.vue', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(TopNavContainerView, {
+ propsData: {
+ ...DEFAULT_PROPS,
+ ...TEST_OTHER_PROPS,
+ ...props,
+ },
+ });
+ };
+
+ const findMenuItems = (parent = wrapper) => parent.findAll(TopNavMenuItem);
+ const findMenuItemsModel = (parent = wrapper) =>
+ findMenuItems(parent).wrappers.map((x) => x.props());
+ const findMenuItemGroups = () => wrapper.findAll('[data-testid="menu-item-group"]');
+ const findMenuItemGroupsModel = () => findMenuItemGroups().wrappers.map(findMenuItemsModel);
+ const findFrequentItemsApp = () => {
+ const parent = wrapper.findComponent(VuexModuleProvider);
+
+ return {
+ vuexModule: parent.props('vuexModule'),
+ props: parent.findComponent(FrequentItemsApp).props(),
+ };
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it.each(['projects', 'groups'])(
+ 'emits frequent items event to event hub (%s)',
+ async (frequentItemsDropdownType) => {
+ const listener = jest.fn();
+ eventHub.$on(`${frequentItemsDropdownType}-dropdownOpen`, listener);
+ createComponent({ frequentItemsDropdownType });
+
+ expect(listener).not.toHaveBeenCalled();
+
+ await nextTick();
+
+ expect(listener).toHaveBeenCalled();
+ },
+ );
+
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders frequent items app', () => {
+ expect(findFrequentItemsApp()).toEqual({
+ vuexModule: DEFAULT_PROPS.frequentItemsVuexModule,
+ props: TEST_OTHER_PROPS,
+ });
+ });
+
+ it('renders menu item groups', () => {
+ expect(findMenuItemGroupsModel()).toEqual([
+ TEST_NAV_DATA.primary.map((menuItem) => ({ menuItem })),
+ TEST_NAV_DATA.secondary.map((menuItem) => ({ menuItem })),
+ ]);
+ });
+
+ it('only the first group does not have margin top', () => {
+ expect(findMenuItemGroups().wrappers.map((x) => x.classes('gl-mt-3'))).toEqual([false, true]);
+ });
+
+ it('only the first menu item does not have margin top', () => {
+ const actual = findMenuItems(findMenuItemGroups().at(1)).wrappers.map((x) =>
+ x.classes('gl-mt-1'),
+ );
+
+ expect(actual).toEqual([false, ...TEST_NAV_DATA.secondary.slice(1).fill(true)]);
+ });
+ });
+
+ describe('without secondary links', () => {
+ beforeEach(() => {
+ createComponent({
+ linksSecondary: [],
+ });
+ });
+
+ it('renders one menu item group', () => {
+ expect(findMenuItemGroupsModel()).toEqual([
+ TEST_NAV_DATA.primary.map((menuItem) => ({ menuItem })),
+ ]);
+ });
+ });
+});
diff --git a/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js b/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js
new file mode 100644
index 00000000000..d9bba22238a
--- /dev/null
+++ b/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js
@@ -0,0 +1,157 @@
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import TopNavDropdownMenu from '~/nav/components/top_nav_dropdown_menu.vue';
+import KeepAliveSlots from '~/vue_shared/components/keep_alive_slots.vue';
+import { TEST_NAV_DATA } from '../mock_data';
+
+const SECONDARY_GROUP_CLASSES = TopNavDropdownMenu.SECONDARY_GROUP_CLASS.split(' ');
+
+describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(TopNavDropdownMenu, {
+ propsData: {
+ primary: TEST_NAV_DATA.primary,
+ secondary: TEST_NAV_DATA.secondary,
+ views: TEST_NAV_DATA.views,
+ ...props,
+ },
+ });
+ };
+
+ const findMenuItems = (parent = wrapper) => parent.findAll('[data-testid="menu-item"]');
+ const findMenuItemsModel = (parent = wrapper) =>
+ findMenuItems(parent).wrappers.map((x) => ({
+ menuItem: x.props('menuItem'),
+ isActive: x.classes('active'),
+ }));
+ const findMenuItemGroups = () => wrapper.findAll('[data-testid="menu-item-group"]');
+ const findMenuItemGroupsModel = () =>
+ findMenuItemGroups().wrappers.map((x) => ({
+ classes: x.classes(),
+ items: findMenuItemsModel(x),
+ }));
+ const findMenuSidebar = () => wrapper.find('[data-testid="menu-sidebar"]');
+ const findMenuSubview = () => wrapper.findComponent(KeepAliveSlots);
+ const hasFullWidthMenuSidebar = () => findMenuSidebar().classes('gl-w-full');
+
+ const createItemsGroupModelExpectation = ({
+ primary = TEST_NAV_DATA.primary,
+ secondary = TEST_NAV_DATA.secondary,
+ activeIndex = -1,
+ } = {}) => [
+ {
+ classes: [],
+ items: primary.map((menuItem, index) => ({ isActive: index === activeIndex, menuItem })),
+ },
+ {
+ classes: SECONDARY_GROUP_CLASSES,
+ items: secondary.map((menuItem) => ({ isActive: false, menuItem })),
+ },
+ ];
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders menu item groups', () => {
+ expect(findMenuItemGroupsModel()).toEqual(createItemsGroupModelExpectation());
+ });
+
+ it('has full width menu sidebar', () => {
+ expect(hasFullWidthMenuSidebar()).toBe(true);
+ });
+
+ it('renders hidden subview with no slot key', () => {
+ const subview = findMenuSubview();
+
+ expect(subview.isVisible()).toBe(false);
+ expect(subview.props()).toEqual({ slotKey: '' });
+ });
+
+ it('the first menu item in a group does not render margin top', () => {
+ const actual = findMenuItems(findMenuItemGroups().at(0)).wrappers.map((x) =>
+ x.classes('gl-mt-1'),
+ );
+
+ expect(actual).toEqual([false, ...TEST_NAV_DATA.primary.slice(1).fill(true)]);
+ });
+ });
+
+ describe('with pre-initialized active view', () => {
+ const primaryWithActive = [
+ TEST_NAV_DATA.primary[0],
+ {
+ ...TEST_NAV_DATA.primary[1],
+ active: true,
+ },
+ ...TEST_NAV_DATA.primary.slice(2),
+ ];
+
+ beforeEach(() => {
+ createComponent({
+ primary: primaryWithActive,
+ });
+ });
+
+ it('renders menu item groups', () => {
+ expect(findMenuItemGroupsModel()).toEqual(
+ createItemsGroupModelExpectation({ primary: primaryWithActive, activeIndex: 1 }),
+ );
+ });
+
+ it('does not have full width menu sidebar', () => {
+ expect(hasFullWidthMenuSidebar()).toBe(false);
+ });
+
+ it('renders visible subview with slot key', () => {
+ const subview = findMenuSubview();
+
+ expect(subview.isVisible()).toBe(true);
+ expect(subview.props('slotKey')).toBe(primaryWithActive[1].view);
+ });
+
+ it('does not change view if non-view menu item is clicked', async () => {
+ const secondaryLink = findMenuItems().at(primaryWithActive.length);
+
+ // Ensure this doesn't have a view
+ expect(secondaryLink.props('menuItem').view).toBeUndefined();
+
+ secondaryLink.vm.$emit('click');
+
+ await nextTick();
+
+ expect(findMenuSubview().props('slotKey')).toBe(primaryWithActive[1].view);
+ });
+
+ describe('when other view menu item is clicked', () => {
+ let primaryLink;
+
+ beforeEach(async () => {
+ primaryLink = findMenuItems().at(0);
+ primaryLink.vm.$emit('click');
+ await nextTick();
+ });
+
+ it('clicked on link with view', () => {
+ expect(primaryLink.props('menuItem').view).toBeTruthy();
+ });
+
+ it('changes active view', () => {
+ expect(findMenuSubview().props('slotKey')).toBe(primaryWithActive[0].view);
+ });
+
+ it('changes active status on menu item', () => {
+ expect(findMenuItemGroupsModel()).toStrictEqual(
+ createItemsGroupModelExpectation({ primary: primaryWithActive, activeIndex: 0 }),
+ );
+ });
+ });
+ });
+});
diff --git a/spec/frontend/nav/components/top_nav_menu_item_spec.js b/spec/frontend/nav/components/top_nav_menu_item_spec.js
new file mode 100644
index 00000000000..579af13d08a
--- /dev/null
+++ b/spec/frontend/nav/components/top_nav_menu_item_spec.js
@@ -0,0 +1,74 @@
+import { GlButton, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import TopNavMenuItem from '~/nav/components/top_nav_menu_item.vue';
+
+const TEST_MENU_ITEM = {
+ title: 'Cheeseburger',
+ icon: 'search',
+ href: '/pretty/good/burger',
+ view: 'burger-view',
+};
+
+describe('~/nav/components/top_nav_menu_item.vue', () => {
+ let listener;
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(TopNavMenuItem, {
+ propsData: {
+ menuItem: TEST_MENU_ITEM,
+ ...props,
+ },
+ listeners: {
+ click: listener,
+ },
+ });
+ };
+
+ const findButton = () => wrapper.find(GlButton);
+ const findButtonIcons = () =>
+ findButton()
+ .findAllComponents(GlIcon)
+ .wrappers.map((x) => x.props('name'));
+
+ beforeEach(() => {
+ listener = jest.fn();
+ });
+
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders button href and text', () => {
+ const button = findButton();
+
+ expect(button.attributes('href')).toBe(TEST_MENU_ITEM.href);
+ expect(button.text()).toBe(TEST_MENU_ITEM.title);
+ });
+
+ it('passes listeners to button', () => {
+ expect(listener).not.toHaveBeenCalled();
+
+ findButton().vm.$emit('click', 'TEST');
+
+ expect(listener).toHaveBeenCalledWith('TEST');
+ });
+ });
+
+ describe.each`
+ desc | menuItem | expectedIcons
+ ${'default'} | ${TEST_MENU_ITEM} | ${[TEST_MENU_ITEM.icon, 'chevron-right']}
+ ${'with no icon'} | ${{ ...TEST_MENU_ITEM, icon: null }} | ${['chevron-right']}
+ ${'with no view'} | ${{ ...TEST_MENU_ITEM, view: null }} | ${[TEST_MENU_ITEM.icon]}
+ ${'with no icon or view'} | ${{ ...TEST_MENU_ITEM, view: null, icon: null }} | ${[]}
+ `('$desc', ({ menuItem, expectedIcons }) => {
+ beforeEach(() => {
+ createComponent({ menuItem });
+ });
+
+ it(`renders expected icons ${JSON.stringify(expectedIcons)}`, () => {
+ expect(findButtonIcons()).toEqual(expectedIcons);
+ });
+ });
+});
diff --git a/spec/frontend/nav/mock_data.js b/spec/frontend/nav/mock_data.js
new file mode 100644
index 00000000000..2987d8deb16
--- /dev/null
+++ b/spec/frontend/nav/mock_data.js
@@ -0,0 +1,35 @@
+import { range } from 'lodash';
+
+export const TEST_NAV_DATA = {
+ activeTitle: 'Test Active Title',
+ primary: [
+ ...['projects', 'groups'].map((view) => ({
+ id: view,
+ href: null,
+ title: view,
+ view,
+ })),
+ ...range(0, 2).map((idx) => ({
+ id: `primary-link-${idx}`,
+ href: `/path/to/primary/${idx}`,
+ title: `Title ${idx}`,
+ })),
+ ],
+ secondary: range(0, 2).map((idx) => ({
+ id: `secondary-link-${idx}`,
+ href: `/path/to/secondary/${idx}`,
+ title: `SecTitle ${idx}`,
+ })),
+ views: {
+ projects: {
+ namespace: 'projects',
+ currentUserName: '',
+ currentItem: {},
+ },
+ groups: {
+ namespace: 'groups',
+ currentUserName: '',
+ currentItem: {},
+ },
+ },
+};