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,
};