ohayolibs
Version:
Ohayo is a set of essential modules for ohayojp.
530 lines (517 loc) • 18.8 kB
text/typescript
import { TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router, RouteReuseStrategy } from '@angular/router';
import { MenuService } from '@ohayo/theme';
import { NzSafeAny } from 'ng-zorro-antd/core/types';
import { filter } from 'rxjs/operators';
import { ReuseTabMatchMode, ReuseTitle } from './reuse-tab.interfaces';
import { ReuseTabService } from './reuse-tab.service';
import { ReuseTabStrategy } from './reuse-tab.strategy';
class MockMenuService {
getPathByUrl(url: string): Array<{ text: string }> {
return url === '/a/0' ? [] : [{ text: `标题` }];
}
}
class MockRouter {
navigateByUrl = jasmine.createSpy();
get events(): any {
return {
subscribe: () => {
return { unsubscribe: () => { } };
},
};
}
}
describe('components: reuse-tab(service)', () => {
let srv: ReuseTabService;
let menuSrv: MenuService;
let router: MockRouter;
afterEach(() => srv.ngOnDestroy());
function genModule(providers: any[] = [{ provide: MenuService, useClass: MockMenuService }]): void {
TestBed.configureTestingModule({
providers: [
ReuseTabService,
{
provide: RouteReuseStrategy,
useClass: ReuseTabStrategy,
deps: [ReuseTabService],
},
{ provide: ActivatedRoute, useValue: { snapshot: { url: [] } } },
{ provide: Router, useFactory: () => new MockRouter() },
].concat(providers),
});
srv = TestBed.inject<ReuseTabService>(ReuseTabService);
menuSrv = TestBed.inject<MenuService>(MenuService, undefined);
router = TestBed.inject<Router>(Router) as NzSafeAny;
}
function genCached(count: number, urlTpl: string = `a/{index}`): void {
srv.clear();
Array(count)
.fill({})
.forEach((_item: any, index: number) => {
srv.store(getSnapshot(index + 1, urlTpl), { a: 1 });
});
}
/**
* 模拟 Snapshot
* - 1 => a/1
*/
function getSnapshot(index: number, urlTpl: string = `a/{index}`): any {
return {
routeConfig: {},
url: [urlTpl.replace(`{index}`, index + '')],
} as any;
}
describe('[property]', () => {
beforeEach(() => genModule());
describe('#max', () => {
it('should working', () => {
genCached(10);
expect(srv.count).toBe(10);
srv.max = 5;
expect(srv.count).toBe(5);
});
it('should be close oldest page', () => {
srv.max = 2;
srv.store(getSnapshot(1), {});
srv.store(getSnapshot(2), {});
srv.store(getSnapshot(3), {});
expect(srv.count).toBe(2);
srv.store(getSnapshot(4), {});
expect(srv.count).toBe(2);
});
it('should be ingore close when all is not closable', () => {
srv.max = 2;
srv.store(getSnapshot(1), {});
srv.store(getSnapshot(2), {});
srv.items.forEach(i => (i.closable = false));
srv.store(getSnapshot(3), {});
expect(srv.count).toBe(3);
srv.store(getSnapshot(4), {});
expect(srv.count).toBe(3);
});
});
describe('#mode', () => {
describe('when ReuseTabMatchMode.Menu', () => {
beforeEach(() => (srv.mode = ReuseTabMatchMode.Menu));
it('can hit because menu data not allow', () => {
const snapshot = getSnapshot(1);
expect(srv.can(snapshot)).toBe(true);
});
it(`can't hit because not found in menu`, () => {
const snapshot = getSnapshot(0);
expect(srv.can(snapshot)).toBe(false);
});
it(`can't hit because not allowed reuse in menu`, () => {
const spy = spyOn(menuSrv, 'getPathByUrl');
spy.and.returnValue([{ text: '1', link: '/a/1', reuse: false }]);
expect(srv.can(getSnapshot(1))).toBe(false);
spy.and.returnValue([{ text: '2', link: '/a/2', reuse: true }]);
expect(srv.can(getSnapshot(2))).toBe(true);
});
});
describe('when ReuseTabMatchMode.MenuForce', () => {
beforeEach(() => (srv.mode = ReuseTabMatchMode.MenuForce));
it('can hit because menu data muse allow', () => {
const spy = spyOn(menuSrv, 'getPathByUrl');
spy.and.returnValue([{ text: '1', link: '/a/1', reuse: true }]);
expect(srv.can(getSnapshot(1))).toBe(true);
});
it(`can't hit because not found in menu`, () => {
const snapshot = getSnapshot(0);
expect(srv.can(snapshot)).toBe(false);
});
it(`can't hit because not allowed reuse in menu`, () => {
const spy = spyOn(menuSrv, 'getPathByUrl');
spy.and.returnValue([{ text: '1', link: '/a/1', reuse: false }]);
expect(srv.can(getSnapshot(1))).toBe(false);
spy.and.returnValue([{ text: '2', link: '/a/2', reuse: true }]);
expect(srv.can(getSnapshot(2))).toBe(true);
});
});
describe('when ReuseTabMatchMode.URL', () => {
beforeEach(() => (srv.mode = ReuseTabMatchMode.URL));
it('can hit because not exclude', () => {
const snapshot = getSnapshot(1);
expect(srv.can(snapshot)).toBe(true);
});
it(`can't hit because exclude`, () => {
srv.excludes = [/\/a\/0/i];
const snapshot = getSnapshot(0);
expect(srv.can(snapshot)).toBe(false);
});
});
});
describe('#debug', () => {
beforeEach(() => {
spyOn(console, 'warn');
genCached(1);
});
it('with true', () => {
srv.debug = true;
srv.close('/a/1');
expect(console.warn).toHaveBeenCalled();
});
it('with false', () => {
srv.debug = false;
srv.close('/a/1');
expect(console.warn).not.toHaveBeenCalled();
});
});
describe('#excludes', () => {
beforeEach(() => (srv.mode = ReuseTabMatchMode.URL));
it('can hit because not exclude', () => {
srv.excludes = [];
const snapshot = getSnapshot(1);
expect(srv.can(snapshot)).toBe(true);
});
it(`can't hit because exclude`, () => {
srv.excludes = [/\/a\/0/i];
const snapshot = getSnapshot(0);
expect(srv.can(snapshot)).toBe(false);
expect(srv.excludes.length).toBe(1);
});
});
it('basic methods', () => {
let count = 5;
genCached(count);
// index
expect(srv.index('/a/1')).toBe(0, `'index' muse be contain '/a/1'`);
expect(srv.index('/a/b')).toBe(-1, `'index' muse be not contain '/a/b'`);
// exists
expect(srv.exists('/a/1')).toBe(true, `'exists' muse be contain '/a/1'`);
expect(srv.exists('/a/b')).toBe(false, `'exists' muse be not contain '/a/b'`);
// get
expect(srv.get('/a/1')).not.toBeNull(`'get' muse be return cache data`);
expect(srv.get('/a/b')).toBeNull(`'get' muse be return null`);
expect(srv.get()).toBeNull(`'get' muse be return null if null`);
// remove
srv.close('/a/1');
--count;
expect(srv.count).toBe(count, `'remove' muse be return ${count} when has removed`);
srv.close('/a/b');
expect(srv.count).toBe(count, `'remove' muse be return ${count} when invalid url`);
// items
expect(srv.items.length).toBe(count, `'items' muse be return ${count} length`);
// count
expect(srv.count).toBe(count, `'count' muse be return ${count}`);
// clear
srv.clear();
expect(srv.count).toBe(0, `'clear' muse be return 0`);
});
describe('#title', () => {
it('should reset title via service', () => {
genCached(1, '/');
expect(srv.getTitle('/').text).toBe('标题');
srv.title = 'a';
expect(srv.getTitle('/').text).toBe('a');
});
it('should get title from route data', () => {
const title = 'aa';
expect(srv.getTitle('/', { data: { title } } as any).text).toBe(title);
});
it('should used ReuseTitle set title via service', () => {
genCached(1, '/');
expect(srv.getTitle('/').text).toBe('标题');
srv.title = { text: 'a' } as ReuseTitle;
expect(srv.getTitle('/').text).toBe('a');
});
it('should use url as title when can be no found title', () => {
const url = '/a/0';
expect(srv.getTitle(url).text).toBe(url);
});
it('#clearTitleCached', () => {
srv.clearTitleCached();
expect(true).toBe(true);
});
});
describe('#closable', () => {
it('should reset closable via service', () => {
genCached(1, '/');
expect(srv.getClosable('/')).toBe(true);
srv.closable = false;
expect(srv.getClosable('/')).toBe(false);
});
it('should get closable from route data', () => {
const closable = false;
expect(srv.getClosable('/', { data: { reuseClosable: closable } } as any)).toBe(closable);
});
it('should get closable from menu data', () => {
const spy = spyOn(menuSrv, 'getPathByUrl');
spy.and.returnValue([{ text: '1', link: '/1', reuseClosable: false }]);
expect(srv.getClosable('/1')).toBe(false);
spy.and.returnValue([{ text: '2', link: '/2', reuseClosable: true }]);
expect(srv.getClosable('/2')).toBe(true);
});
it('should use url as title when can be no found title', () => {
const url = '/a/0';
expect(srv.getClosable(url)).toBe(true);
});
it('should be keep closable: false when cleared', () => {
genCached(5);
srv.items[0].closable = false;
expect(srv.count).toBe(5);
srv.clear();
expect(srv.count).toBe(1);
});
it('#clearClosableCached', () => {
srv.clearClosableCached();
expect(true).toBe(true);
});
it('should be forced clear even though closable: false', () => {
genCached(5);
srv.items[0].closable = false;
expect(srv.count).toBe(5);
srv.clear(true);
expect(srv.count).toBe(0);
});
});
describe('#remove', () => {
const INITCOUNT = 2;
beforeEach(() => genCached(INITCOUNT));
it('should be remove a route', () => {
expect(srv.count).toBe(INITCOUNT);
srv.close('/a/1');
expect(srv.count).toBe(INITCOUNT - 1);
});
it('should be forced remove even though closable: false', () => {
expect(srv.count).toBe(INITCOUNT);
srv.items[0].closable = false;
srv.close('/a/1', true);
expect(srv.count).toBe(INITCOUNT - 1);
});
it('should be remove a non-exists page', () => {
expect(srv.count).toBe(INITCOUNT);
srv.close('/a/10');
expect(srv.count).toBe(INITCOUNT);
});
it('should be destroy component instance when remove a page', () => {
const instance = {
componentRef: {
destroy: jasmine.createSpy('destroy'),
},
};
srv.store(getSnapshot(3), instance);
srv.close('/a/3');
expect(instance.componentRef.destroy).toHaveBeenCalled();
});
it('should be close right', () => {
genCached(10);
srv.closeRight('/a/5');
expect(srv.count).toBe(5);
expect(srv.items[srv.count - 1].url).toBe('/a/5');
});
it('should forced close right even though closable: false', () => {
genCached(10);
srv.items[7].closable = false;
srv.items[8].closable = false;
srv.closeRight('/a/5', true);
expect(srv.count).toBe(5);
expect(srv.items[srv.count - 1].url).toBe('/a/5');
});
});
describe('#move', () => {
const INITCOUNT = 5;
beforeEach(() => genCached(INITCOUNT));
it('should be [/a/1] from 0 to 2', () => {
const source = '/a/1';
const target = '/a/3';
expect(srv.items[0].url).toBe(source);
expect(srv.items[2].url).toBe(target);
srv.move(source, 2);
expect(srv.items[0].url).toBe('/a/2');
expect(srv.items[1].url).toBe(target);
expect(srv.items[2].url).toBe(source);
});
it('should be [/a/1] from 0 to -1', () => {
const source = '/a/1';
const target = '/a/5';
expect(srv.items[0].url).toBe(source);
expect(srv.items[INITCOUNT - 1].url).toBe(target);
srv.move(source, -1);
expect(srv.items[0].url).toBe('/a/2');
expect(srv.items[INITCOUNT - 2].url).toBe(target);
expect(srv.items[INITCOUNT - 1].url).toBe(source);
});
it('should be move a invalid url', () => {
const source = '/a/1';
const target = '/a/3';
const invalidUrl = '/a/a';
expect(srv.items[0].url).toBe(source);
expect(srv.items[2].url).toBe(target);
srv.move(invalidUrl, 2);
expect(srv.items[0].url).toBe(source);
expect(srv.items[2].url).toBe(target);
});
});
it('#refresh', () => {
const _$ = srv.change.pipe(filter(w => w !== null)).subscribe(
res => {
expect(res!.active).toBe('refresh');
_$.unsubscribe();
},
() => expect(false).toBe(true),
);
srv.refresh(true);
});
describe('#replace', () => {
it('should be navigate to new url', () => {
expect(router.navigateByUrl).not.toHaveBeenCalled();
srv.replace('/a/1');
expect(router.navigateByUrl).toHaveBeenCalled();
});
it('should be closed current router after navigate to new url', () => {
genCached(1, '');
expect(router.navigateByUrl).not.toHaveBeenCalled();
srv.replace('/b');
expect(router.navigateByUrl).toHaveBeenCalled();
});
});
describe('#keepingScroll', () => {
it('should get keepingScroll from service', () => {
srv.keepingScroll = true;
expect(srv.getKeepingScroll('')).toBe(true);
srv.keepingScroll = false;
expect(srv.getKeepingScroll('')).toBe(false);
});
it('should get keepingScroll from route data', () => {
expect(srv.getKeepingScroll('/', { data: { keepingScroll: false } } as any)).toBe(false);
expect(srv.getKeepingScroll('/', { data: { keepingScroll: true } } as any)).toBe(true);
});
it('should get keepingScroll from menu data', () => {
const spy = spyOn(menuSrv, 'getPathByUrl');
spy.and.returnValue([{ text: '1', link: '/1', keepingScroll: false }]);
expect(srv.getKeepingScroll('/1')).toBe(false);
spy.and.returnValue([{ text: '2', link: '/2', keepingScroll: true }]);
expect(srv.getKeepingScroll('/2')).toBe(true);
});
});
});
describe('##boundary##', () => {
it(`can't neve hit without MenuService in ReuseTabMatchMode.Menu mode`, () => {
genModule([]);
srv.mode = ReuseTabMatchMode.Menu;
expect(srv.can(getSnapshot(1))).toBe(false);
});
});
describe('#shouldDetach', () => {
beforeEach(() => {
genModule();
srv.mode = ReuseTabMatchMode.URL;
});
it(`can hit when target route`, () => {
expect(srv.shouldDetach(getSnapshot(0))).toBe(true);
});
it(`can't hit when is child route`, () => {
const snapshot = getSnapshot(0);
snapshot.routeConfig.children = [];
expect(srv.shouldDetach(snapshot)).toBe(false);
});
it(`can't hit when is lazy loading route`, () => {
const snapshot = getSnapshot(0);
snapshot.routeConfig.loadChildren = {};
expect(srv.shouldDetach(snapshot)).toBe(false);
});
it(`should route data first`, () => {
const snapshotTrue = getSnapshot(0);
snapshotTrue.data = {
reuse: true,
};
expect(srv.shouldDetach(snapshotTrue)).toBe(true);
const snapshotFalse = getSnapshot(0);
snapshotFalse.data = {
reuse: false,
};
expect(srv.shouldDetach(snapshotFalse)).toBe(false);
});
it(`can't hit when remove current page`, () => {
const snapshot = getSnapshot(1);
expect(srv.shouldDetach(snapshot)).toBe(true);
srv.store(snapshot, {});
srv.close(srv.getUrl(snapshot));
expect(srv.shouldDetach(snapshot)).toBe(false);
});
});
describe('#store', () => {
beforeEach(() => {
genModule();
srv.mode = ReuseTabMatchMode.URL;
genCached(2);
});
it(`should be store a new route`, () => {
expect(srv.count).toBe(2);
srv.store(getSnapshot(3), {});
expect(srv.count).toBe(3);
});
it(`should be store a exists route`, () => {
expect(srv.count).toBe(2);
srv.store(getSnapshot(1), {});
expect(srv.count).toBe(2);
});
it(`should be store a route when out of cache count`, () => {
srv.max = 2;
expect(srv.count).toBe(2);
srv.store(getSnapshot(3), { componentRef: {} });
expect(srv.count).toBe(2);
});
it(`should be run _onReuseDestroy event hook`, () => {
const handle = {
componentRef: {
instance: {
_onReuseDestroy: jasmine.createSpy('_onReuseDestroy'),
},
},
};
srv.store(getSnapshot(3), handle);
expect(handle.componentRef.instance._onReuseDestroy).toHaveBeenCalled();
});
});
describe('#shouldAttach', () => {
beforeEach(() => {
genModule();
srv.mode = ReuseTabMatchMode.URL;
});
it('should be attached when is cached', () => {
genCached(1);
expect(srv.shouldAttach(getSnapshot(1))).toBe(true);
expect(srv.shouldAttach(getSnapshot(2))).toBe(false);
});
it('should be not attach when not cached', () => {
expect(srv.shouldAttach(getSnapshot(1))).toBe(false);
expect(srv.shouldAttach(getSnapshot(2))).toBe(false);
});
});
describe('#retrieve', () => {
beforeEach(() => {
genModule();
srv.mode = ReuseTabMatchMode.URL;
});
it('should be retrieve when is cached', () => {
genCached(1);
const handle: any = srv.retrieve(getSnapshot(1));
expect(handle).not.toBeNull();
expect(handle.a).toBe(1);
});
it('should be not retrieve when not cached', () => {
expect(srv.retrieve(getSnapshot(2))).toBeNull();
});
it(`can't retrieve when is lazy loading route`, () => {
const snapshot = getSnapshot(0);
snapshot.routeConfig.loadChildren = {};
expect(srv.retrieve(getSnapshot(2))).toBeNull();
});
it(`should be run _onReuseInit event hook`, () => {
const handle = {
componentRef: {
instance: {
_onReuseInit: jasmine.createSpy('_onReuseInit'),
},
},
};
const snapshot = getSnapshot(3);
// handle
srv.store(snapshot, handle);
srv.shouldAttach(snapshot);
expect(handle.componentRef.instance._onReuseInit).toHaveBeenCalled();
});
});
});