UNPKG

ohayolibs

Version:

Ohayo is a set of essential modules for ohayojp.

593 lines (564 loc) 20.3 kB
import { DOCUMENT } from '@angular/common'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { Component, DebugElement, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Router, RouterModule } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { ACLService } from '@ohayo/acl'; import { OhayoThemeModule, MenuIcon, MenuService, SettingsService, WINDOW } from '@ohayo/theme'; import { deepCopy } from '@ohayo/util'; import { SidebarNavComponent } from './sidebar-nav.component'; import { SidebarNavModule } from './sidebar-nav.module'; import { Nav } from './sidebar-nav.types'; const floatingShowCls = '.sidebar-nav__floating-show'; const MOCKMENUS = [ { text: '主导航', group: true, children: [ { text: '仪表盘', children: [ { text: 'v1', link: '/v1' }, { text: 'v2', link: '#/v2', i18n: 'v2-i18n' }, { text: 'v3' }, { text: 'externalLink-blank', externalLink: '//ohayojp.com/blank', target: '_blank', }, { text: 'externalLink-top', externalLink: '//ohayojp.com/top', target: '_top', }, ], }, ], }, ] as Nav[]; const MOCKOPENSTRICTLY = [ { text: '', group: true, children: [ { text: '', link: '/v1', open: true, children: [{ text: '' }], }, { text: '', link: '/v1', open: true, children: [{ text: '' }], }, ], }, ] as Nav[]; class MockACLService { can(val: string): boolean { return val === 'admin'; } } class MockWindow { location = new MockLocation(); open(): void { } } class MockLocation { private url: string; get href(): string { return this.url; } set href(url: string) { this.url = url; } } describe('components: sidebar-nav', () => { let fixture: ComponentFixture<TestComponent>; let dl: DebugElement; let context: TestComponent; let router: Router; let setSrv: SettingsService; let menuSrv: MenuService; let page: PageObject; let doc: Document; function createModule(): void { TestBed.configureTestingModule({ imports: [RouterModule.forRoot([]), OhayoThemeModule, HttpClientTestingModule, SidebarNavModule], declarations: [TestComponent], providers: [ { provide: ACLService, useClass: MockACLService }, { provide: WINDOW, useFactory: () => new MockWindow() }, ], }); } function createComp(needMockNavigateByUrl: boolean = true, callback?: () => void): void { fixture = TestBed.createComponent(TestComponent); dl = fixture.debugElement; context = fixture.componentInstance; fixture.detectChanges(); router = TestBed.inject<Router>(Router); setSrv = TestBed.inject<SettingsService>(SettingsService); menuSrv = TestBed.inject<MenuService>(MenuService); doc = TestBed.inject(DOCUMENT); menuSrv.add(deepCopy(MOCKMENUS)); page = new PageObject(); if (needMockNavigateByUrl) spyOn(router, 'navigateByUrl'); if (callback) callback(); } afterEach(() => context.comp.ngOnDestroy()); describe('', () => { beforeEach(() => createModule()); describe('[default]', () => { it('should be navigate url', () => { createComp(); spyOn(context, 'select'); const data = deepCopy(MOCKMENUS); menuSrv.add(data); expect(context.select).not.toHaveBeenCalled(); expect(router.navigateByUrl).not.toHaveBeenCalled(); const itemEl = page.getEl<HTMLElement>('.sidebar-nav__depth1 a'); itemEl!.click(); fixture.detectChanges(); expect(context.select).toHaveBeenCalled(); expect(router.navigateByUrl).toHaveBeenCalled(); }); describe('should be navigate external link', () => { it('with target is _blank', () => { createComp(); const win = TestBed.inject(WINDOW) as MockWindow; spyOn(win, 'open'); const itemEl = page.getEl<HTMLElement>('.sidebar-nav__item [data-id="6"]'); itemEl!.click(); expect(win.open).toHaveBeenCalled(); }); it('with target is _top', () => { createComp(); const win = TestBed.inject(WINDOW) as MockWindow; const itemEl = page.getEl<HTMLElement>('.sidebar-nav__item [data-id="7"]'); itemEl!.click(); expect(win.location.href).toBe(`//ohayojp.com/top`); }); }); it('should be hide group name', () => { createComp(); page.checkCount('.sidebar-nav__group-title'); const data = deepCopy(MOCKMENUS) as Nav[]; data[0].group = false; menuSrv.add(data); fixture.detectChanges(); page.checkCount('.sidebar-nav__group-title', 0); }); it('should be toggle open', () => { createComp(); const data = deepCopy(MOCKMENUS); menuSrv.add(data); expect(data[0].children[0]._open).toBeUndefined(); const subTitleEl = page.getEl<HTMLElement>('.sidebar-nav__item-link'); subTitleEl!.click(); fixture.detectChanges(); expect(data[0].children[0]._open).toBe(true); }); it('should be reset menu when service is changed', () => { createComp(); page.checkText('.sidebar-nav__group-title', MOCKMENUS[0].text); const newMenu = deepCopy(MOCKMENUS); newMenu[0].text = 'new主导航'; menuSrv.add(newMenu); fixture.detectChanges(); page.checkText('.sidebar-nav__group-title', newMenu[0].text); }); it('should be block click menu when is disabled', () => { createComp(); spyOn(context, 'select'); const newMenus = [ { text: '', children: [{ text: 'new menu', disabled: true }], }, ]; menuSrv.add(newMenus); expect(context.select).not.toHaveBeenCalled(); const itemEl = page.getEl<HTMLElement>('.sidebar-nav__item-disabled'); itemEl!.click(); fixture.detectChanges(); expect(context.select).toHaveBeenCalled(); }); it('should be support html in text or i18n', () => { createComp(); menuSrv.add([{ text: 'text <strong>1</strong>' }]); page.checkText('.sidebar-nav__item', `text 1`); menuSrv.add([{ i18n: 'i18n <strong>1</strong>' }]); page.checkText('.sidebar-nav__item', `i18n 1`); }); }); describe('#icon', () => { function updateIcon(icon: string | MenuIcon): void { createComp(); menuSrv.add([ { text: '', group: true, children: [ { text: '', icon, }, ], }, ] as Nav[]); fixture.detectChanges(); } describe('with icon', () => { it('when is string and includes [anticon-]', () => { updateIcon('anticon-edit'); const el = page.getEl('.sidebar-nav__item-icon') as HTMLElement; expect(el.classList).toContain('anticon-edit'); }); it('when is string and http prefix', () => { updateIcon('http://ohayojp/1.jpg'); page.checkCount('.sidebar-nav__item-img', 1); }); it('when is class string', () => { updateIcon('demo-class'); page.checkCount('.demo-class', 1); }); }); it('with className', () => { updateIcon({ type: 'class', value: 'demo-class' }); page.checkCount('.demo-class', 1); }); it('with img', () => { updateIcon({ type: 'img', value: '1.jpg' }); page.checkCount('.sidebar-nav__item-img', 1); }); }); describe('[collapsed]', () => { describe('#default', () => { beforeEach(() => { createComp(); setSrv.layout.collapsed = true; fixture.detectChanges(); }); it(`should be won't show sub-menu when not collapse`, () => { setSrv.layout.collapsed = false; fixture.detectChanges(); page.showSubMenu(false); }); it('should be show sub-menu', () => { page.showSubMenu(); }); it('should be displayed full submenu', () => { const clientHeight = spyOnProperty(doc.documentElement, 'clientHeight').and.returnValue(0); spyOnProperty(doc.querySelector('body')!, 'clientHeight').and.returnValue(0); expect(clientHeight).not.toHaveBeenCalled(); page.showSubMenu(); expect(clientHeight).toHaveBeenCalled(); }); it('should be working when include badge', () => { const mockMenu = deepCopy(MOCKMENUS) as Nav[]; mockMenu[0].children![0].badge = 1; menuSrv.add(mockMenu); fixture.detectChanges(); expect(page.getEl('.badge') != null).toBe(true); page.showSubMenu(); expect(page.getEl('.sidebar-nav__floating-container .sidebar-nav__item', true) != null).toBe(true); }); it('should be ingore children title trigger event', () => { spyOn(context, 'select'); expect(context.select).not.toHaveBeenCalled(); const mockMenu = deepCopy(MOCKMENUS) as Nav[]; mockMenu[0].children![0].children = [{ text: 'a', children: [{ text: 'b' }] }]; menuSrv.add(mockMenu); fixture.detectChanges(); page.showSubMenu(); const containerEl = page.getEl<HTMLElement>(floatingShowCls, true)!; (containerEl.querySelector('.sidebar-nav__item-link') as HTMLElement).click(); expect(context.select).not.toHaveBeenCalled(); }); }); describe('should be hide sub-menu in floating container', () => { it('muse be hide via click menu link', () => { createComp(); setSrv.layout.collapsed = true; fixture.detectChanges(); page.showSubMenu(); page.hideSubMenu(); expect(router.navigateByUrl).toHaveBeenCalled(); }); it('muse be hide via mouse leave area', () => { createComp(); setSrv.layout.collapsed = true; fixture.detectChanges(); page.showSubMenu(); page.getEl<HTMLElement>(floatingShowCls, true)!.dispatchEvent(new Event('mouseleave')); fixture.detectChanges(); expect(page.getEl<HTMLElement>(floatingShowCls, true)).toBeNull(); }); it('muse be not hide via click except menu link area', () => { createComp(); setSrv.layout.collapsed = true; fixture.detectChanges(); page.showSubMenu(); const containerEl = page.getEl<HTMLElement>(floatingShowCls, true); containerEl!.querySelectorAll('li')[1].click(); fixture.detectChanges(); expect(router.navigateByUrl).not.toHaveBeenCalled(); }); it('muse be hide via click span of menu item', () => { createComp(); setSrv.layout.collapsed = true; fixture.detectChanges(); page.showSubMenu(); const containerEl = page.getEl<HTMLElement>(floatingShowCls, true); containerEl!.querySelectorAll('span')[1].click(); fixture.detectChanges(); expect(router.navigateByUrl).toHaveBeenCalled(); }); it('muse be hide via document click', () => { createComp(); setSrv.layout.collapsed = true; fixture.detectChanges(); page.showSubMenu(); document.dispatchEvent(new MouseEvent('click')); fixture.detectChanges(); expect(router.navigateByUrl).not.toHaveBeenCalled(); }); }); it('#52', () => { createComp(); setSrv.layout.collapsed = true; fixture.detectChanges(); page.showSubMenu(); // tslint:disable-next-line: no-string-literal spyOn(context.comp['floatingEl'], 'remove'); page.hideSubMenu(); expect(page.getEl<HTMLElement>(floatingShowCls, true)).toBeNull(); }); }); describe('#disabledAcl', () => { const newMenus = [ { text: '', children: [ { text: 'new menu', acl: 'admin' }, { text: 'new menu', acl: 'user' }, ], }, ]; beforeEach(() => createComp()); it('should be disabled item when with true', () => { context.disabledAcl = true; fixture.detectChanges(); menuSrv.add(newMenus); const itemEl = page.getEl<HTMLElement>('.sidebar-nav__item [data-id="3"]'); expect(itemEl!.classList).toContain('sidebar-nav__item-disabled'); }); it('should be hidden item when with false', () => { context.disabledAcl = false; fixture.detectChanges(); menuSrv.add(newMenus); const itemEl = page.getEl<HTMLElement>('.sidebar-nav__item [data-id="3"]'); expect(itemEl == null).toBe(true); }); }); describe('#openStrictly', () => { beforeEach(() => { createComp(); context.openStrictly = true; fixture.detectChanges(); menuSrv.add(deepCopy(MOCKOPENSTRICTLY)); fixture.detectChanges(); }); it('should working', () => { page.checkCount('.sidebar-nav__open', 2); }); it(`should be won't close other item`, () => { const list = dl.queryAll(By.css('.sidebar-nav__item-link')); expect(list.length).toBe(4); (list[0].nativeElement as HTMLElement).click(); fixture.detectChanges(); page.checkCount('.sidebar-nav__open', 1); (list[2].nativeElement as HTMLElement).click(); fixture.detectChanges(); page.checkCount('.sidebar-nav__open', 0); }); }); }); describe('[underPad]', () => { beforeEach(createModule); it('should be auto collapsed when less than pad', fakeAsync(() => { // create test component TestBed.overrideTemplate(TestComponent, `<sidebar-nav #comp [autoCloseUnderPad]="true"></sidebar-nav>`); const defaultCollapsed = false; createComp(false, () => { spyOnProperty(window, 'innerWidth').and.returnValue(767); setSrv.layout.collapsed = defaultCollapsed; fixture.detectChanges(); }); router.navigateByUrl('/'); fixture.detectChanges(); tick(20); expect(setSrv.layout.collapsed).toBe(!defaultCollapsed); })); it(`should be won't collapsed when more than pad`, fakeAsync(() => { // create test component TestBed.overrideTemplate(TestComponent, `<sidebar-nav #comp [autoCloseUnderPad]="true"></sidebar-nav>`); const defaultCollapsed = false; createComp(false, () => { spyOnProperty(window, 'innerWidth').and.returnValue(769); setSrv.layout.collapsed = defaultCollapsed; fixture.detectChanges(); }); router.navigateByUrl('/'); fixture.detectChanges(); tick(1000); expect(setSrv.layout.collapsed).toBe(defaultCollapsed); })); it('should be auto expaned when less than pad trigger click', fakeAsync(() => { // create test component TestBed.overrideTemplate(TestComponent, `<sidebar-nav #comp [autoCloseUnderPad]="true"></sidebar-nav>`); createComp(); setSrv.layout.collapsed = true; fixture.detectChanges(); spyOnProperty(window, 'innerWidth').and.returnValue(767); expect(setSrv.layout.collapsed).toBe(true); page.getEl<HTMLElement>('.sidebar-nav')!.click(); fixture.detectChanges(); tick(20); expect(setSrv.layout.collapsed).toBe(false); })); }); describe('should be recursive path', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [ RouterModule.forRoot([]), OhayoThemeModule, SidebarNavModule, RouterTestingModule.withRoutes([ { path: 'user', component: TestRouteComponent }, { path: 'user2', component: TestRouteComponent }, { path: 'user/type', component: TestRouteComponent }, ]), ], declarations: [TestComponent, TestRouteComponent], }); }); beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(TestComponent); dl = fixture.debugElement; context = fixture.componentInstance; menuSrv = TestBed.inject<MenuService>(MenuService); fixture.detectChanges(); createComp(false); menuSrv.add([ { text: '主导航', group: true, children: [ { text: 'user1', link: '/user' }, { text: 'user2', link: '/user' }, ], }, ]); })); it('with true', fakeAsync(() => { context.recursivePath = true; fixture.detectChanges(); router.navigateByUrl('/user2'); tick(); fixture.detectChanges(); page.checkCount('.sidebar-nav__selected', 0); router.navigateByUrl('/user/type'); tick(); fixture.detectChanges(); page.checkCount('.sidebar-nav__selected', 1); })); it('with false', fakeAsync(() => { context.recursivePath = false; fixture.detectChanges(); router.navigateByUrl('/user2'); tick(); fixture.detectChanges(); page.checkCount('.sidebar-nav__selected', 0); router.navigateByUrl('/user/type'); tick(); fixture.detectChanges(); page.checkCount('.sidebar-nav__selected', 0); })); it('should be ingore _open when enabled openStrictly', fakeAsync(() => { context.openStrictly = true; fixture.detectChanges(); menuSrv.add(deepCopy(MOCKOPENSTRICTLY)); page.checkCount('.sidebar-nav__open', 2); router.navigateByUrl('/user2'); fixture.detectChanges(); page.checkCount('.sidebar-nav__open', 2); })); }); class PageObject { getEl<T>(cls: string, body: boolean = false): T | null { const el = body ? document.querySelector(cls) : dl.query(By.css(cls)) ? dl.query(By.css(cls)).nativeElement : null; return el ? (el as T) : null; } checkText(cls: string, value: any): void { const el = this.getEl<HTMLElement>(cls); expect(el ? el.innerText.trim() : '').toBe(value); } checkCount(cls: string, count: number = 1): this { expect(dl.queryAll(By.css(cls)).length).toBe(count); return this; } /** 期望显示子菜单,默认:`true` */ showSubMenu(resultExpectShow: boolean = true): void { let conEl = this.getEl<HTMLElement>(floatingShowCls, true); expect(conEl).toBeNull(); const subTitleEl = this.getEl<HTMLElement>('.sidebar-nav__item-link'); subTitleEl!.dispatchEvent(new Event('mouseenter')); fixture.detectChanges(); conEl = this.getEl<HTMLElement>(floatingShowCls, true); if (resultExpectShow) { expect(conEl).not.toBeNull(); } else { expect(conEl).toBeNull(); } } /** 期望隐藏子菜单,默认:`true` */ hideSubMenu(resultExpectHide: boolean = true): void { const containerEl = this.getEl<HTMLElement>(floatingShowCls, true); expect(containerEl).not.toBeNull(); containerEl!.querySelector(resultExpectHide ? 'a' : 'li')!.click(); fixture.detectChanges(); const conEl = this.getEl<HTMLElement>(floatingShowCls, true); if (resultExpectHide) expect(conEl).toBeNull(); else expect(conEl).not.toBeNull(); } } }); @Component({ template: ` <sidebar-nav #comp [disabledAcl]="disabledAcl" [autoCloseUnderPad]="autoCloseUnderPad" [recursivePath]="recursivePath" [openStrictly]="openStrictly" (select)="select()" ></sidebar-nav> `, }) class TestComponent { @ViewChild('comp', { static: true }) comp: SidebarNavComponent; disabledAcl = false; autoCloseUnderPad = false; recursivePath = false; openStrictly = false; select(): void { } } @Component({ template: `` }) class TestRouteComponent { }