From 5a1f7f6b69d74d8c36bbcda9e3a234823d88de2d Mon Sep 17 00:00:00 2001 From: LinFeng1997 Date: Tue, 28 Jun 2022 12:58:42 +0800 Subject: [PATCH] test: link-loader refactor --- src/__tests__/unit/mocks/link.ts | 22 +++++ src/__tests__/unit/source/loader/link.test.ts | 81 +++++++++++++++++++ src/source/index.ts | 17 +++- src/source/links.ts | 33 -------- src/source/loader/link.ts | 62 ++++++++++++++ 5 files changed, 179 insertions(+), 36 deletions(-) create mode 100644 src/__tests__/unit/mocks/link.ts create mode 100644 src/__tests__/unit/source/loader/link.test.ts create mode 100644 src/source/loader/link.ts diff --git a/src/__tests__/unit/mocks/link.ts b/src/__tests__/unit/mocks/link.ts new file mode 100644 index 000000000..55fe7a389 --- /dev/null +++ b/src/__tests__/unit/mocks/link.ts @@ -0,0 +1,22 @@ +import { sourceLinkInfo } from '@micro-app/types' + +export const MOCK_CSS = ` +div { + color: red; +} +` + +export const MOCK_CSS_URL = 'http://www.abc.com/common.css' +export const MOCK_ERROR_CSS_URL = 'http://www.abc.com/error.css' + +export const MOCK_CSS_LINK_INFO: sourceLinkInfo = { + code: '', + isGlobal: false, + placeholder: document.createComment('placeholder for link with href=common'), +} + +export const MOCK_ERROR_CSS_LINK_INFO: sourceLinkInfo = { + code: '', + isGlobal: false, + placeholder: document.createComment('placeholder for link with href=error'), +} diff --git a/src/__tests__/unit/source/loader/link.test.ts b/src/__tests__/unit/source/loader/link.test.ts new file mode 100644 index 000000000..eaba00153 --- /dev/null +++ b/src/__tests__/unit/source/loader/link.test.ts @@ -0,0 +1,81 @@ +import { waitFor } from '../../../common/util' +import { + MOCK_CSS, + MOCK_CSS_LINK_INFO, + MOCK_CSS_URL, + MOCK_ERROR_CSS_LINK_INFO, + MOCK_ERROR_CSS_URL, +} from '../../mocks/link' +import { setupMockFetch } from '../../mocks/fetch' +import { LinkLoader } from '../../../../source/loader/link' +import { MOCK_APP_URL } from '../../mocks/app' +import { AppInterface } from '@micro-app/types' + +const setup = (data: string) => { + const mockFetch = (url: string) => { + if (url === MOCK_ERROR_CSS_URL) { + return setupMockFetch(data, true)() + } + return setupMockFetch(data)() + } + global.fetch = jest.fn().mockImplementation(mockFetch) + const linkLoader = LinkLoader.getInstance() + const successCb = jest.fn() + const errorCb = jest.fn() + const finallyCb = jest.fn() + + return { + linkLoader, + successCb, + errorCb, + finallyCb, + } +} + +describe('LinkLoader', () => { + beforeAll(() => { + console.error = jest.fn() + }) + + test('给定一个链接集数据,可以获取到 css 字符串', async () => { + const { linkLoader, successCb, finallyCb } = setup(MOCK_CSS) + const links = new Map() + const app = { + name: 'app-1', + url: MOCK_APP_URL, + source: { + links + } + } as AppInterface + links.set(MOCK_CSS_URL, MOCK_CSS_LINK_INFO) + + linkLoader.run(app, successCb, finallyCb) + + await waitFor(() => { + expect(successCb).toBeCalledWith(MOCK_CSS_URL, MOCK_CSS_LINK_INFO, MOCK_CSS) + expect(finallyCb).toBeCalled() + }) + }) + + test('给定一个链接集数据,里面有多个链接信息,其中有一个会发生网络错误,会执行 errorCb 回调和 finallyCb 回调', async () => { + const { linkLoader, successCb, finallyCb } = setup(MOCK_CSS) + const links = new Map() + const app = { + name: 'app-1', + url: MOCK_APP_URL, + source: { + links + } + } as AppInterface + links.set(MOCK_CSS_URL, MOCK_CSS_LINK_INFO) + links.set(MOCK_ERROR_CSS_URL, MOCK_ERROR_CSS_LINK_INFO) + + linkLoader.run(app, successCb, finallyCb) + + const logError = jest.spyOn(console, 'error') + await waitFor(() => { + expect(logError).toBeCalled() + expect(finallyCb).toBeCalled() + }) + }) +}) diff --git a/src/source/index.ts b/src/source/index.ts index 6bd6e7261..7f9b70de7 100644 --- a/src/source/index.ts +++ b/src/source/index.ts @@ -1,10 +1,11 @@ -import type { AppInterface } from '@micro-app/types' +import type { AppInterface, sourceLinkInfo } from '@micro-app/types' import { logError, CompletionPath, pureCreateElement, } from '../libs/utils' -import { extractLinkFromHtml, fetchLinksFromHtml } from './links' +import { extractLinkFromHtml, fetchLinkSuccess } from './links' +import { LinkLoader } from './loader/link' import { extractScriptElement, fetchScriptsFromHtml, checkExcludeUrl, checkIgnoreUrl } from './scripts' import scopedCSS from './scoped_css' @@ -81,7 +82,17 @@ export function extractSourceDom (htmlStr: string, app: AppInterface) { flatChildren(wrapElement, app, microAppHead) if (app.source.links.size) { - fetchLinksFromHtml(wrapElement, app, microAppHead) + LinkLoader.getInstance().run(app, (url: string, info: sourceLinkInfo, data: string) => { + fetchLinkSuccess( + url, + info, + data, + microAppHead, + app, + ) + }, () => { + app.onLoad(wrapElement) + }) } else { app.onLoad(wrapElement) } diff --git a/src/source/links.ts b/src/source/links.ts index 4ec7b81e5..0dc3a46bf 100644 --- a/src/source/links.ts +++ b/src/source/links.ts @@ -5,7 +5,6 @@ import type { import { fetchSource } from './fetch' import { CompletionPath, - promiseStream, pureCreateElement, defer, logError, @@ -73,38 +72,6 @@ export function extractLinkFromHtml ( } } -/** - * Get link remote resources - * @param wrapElement htmlDom - * @param app app - * @param microAppHead micro-app-head - */ -export function fetchLinksFromHtml ( - wrapElement: HTMLElement, - app: AppInterface, - microAppHead: Element, -): void { - const linkEntries: Array<[string, sourceLinkInfo]> = Array.from(app.source.links.entries()) - - const fetchLinkPromise: Array|string> = linkEntries.map(([url]) => { - return globalLinks.has(url) ? globalLinks.get(url)! : fetchSource(url, app.name) - }) - - promiseStream(fetchLinkPromise, (res: {data: string, index: number}) => { - fetchLinkSuccess( - linkEntries[res.index][0], - linkEntries[res.index][1], - res.data, - microAppHead, - app, - ) - }, (err: {error: Error, index: number}) => { - logError(err, app.name) - }, () => { - app.onLoad(wrapElement) - }) -} - /** * fetch link succeeded, replace placeholder with style tag * @param url resource address diff --git a/src/source/loader/link.ts b/src/source/loader/link.ts new file mode 100644 index 000000000..dc60d8f16 --- /dev/null +++ b/src/source/loader/link.ts @@ -0,0 +1,62 @@ +import { AppInterface, sourceLinkInfo } from '@micro-app/types' +import { fetchSource } from '../fetch' +import { logError, promiseStream } from '../../libs/utils' +import { globalLinks } from '../links' + +export interface ILinkLoader { + run ( + app: AppInterface, + successCb: CallableFunction, + finallyCb: CallableFunction + ): void +} + +export class LinkLoader implements ILinkLoader { + private static instance: LinkLoader; + public static getInstance (): LinkLoader { + if (!this.instance) { + this.instance = new LinkLoader() + } + return this.instance + } + + /** + * Get link remote resources + * @param app app + * @param successCb success callback + * @param finallyCb finally callback + */ + public run ( + app: AppInterface, + successCb: CallableFunction, + finallyCb: CallableFunction + ): void { + const { + linkEntries, + fetchLinkPromise + } = this.getLinkData(app) + + promiseStream(fetchLinkPromise, (res: {data: string, index: number}) => { + const [url, info] = linkEntries[res.index] + + successCb(url, info, res.data) + }, (err: {error: Error, index: number}) => { + logError(err, app.name) + }, () => { + finallyCb?.() + }) + } + + private getLinkData (app: AppInterface) { + const linkEntries: Array<[string, sourceLinkInfo]> = Array.from(app.source.links.entries()) + + const fetchLinkPromise: Array|string> = linkEntries.map(([url]) => { + return globalLinks.has(url) ? globalLinks.get(url)! : fetchSource(url, app.name) + }) + + return { + linkEntries, + fetchLinkPromise + } + } +}