/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. * * Any modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. 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 { mount, ReactWrapper } from 'enzyme'; import React from 'react'; import { BehaviorSubject } from 'rxjs'; import sinon from 'sinon'; import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; import { ChromeNavLink, DEFAULT_APP_CATEGORIES } from '../../..'; import { httpServiceMock } from '../../../http/http_service.mock'; import { ChromeRecentlyAccessedHistoryItem } from '../../recently_accessed'; import { CollapsibleNav } from './collapsible_nav'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => 'mockId', })); const { opensearchDashboards, observability, security, management } = DEFAULT_APP_CATEGORIES; function mockLink({ title = 'discover', category }: Partial) { return { title, category, id: title, href: title, baseUrl: '/', isActive: true, 'data-test-subj': title, }; } function mockRecentNavLink({ label = 'recent' }: Partial) { return { label, link: label, id: label, }; } function mockProps() { return { appId$: new BehaviorSubject('test'), basePath: httpServiceMock.createSetupContract({ basePath: '/test' }).basePath, id: 'collapsibe-nav', isLocked: false, isNavOpen: false, homeHref: '/', navLinks$: new BehaviorSubject([]), recentlyAccessed$: new BehaviorSubject([]), storage: new StubBrowserStorage(), onIsLockedUpdate: () => {}, closeNav: () => {}, navigateToApp: () => Promise.resolve(), navigateToUrl: () => Promise.resolve(), customNavLink$: new BehaviorSubject(undefined), branding: { darkMode: false, mark: { defaultUrl: '/defaultModeLogo', darkModeUrl: '/darkModeLogo', }, }, }; } function expectShownNavLinksCount(component: ReactWrapper, count: number) { expect( component.find('.euiAccordion-isOpen a[data-test-subj^="collapsibleNavAppLink"]').length ).toEqual(count); } function expectNavIsClosed(component: ReactWrapper) { expectShownNavLinksCount(component, 0); } function clickGroup(component: ReactWrapper, group: string) { component.find(`[data-test-subj="collapsibleNavGroup-${group}"] button`).simulate('click'); } describe('CollapsibleNav', () => { // this test is mostly an "EUI works as expected" sanity check it('renders the default nav', () => { const onLock = sinon.spy(); const component = mount(); expect(component).toMatchSnapshot(); component.setProps({ isOpen: true }); expect(component).toMatchSnapshot(); component.setProps({ isLocked: true }); expect(component).toMatchSnapshot(); // limit the find to buttons because jest also renders data-test-subj on a JSX wrapper element component.find('button[data-test-subj="collapsible-nav-lock"]').simulate('click'); expect(onLock.callCount).toEqual(1); }); it('renders links grouped by category', () => { // just a test of category functionality, categories are not accurate const navLinks = [ mockLink({ title: 'discover', category: opensearchDashboards }), mockLink({ title: 'siem', category: security }), mockLink({ title: 'metrics', category: observability }), mockLink({ title: 'monitoring', category: management }), mockLink({ title: 'visualize', category: opensearchDashboards }), mockLink({ title: 'dashboard', category: opensearchDashboards }), mockLink({ title: 'canvas' }), // links should be able to be rendered top level as well mockLink({ title: 'logs', category: observability }), ]; const recentNavLinks = [ mockRecentNavLink({ label: 'recent 1' }), mockRecentNavLink({ label: 'recent 2' }), ]; const customNavLink = mockLink({ title: 'Custom link' }); const component = mount( ); expect(component).toMatchSnapshot(); }); it('remembers collapsible section state', () => { const navLinks = [ mockLink({ category: opensearchDashboards }), mockLink({ category: observability }), ]; const recentNavLinks = [mockRecentNavLink({})]; const component = mount( ); expectShownNavLinksCount(component, 3); clickGroup(component, 'opensearchDashboards'); clickGroup(component, 'recentlyViewed'); expectShownNavLinksCount(component, 1); component.setProps({ isNavOpen: false }); expectNavIsClosed(component); component.setProps({ isNavOpen: true }); expectShownNavLinksCount(component, 1); }); it('closes the nav after clicking a link', () => { const onClose = sinon.spy(); const navLinks = [ mockLink({ category: opensearchDashboards }), mockLink({ title: 'categoryless' }), ]; const recentNavLinks = [mockRecentNavLink({})]; const component = mount( ); component.setProps({ closeNav: () => { component.setProps({ isNavOpen: false }); onClose(); }, }); component.find('[data-test-subj="collapsibleNavGroup-recentlyViewed"] a').simulate('click'); expect(onClose.callCount).toEqual(1); expectNavIsClosed(component); component.setProps({ isNavOpen: true }); component .find('[data-test-subj="collapsibleNavGroup-opensearchDashboards"] a') .simulate('click'); expect(onClose.callCount).toEqual(2); expectNavIsClosed(component); component.setProps({ isNavOpen: true }); component.find('[data-test-subj="collapsibleNavGroup-noCategory"] a').simulate('click'); expect(onClose.callCount).toEqual(3); expectNavIsClosed(component); }); it('renders the nav bar with custom logo in default mode', () => { const navLinks = [ mockLink({ category: opensearchDashboards }), mockLink({ category: observability }), ]; const recentNavLinks = [mockRecentNavLink({})]; const component = mount( ); // check if nav bar renders default mode custom logo expect(component).toMatchSnapshot(); // check if nav bar renders the original default mode opensearch mark component.setProps({ branding: { darkMode: false, mark: {}, }, }); expect(component).toMatchSnapshot(); }); it('renders the nav bar with custom logo in dark mode', () => { const navLinks = [ mockLink({ category: opensearchDashboards }), mockLink({ category: observability }), ]; const recentNavLinks = [mockRecentNavLink({})]; const component = mount( ); // check if nav bar renders dark mode custom logo component.setProps({ branding: { darkMode: true, mark: { defaultUrl: '/defaultModeLogo', darkModeUrl: '/darkModeLogo', }, }, }); expect(component).toMatchSnapshot(); // check if nav bar renders default mode custom logo component.setProps({ branding: { darkMode: true, mark: { defaultUrl: '/defaultModeLogo', }, }, }); expect(component).toMatchSnapshot(); // check if nav bar renders the original dark mode opensearch mark component.setProps({ branding: { darkMode: false, mark: {}, }, }); expect(component).toMatchSnapshot(); }); });