|
1 | 1 | import { jest, describe, it, beforeEach, expect } from '@jest/globals';
|
2 |
| -import { h, render } from 'preact'; |
| 2 | +import { h, hydrate, options, render } from 'preact'; |
3 | 3 | import { useState } from 'preact/hooks';
|
4 | 4 | import { html } from 'htm/preact';
|
5 | 5 | import { LocationProvider, Router, useLocation, Route, useRoute } from '../src/router.js';
|
@@ -760,3 +760,82 @@ describe('Router', () => {
|
760 | 760 | replaceState.mockRestore();
|
761 | 761 | });
|
762 | 762 | });
|
| 763 | + |
| 764 | +describe('hydration', () => { |
| 765 | + let scratch; |
| 766 | + beforeEach(() => { |
| 767 | + if (scratch) { |
| 768 | + render(null, scratch); |
| 769 | + scratch.remove(); |
| 770 | + } |
| 771 | + scratch = document.createElement('scratch'); |
| 772 | + document.body.appendChild(scratch); |
| 773 | + history.replaceState(null, null, '/'); |
| 774 | + }); |
| 775 | + |
| 776 | + it.only('should wait for asynchronous routes', async () => { |
| 777 | + scratch.innerHTML = '<div><h1>A</h1><p>hello</p></div>'; |
| 778 | + const route = name => html` |
| 779 | + <div> |
| 780 | + <h1>${name}</h1> |
| 781 | + <p>hello</p> |
| 782 | + </div> |
| 783 | + `; |
| 784 | + const A = jest.fn(groggy(() => route('A'), 1)); |
| 785 | + let loc; |
| 786 | + hydrate( |
| 787 | + html` |
| 788 | + <${ErrorBoundary}> |
| 789 | + <${LocationProvider}> |
| 790 | + <${Router}> |
| 791 | + <${A} path="/" /> |
| 792 | + <//> |
| 793 | + <${() => { |
| 794 | + loc = useLocation(); |
| 795 | + }} /> |
| 796 | + <//> |
| 797 | + <//> |
| 798 | + `, |
| 799 | + scratch |
| 800 | + ); |
| 801 | + |
| 802 | + const mutations = []; |
| 803 | + const mutationObserver = new MutationObserver((x) => { |
| 804 | + mutations.push(...x) |
| 805 | + }); |
| 806 | + mutationObserver.observe(scratch, { childList: true, subtree: true }); |
| 807 | + |
| 808 | + expect(scratch).toHaveProperty('innerHTML', '<div><h1>A</h1><p>hello</p></div>'); |
| 809 | + expect(A).toHaveBeenCalledWith({ path: '/', query: {}, params: {}, rest: '' }, expect.anything()); |
| 810 | + const oldOptionsVnode = options.__b; |
| 811 | + let hasMatched = false; |
| 812 | + options.__b = (vnode) => { |
| 813 | + if (vnode.type === A && !hasMatched) { |
| 814 | + hasMatched = true; |
| 815 | + if (vnode.__ && vnode.__.__h) { |
| 816 | + expect(vnode.__.__h).toBe(true) |
| 817 | + } else if (vnode.__ && vnode.__.__u) { |
| 818 | + expect(!!(vnode.__.__u & MODE_SUSPENDED)).toBe(true); |
| 819 | + expect(!!(vnode.__.__u & MODE_HYDRATE)).toBe(true); |
| 820 | + } else { |
| 821 | + expect(true).toBe(false); |
| 822 | + } |
| 823 | + } |
| 824 | + |
| 825 | + if (oldOptionsVnode) { |
| 826 | + oldOptionsVnode(vnode); |
| 827 | + } |
| 828 | + } |
| 829 | + A.mockClear(); |
| 830 | + await sleep(10); |
| 831 | + |
| 832 | + expect(scratch).toHaveProperty('innerHTML', '<div><h1>A</h1><p>hello</p></div>'); |
| 833 | + expect(A).toHaveBeenCalledWith({ path: '/', query: {}, params: {}, rest: '' }, expect.anything()); |
| 834 | + expect(mutations).toHaveLength(0); |
| 835 | + |
| 836 | + options.__b = oldOptionsVnode; |
| 837 | + }); |
| 838 | +}) |
| 839 | + |
| 840 | +const MODE_HYDRATE = 1 << 5; |
| 841 | +const MODE_SUSPENDED = 1 << 7; |
0 commit comments