mirror of https://github.com/alibaba/ice.git
				
				
				
			
		
			
	
	
		
			350 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
		
		
			
		
	
	
			350 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
|  | import React from 'react'; | ||
|  | import { renderToString } from 'react-dom/server'; | ||
|  | import { expect, it, vi, describe, beforeEach, afterEach } from 'vitest'; | ||
|  | import runClientApp, { loadNextPage } from '../src/runClientApp'; | ||
|  | import { useAppData } from '../src/AppData'; | ||
|  | import { useConfig, useData } from '../src/RouteContext'; | ||
|  | 
 | ||
|  | describe('run client app', () => { | ||
|  |   let windowSpy; | ||
|  |   let documentSpy; | ||
|  |   const mockData = { | ||
|  |     location: new URL('http://localhost:4000/'), | ||
|  |     history: { | ||
|  |       replaceState: vi.fn(), | ||
|  |     }, | ||
|  |     addEventListener: vi.fn(), | ||
|  |   }; | ||
|  |   beforeEach(() => { | ||
|  |     process.env.ICE_CORE_ROUTER = 'true'; | ||
|  |     windowSpy = vi.spyOn(global, 'window', 'get'); | ||
|  |     documentSpy = vi.spyOn(global, 'document', 'get'); | ||
|  | 
 | ||
|  |     windowSpy.mockImplementation(() => mockData); | ||
|  |     documentSpy.mockImplementation(() => ({ | ||
|  |       head: { | ||
|  |         querySelector: () => ({ | ||
|  |           content: '', | ||
|  |         }), | ||
|  |       }, | ||
|  |       getElementById: () => null, | ||
|  |       querySelectorAll: () => [], | ||
|  |     })); | ||
|  |   }); | ||
|  |   afterEach(() => { | ||
|  |     windowSpy.mockRestore(); | ||
|  |     documentSpy.mockRestore(); | ||
|  |   }); | ||
|  | 
 | ||
|  |   let domstring = ''; | ||
|  | 
 | ||
|  |   const serverRuntime = async ({ setRender }) => { | ||
|  |     setRender((container, element) => { | ||
|  |       try { | ||
|  |         domstring = renderToString(element as any); | ||
|  |       } catch (err) {} | ||
|  |     }); | ||
|  |   }; | ||
|  | 
 | ||
|  |   const wrapperRuntime = async ({ addWrapper }) => { | ||
|  |     const RouteWrapper = ({ children }) => { | ||
|  |       return <div>{children}</div>; | ||
|  |     }; | ||
|  |     addWrapper(RouteWrapper, true); | ||
|  |   }; | ||
|  | 
 | ||
|  |   const providerRuntmie = async ({ addProvider }) => { | ||
|  |     const Provider = ({ children }) => { | ||
|  |       return <div>{children}</div>; | ||
|  |     }; | ||
|  |     addProvider(Provider); | ||
|  |     // Add twice.
 | ||
|  |     addProvider(Provider); | ||
|  |   }; | ||
|  | 
 | ||
|  |   const basicRoutes = [ | ||
|  |     { | ||
|  |       id: 'home', | ||
|  |       path: '/', | ||
|  |       componentName: 'Home', | ||
|  |       load: async () => ({ | ||
|  |         default: () => { | ||
|  |           // eslint-disable-next-line react-hooks/rules-of-hooks
 | ||
|  |           const appData = useAppData(); | ||
|  |           return ( | ||
|  |             <div>home{appData?.msg || ''}</div> | ||
|  |           ); | ||
|  |         }, | ||
|  |         getConfig: () => ({ title: 'home' }), | ||
|  |         getData: async () => ({ data: 'test' }), | ||
|  |       }), | ||
|  |     }, | ||
|  |   ]; | ||
|  | 
 | ||
|  |   it('run client basic', async () => { | ||
|  |     windowSpy.mockImplementation(() => ({ | ||
|  |       ...mockData, | ||
|  |       location: new URL('http://localhost:4000/?test=1&runtime=true&baisc'), | ||
|  |     })); | ||
|  | 
 | ||
|  |     await runClientApp({ | ||
|  |       app: {}, | ||
|  |       routes: basicRoutes, | ||
|  |       runtimeModules: [serverRuntime], | ||
|  |       hydrate: false, | ||
|  |     }); | ||
|  |     expect(domstring).toBe('<div>home</div>'); | ||
|  |   }); | ||
|  | 
 | ||
|  |   it('run client single-router', async () => { | ||
|  |     process.env.ICE_CORE_ROUTER = ''; | ||
|  |     await runClientApp({ | ||
|  |       app: {}, | ||
|  |       routes: basicRoutes, | ||
|  |       runtimeModules: [serverRuntime], | ||
|  |       hydrate: false, | ||
|  |     }); | ||
|  |     process.env.ICE_CORE_ROUTER = 'true'; | ||
|  |     expect(domstring).toBe('<div>home</div>'); | ||
|  |   }); | ||
|  | 
 | ||
|  |   it('run client with wrapper', async () => { | ||
|  |     await runClientApp({ | ||
|  |       app: {}, | ||
|  |       routes: basicRoutes, | ||
|  |       runtimeModules: [serverRuntime, wrapperRuntime], | ||
|  |       hydrate: true, | ||
|  |     }); | ||
|  |     expect(domstring).toBe('<div><div>home</div></div>'); | ||
|  |   }); | ||
|  | 
 | ||
|  |   it('run client with app provider', async () => { | ||
|  |     await runClientApp({ | ||
|  |       app: {}, | ||
|  |       routes: basicRoutes, | ||
|  |       runtimeModules: [serverRuntime, providerRuntmie], | ||
|  |       hydrate: true, | ||
|  |     }); | ||
|  |     expect(domstring).toBe('<div><div><div>home</div></div></div>'); | ||
|  |   }); | ||
|  | 
 | ||
|  |   it('run client with empty route', async () => { | ||
|  |     await runClientApp({ | ||
|  |       app: {}, | ||
|  |       routes: [], | ||
|  |       runtimeModules: [serverRuntime], | ||
|  |       hydrate: false, | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   it('run client with memory router', async () => { | ||
|  |     const routes = [...basicRoutes, { | ||
|  |       id: 'about', | ||
|  |       path: '/about', | ||
|  |       componentName: 'About', | ||
|  |       load: async () => ({ | ||
|  |         default: () => { | ||
|  |           return ( | ||
|  |             <div>about</div> | ||
|  |           ); | ||
|  |         }, | ||
|  |       }), | ||
|  |     }]; | ||
|  |     await runClientApp({ | ||
|  |       app: { | ||
|  |         default: { | ||
|  |           router: { | ||
|  |             type: 'memory', | ||
|  |             initialEntries: ['/about'], | ||
|  |           }, | ||
|  |         }, | ||
|  |       }, | ||
|  |       routes, | ||
|  |       runtimeModules: [serverRuntime], | ||
|  |       hydrate: true, | ||
|  |     }); | ||
|  | 
 | ||
|  |     expect(domstring).toBe('<div>about</div>'); | ||
|  |     await runClientApp({ | ||
|  |       app: { | ||
|  |         default: { | ||
|  |         }, | ||
|  |       }, | ||
|  |       routes, | ||
|  |       runtimeModules: [serverRuntime], | ||
|  |       hydrate: true, | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   it('run client with memory router - from context', async () => { | ||
|  |     windowSpy.mockImplementation(() => ({ | ||
|  |       ...mockData, | ||
|  |       __ICE_APP_CONTEXT__: { | ||
|  |         routePath: '/about', | ||
|  |       }, | ||
|  |     })); | ||
|  |     const routes = [...basicRoutes, { | ||
|  |       id: 'about', | ||
|  |       path: '/about', | ||
|  |       componentName: 'About', | ||
|  |       load: async () => ({ | ||
|  |         default: () => { | ||
|  |           return ( | ||
|  |             <div>about</div> | ||
|  |           ); | ||
|  |         }, | ||
|  |       }), | ||
|  |     }]; | ||
|  |     await runClientApp({ | ||
|  |       app: { | ||
|  |       }, | ||
|  |       routes, | ||
|  |       runtimeModules: [serverRuntime], | ||
|  |       hydrate: true, | ||
|  |       memoryRouter: true, | ||
|  |     }); | ||
|  | 
 | ||
|  |     expect(domstring).toBe('<div>about</div>'); | ||
|  |   }); | ||
|  | 
 | ||
|  |   it('run client with hash router', async () => { | ||
|  |     await runClientApp({ | ||
|  |       app: { | ||
|  |         default: { | ||
|  |           router: { | ||
|  |             type: 'hash', | ||
|  |           }, | ||
|  |         }, | ||
|  |       }, | ||
|  |       routes: basicRoutes, | ||
|  |       runtimeModules: [serverRuntime], | ||
|  |       hydrate: true, | ||
|  |     }); | ||
|  |     expect(domstring).toBe('<div>home</div>'); | ||
|  |   }); | ||
|  | 
 | ||
|  |   it('run client with app data', async () => { | ||
|  |     let executed = false; | ||
|  |     await runClientApp({ | ||
|  |       app: { | ||
|  |         getAppData: async () => { | ||
|  |           executed = true; | ||
|  |           return { msg: '-getAppData' }; | ||
|  |         }, | ||
|  |       }, | ||
|  |       routes: basicRoutes, | ||
|  |       runtimeModules: [serverRuntime], | ||
|  |       hydrate: false, | ||
|  |     }); | ||
|  |     expect(domstring).toBe('<div>home<!-- -->-getAppData</div>'); | ||
|  |     expect(executed).toBe(true); | ||
|  |   }); | ||
|  | 
 | ||
|  |   it('run client with app data', async () => { | ||
|  |     let useGlobalLoader = false; | ||
|  |     let executed = false; | ||
|  |     windowSpy.mockImplementation(() => ({ | ||
|  |       ...mockData, | ||
|  |       __ICE_DATA_LOADER__: async () => { | ||
|  |         useGlobalLoader = true; | ||
|  |         return { msg: '-globalData' }; | ||
|  |       }, | ||
|  |     })); | ||
|  | 
 | ||
|  |     await runClientApp({ | ||
|  |       app: { | ||
|  |         getAppData: async () => { | ||
|  |           executed = true; | ||
|  |           return { msg: 'app' }; | ||
|  |         }, | ||
|  |       }, | ||
|  |       routes: basicRoutes, | ||
|  |       runtimeModules: [serverRuntime], | ||
|  |       hydrate: false, | ||
|  |     }); | ||
|  |     expect(executed).toBe(false); | ||
|  |     expect(useGlobalLoader).toBe(true); | ||
|  |     expect(domstring).toBe('<div>home<!-- -->-globalData</div>'); | ||
|  |   }); | ||
|  | 
 | ||
|  |   it('run client with AppErrorBoundary', async () => { | ||
|  |     await runClientApp({ | ||
|  |       app: { | ||
|  |         default: { | ||
|  |           app: { | ||
|  |             errorBoundary: true, | ||
|  |           }, | ||
|  |         }, | ||
|  |       }, | ||
|  |       routes: [{ | ||
|  |         id: 'home', | ||
|  |         path: '/', | ||
|  |         componentName: 'Home', | ||
|  |         load: async () => ({ | ||
|  |           default: () => { | ||
|  |             // eslint-disable-next-line react-hooks/rules-of-hooks
 | ||
|  |             const config = useConfig(); | ||
|  |             // eslint-disable-next-line react-hooks/rules-of-hooks
 | ||
|  |             const data = useData(); | ||
|  |             return ( | ||
|  |               <div>home{data?.data}{config.title}</div> | ||
|  |             ); | ||
|  |           }, | ||
|  |           getConfig: () => ({ title: 'home' }), | ||
|  |           getData: async () => ({ data: 'test' }), | ||
|  |         }), | ||
|  |       }], | ||
|  |       runtimeModules: [serverRuntime], | ||
|  |       hydrate: false, | ||
|  |     }); | ||
|  |     expect(domstring).toBe('<div>home<!-- -->test<!-- -->home</div>'); | ||
|  |   }); | ||
|  | 
 | ||
|  |   it('load next page', async () => { | ||
|  |     const homePage = { | ||
|  |       default: () => <></>, | ||
|  |       getConfig: () => ({ title: 'home' }), | ||
|  |       getData: async () => ({ type: 'getDataHome' }), | ||
|  |     }; | ||
|  |     const aboutPage = { | ||
|  |       default: () => <></>, | ||
|  |       getConfig: () => ({ title: 'about' }), | ||
|  |       getData: async () => ({ type: 'getDataAbout' }), | ||
|  |     }; | ||
|  |     const mockedModules = [ | ||
|  |       { | ||
|  |         id: 'home', | ||
|  |         load: async () => { | ||
|  |           return homePage; | ||
|  |         }, | ||
|  |       }, | ||
|  |       { | ||
|  |         id: 'about', | ||
|  |         load: async () => { | ||
|  |           return aboutPage; | ||
|  |         }, | ||
|  |       }, | ||
|  |     ]; | ||
|  |     const { routesData, routesConfig, routeModules } = await loadNextPage( | ||
|  |       // @ts-ignore
 | ||
|  |       [{ route: mockedModules[0] }], | ||
|  |       { | ||
|  |         // @ts-ignore
 | ||
|  |         matches: [{ route: mockedModules[1] }], | ||
|  |         routesData: {}, | ||
|  |         routeModules: {}, | ||
|  |       }, | ||
|  |     ); | ||
|  |     expect(routesData).toStrictEqual({ | ||
|  |       home: { type: 'getDataHome' }, | ||
|  |     }); | ||
|  |     expect(routesConfig).toStrictEqual({ | ||
|  |       home: { | ||
|  |         title: 'home', | ||
|  |       }, | ||
|  |     }); | ||
|  |     expect(routeModules).toStrictEqual({ | ||
|  |       home: homePage, | ||
|  |     }); | ||
|  |   }); | ||
|  | }); |