Skip to content

Commit d9eff50

Browse files
authored
fix: Ensure loadEnd is called even without route navigation (#14)
* fix: Call `loadEnd` even if route hasn't changed * test: Add tests for loadStart/End & routeChange * test: Simplify regression test
1 parent 1647bd7 commit d9eff50

File tree

2 files changed

+147
-2
lines changed

2 files changed

+147
-2
lines changed

src/router.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,13 @@ export function Router(props) {
186186
// The route is loaded and rendered.
187187
if (prevRoute.current !== path) {
188188
if (wasPush) scrollTo(0, 0);
189-
if (props.onLoadEnd && isLoading.current) props.onLoadEnd(url);
190189
if (props.onRouteChange) props.onRouteChange(url);
191190

192-
isLoading.current = false;
193191
prevRoute.current = path;
194192
}
193+
194+
if (props.onLoadEnd && isLoading.current) props.onLoadEnd(url);
195+
isLoading.current = false;
195196
}, [path, wasPush, c]);
196197

197198
// Note: curChildren MUST render first in order to set didSuspend & prev.

test/router.test.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,150 @@ describe('Router', () => {
306306
expect(A).toHaveBeenCalledWith({ path: '/', query: {}, params: {}, rest: '' }, expect.anything());
307307
});
308308

309+
it('should support onLoadStart/onLoadEnd/onRouteChange w/out navigation', async () => {
310+
const route = name => html`
311+
<h1>${name}</h1>
312+
<p>hello</p>
313+
`;
314+
const A = jest.fn(groggy(() => route('A'), 1));
315+
const loadStart = jest.fn();
316+
const loadEnd = jest.fn();
317+
const routeChange = jest.fn();
318+
render(
319+
html`
320+
<${ErrorBoundary}>
321+
<${LocationProvider}>
322+
<${Router}
323+
onLoadStart=${loadStart}
324+
onLoadEnd=${loadEnd}
325+
onRouteChange=${routeChange}
326+
>
327+
<${A} path="/" />
328+
<//>
329+
<//>
330+
<//>
331+
`,
332+
scratch
333+
);
334+
335+
expect(scratch).toHaveProperty('innerHTML', '');
336+
expect(A).toHaveBeenCalledWith({ path: '/', query: {}, params: {}, rest: '' }, expect.anything());
337+
expect(loadStart).toHaveBeenCalledWith('/');
338+
expect(loadEnd).not.toHaveBeenCalled();
339+
expect(routeChange).not.toHaveBeenCalled();
340+
341+
A.mockClear();
342+
loadStart.mockClear();
343+
loadEnd.mockClear();
344+
routeChange.mockClear();
345+
await sleep(10);
346+
347+
expect(scratch).toHaveProperty('innerHTML', '<h1>A</h1><p>hello</p>');
348+
expect(A).toHaveBeenCalledWith({ path: '/', query: {}, params: {}, rest: '' }, expect.anything());
349+
expect(loadStart).not.toHaveBeenCalled();
350+
expect(loadEnd).toHaveBeenCalledWith('/');
351+
expect(routeChange).not.toHaveBeenCalled();
352+
});
353+
354+
it('should support onLoadStart/onLoadEnd/onRouteChange w/ navigation', async () => {
355+
const route = name => html`
356+
<h1>${name}</h1>
357+
<p>hello</p>
358+
`;
359+
const A = jest.fn(groggy(() => route('A'), 1));
360+
const B = jest.fn(groggy(() => route('B'), 1));
361+
const loadStart = jest.fn();
362+
const loadEnd = jest.fn();
363+
const routeChange = jest.fn();
364+
let loc;
365+
render(
366+
html`
367+
<${ErrorBoundary}>
368+
<${LocationProvider}>
369+
<${Router}
370+
onLoadStart=${loadStart}
371+
onLoadEnd=${loadEnd}
372+
onRouteChange=${routeChange}
373+
>
374+
<${A} path="/" />
375+
<${B} path="/b" />
376+
<//>
377+
<${() => {
378+
loc = useLocation();
379+
}} />
380+
<//>
381+
<//>
382+
`,
383+
scratch
384+
);
385+
386+
await sleep(10);
387+
388+
A.mockClear();
389+
loadStart.mockClear();
390+
loadEnd.mockClear();
391+
routeChange.mockClear();
392+
393+
loc.route('/b');
394+
395+
await sleep(1);
396+
397+
expect(loadStart).toHaveBeenCalledWith('/b');
398+
expect(loadEnd).not.toHaveBeenCalled();
399+
expect(routeChange).not.toHaveBeenCalled();
400+
401+
A.mockClear();
402+
loadStart.mockClear();
403+
loadEnd.mockClear();
404+
routeChange.mockClear();
405+
406+
await sleep(10);
407+
408+
expect(scratch).toHaveProperty('innerHTML', '<h1>B</h1><p>hello</p>');
409+
expect(loadStart).not.toHaveBeenCalled();
410+
expect(loadEnd).toHaveBeenCalledWith('/b');
411+
expect(routeChange).toHaveBeenCalledWith('/b');
412+
});
413+
414+
it('should only call onLoadEnd once upon promise flush', async () => {
415+
const route = name => html`
416+
<h1>${name}</h1>
417+
<p>hello</p>
418+
`;
419+
const A = jest.fn(groggy(() => route('A'), 1));
420+
const loadEnd = jest.fn();
421+
let set;
422+
423+
const App = () => {
424+
const [test, setTest] = useState('1');
425+
set = setTest;
426+
return html`
427+
<${ErrorBoundary}>
428+
<${LocationProvider}>
429+
<${Router}
430+
onLoadEnd=${loadEnd}
431+
>
432+
<${A} path="/" />
433+
<//>
434+
<//>
435+
<//>
436+
`;
437+
}
438+
render(
439+
html`<${App} />`,
440+
scratch
441+
);
442+
443+
await sleep(10);
444+
445+
loadEnd.mockClear();
446+
447+
set('2');
448+
await sleep(1);
449+
450+
expect(loadEnd).not.toHaveBeenCalled();
451+
});
452+
309453
describe('intercepted VS external links', () => {
310454
const shouldIntercept = [null, '', '_self', 'self', '_SELF'];
311455
const shouldNavigate = ['_top', '_parent', '_blank', 'custom', '_BLANK'];

0 commit comments

Comments
 (0)