packages/recoil-sync/__test_utils__/RecoilSync_MockURLSerialization.js (113 lines of code) (raw):

/** * Copyright (c) Facebook, Inc. and its affiliates. Confidential and proprietary. * * @emails oncall+recoil * @flow strict-local * @format */ 'use strict'; import type {BrowserInterface, LocationOption} from '../RecoilSync_URL'; const {useRecoilURLSync} = require('../RecoilSync_URL'); const React = require('react'); const {useCallback} = require('react'); const { flushPromisesAndTimers, } = require('recoil-shared/__test_utils__/Recoil_TestingUtils'); const nullthrows = require('recoil-shared/util/Recoil_nullthrows'); // //////////////////////////// // // Mock Serialization // //////////////////////////// function TestURLSync({ storeKey, location, browserInterface, }: { storeKey?: string, location: LocationOption, browserInterface?: BrowserInterface, }): React.Node { const serialize = useCallback( items => { const str = nullthrows(JSON.stringify(items)); return location.part === 'href' ? `/TEST#${encodeURIComponent(str)}` : str; }, [location.part], ); const deserialize = useCallback( str => { const stateStr = location.part === 'href' ? decodeURIComponent(str.split('#')[1]) : str; // Skip the default URL parts which don't conform to the serialized standard. // 'bar' also doesn't conform, but we want to test coexistence of foreign // query parameters. if (stateStr == null || stateStr === 'anchor' || stateStr === 'foo=bar') { return {}; } return JSON.parse(stateStr); }, [location.part], ); useRecoilURLSync({ storeKey, location, serialize, deserialize, browserInterface, }); return null; } function encodeState(obj) { return encodeURIComponent(JSON.stringify(obj)); } function encodeURLPart(href: string, loc: LocationOption, obj): string { const url = new URL(href); switch (loc.part) { case 'href': url.pathname = '/TEST'; url.hash = encodeState(obj); break; case 'hash': url.hash = encodeState(obj); break; case 'search': { url.search = encodeState(obj); break; } case 'queryParams': { const {param} = loc; const {searchParams} = url; if (param != null) { searchParams.set(param, JSON.stringify(obj)); } else { for (const [key, value] of Object.entries(obj)) { searchParams.set(key, JSON.stringify(value) ?? ''); } } url.search = searchParams.toString(); break; } } return url.href; } function encodeURL( parts: Array<[LocationOption, {...}]>, url: string = window.location.href, ): string { let href = url; for (const [loc, obj] of parts) { href = encodeURLPart(href, loc, obj); } return href; } function expectURL( parts: Array<[LocationOption, {...}]>, url: string = window.location.href, ) { expect(url).toBe(encodeURL(parts, url)); } async function gotoURL(parts: Array<[LocationOption, {...}]>) { history.replaceState(null, '', encodeURL(parts)); history.pushState(null, '', '/POPSTATE'); history.back(); await flushPromisesAndTimers(); } async function goBack() { history.back(); await flushPromisesAndTimers(); } module.exports = { TestURLSync, encodeURL, expectURL, gotoURL, goBack, };