lib/router/src/utils.test.ts
import { buildArgsParam, deepDiff, DEEPLY_EQUAL, getMatch, parsePath } from './utils';
jest.mock('@storybook/client-logger', () => ({
once: { warn: jest.fn() },
}));
describe('getMatch', () => {
it('gets startsWithTarget match', () => {
const output = getMatch('/foo/bar', '/foo', true);
expect(output).toEqual({
path: '/foo/bar',
});
});
it('gets currentIsTarget match', () => {
const output = getMatch('/foo', '/foo', false);
expect(output).toEqual({
path: '/foo',
});
});
it('gets matchTarget match', () => {
const output = getMatch('/foo', '/f.+', false);
expect(output).toEqual({
path: '/foo',
});
});
it('returns null match', () => {
const output = getMatch('/foo/bar', '/foo/baz', true);
expect(output).toBe(null);
});
});
describe('parsePath', () => {
it('should work without path', () => {
const output = parsePath(undefined);
expect(output).toEqual({
viewMode: undefined,
storyId: undefined,
refId: undefined,
});
});
it('should parse /foo/bar correctly', () => {
const output = parsePath('/foo/bar');
expect(output).toMatchObject({
viewMode: 'foo',
storyId: 'bar',
});
});
it('should parse /foo/bar/x correctly', () => {
const output = parsePath('/foo/bar/x');
expect(output).toMatchObject({
viewMode: 'foo',
storyId: 'bar',
});
});
it('should parse /viewMode/refId_story--id correctly', () => {
const output = parsePath('/viewMode/refId_story--id');
expect(output).toMatchObject({
viewMode: 'viewmode',
storyId: 'story--id',
refId: 'refid',
});
});
});
describe('deepDiff', () => {
it('returns DEEPLY_EQUAL when the values are deeply equal', () => {
expect(deepDiff({ foo: [{ bar: 1 }] }, { foo: [{ bar: 1 }] })).toBe(DEEPLY_EQUAL);
});
it('returns the update when the types are different', () => {
expect(deepDiff(true, 1)).toBe(1);
});
it('returns a sparse array when updating an array', () => {
// eslint-disable-next-line no-sparse-arrays
expect(deepDiff([1, 2], [1, 3])).toStrictEqual([, 3]);
});
it('returns undefined for removed array values', () => {
// eslint-disable-next-line no-sparse-arrays
expect(deepDiff([1, 2], [1])).toStrictEqual([, undefined]);
});
it('returns a longer array when adding to an array', () => {
// eslint-disable-next-line no-sparse-arrays
expect(deepDiff([1, 2], [1, 2, 3])).toStrictEqual([, , 3]);
});
it('returns a partial when updating an object', () => {
expect(deepDiff({ foo: 1, bar: 2 }, { foo: 1, bar: 3 })).toStrictEqual({ bar: 3 });
});
it('returns undefined for omitted object properties', () => {
expect(deepDiff({ foo: 1, bar: 2 }, { foo: 1 })).toStrictEqual({ bar: undefined });
});
it('traverses into objects', () => {
expect(deepDiff({ foo: { bar: [1, 2], baz: [3, 4] } }, { foo: { bar: [3] } })).toStrictEqual({
foo: { bar: [3, undefined], baz: undefined },
});
});
});
describe('buildArgsParam', () => {
it('builds a simple key-value pair', () => {
const param = buildArgsParam({}, { key: 'val' });
expect(param).toEqual('key:val');
});
it('builds multiple values', () => {
const param = buildArgsParam({}, { one: '1', two: '2', three: '3' });
expect(param).toEqual('one:1;two:2;three:3');
});
it('builds booleans', () => {
const param = buildArgsParam({}, { yes: true, no: false });
expect(param).toEqual('yes:true;no:false');
});
it('builds arrays', () => {
const param = buildArgsParam({}, { arr: ['1', '2', '3'] });
expect(param).toEqual('arr[0]:1;arr[1]:2;arr[2]:3');
});
it('builds sparse arrays', () => {
// eslint-disable-next-line no-sparse-arrays
const param = buildArgsParam({}, { arr: ['1', , '3'] });
expect(param).toEqual('arr[0]:1;arr[2]:3');
});
it('builds simple objects', () => {
const param = buildArgsParam({}, { obj: { one: '1', two: '2' } });
expect(param).toEqual('obj.one:1;obj.two:2');
});
it('builds nested objects', () => {
const param = buildArgsParam({}, { obj: { foo: { one: '1', two: '2' }, bar: { one: '1' } } });
expect(param).toEqual('obj.foo.one:1;obj.foo.two:2;obj.bar.one:1');
});
it('builds arrays in objects', () => {
// eslint-disable-next-line no-sparse-arrays
const param = buildArgsParam({}, { obj: { foo: ['1', , '3'] } });
expect(param).toEqual('obj.foo[0]:1;obj.foo[2]:3');
});
it('builds single object in array', () => {
const param = buildArgsParam({}, { arr: [{ one: '1', two: '2' }] });
expect(param).toEqual('arr[0].one:1;arr[0].two:2');
});
it('builds multiple objects in array', () => {
const param = buildArgsParam({}, { arr: [{ one: '1' }, { two: '2' }] });
expect(param).toEqual('arr[0].one:1;arr[1].two:2');
});
it('builds nested object in array', () => {
const param = buildArgsParam({}, { arr: [{ foo: { bar: 'val' } }] });
expect(param).toEqual('arr[0].foo.bar:val');
});
it('encodes space as +', () => {
const param = buildArgsParam({}, { key: 'foo bar baz' });
expect(param).toEqual('key:foo+bar+baz');
});
it('encodes null values as !null', () => {
const param = buildArgsParam({}, { key: null });
expect(param).toEqual('key:!null');
});
it('encodes nested null values as !null', () => {
const param = buildArgsParam({}, { foo: { bar: [{ key: null }], baz: null } });
expect(param).toEqual('foo.bar[0].key:!null;foo.baz:!null');
});
it('encodes hex color values as !hex(value)', () => {
const param = buildArgsParam({}, { key: '#ff4785' });
expect(param).toEqual('key:!hex(ff4785)');
});
it('encodes rgba color values by prefixing and compacting', () => {
const param = buildArgsParam({}, { rgb: 'rgb(255, 71, 133)', rgba: 'rgba(255, 71, 133, 0.5)' });
expect(param).toEqual('rgb:!rgb(255,71,133);rgba:!rgba(255,71,133,0.5)');
});
it('encodes hsla color values by prefixing and compacting', () => {
const param = buildArgsParam({}, { hsl: 'hsl(45, 99%, 70%)', hsla: 'hsla(45, 99%, 70%, 0.5)' });
expect(param).toEqual('hsl:!hsl(45,99,70);hsla:!hsla(45,99,70,0.5)');
});
it('encodes Date objects as !date(ISO string)', () => {
const param = buildArgsParam({}, { key: new Date('2001-02-03T04:05:06.789Z') });
expect(param).toEqual('key:!date(2001-02-03T04:05:06.789Z)');
});
describe('with initial state', () => {
it('omits unchanged values', () => {
const param = buildArgsParam({ one: 1 }, { one: 1, two: 2 });
expect(param).toEqual('two:2');
});
it('omits unchanged object properties', () => {
const param = buildArgsParam({ obj: { one: 1 } }, { obj: { one: 1, two: 2 } });
expect(param).toEqual('obj.two:2');
});
it('sets !undefined for removed array values', () => {
const param = buildArgsParam({ arr: [1] }, { arr: [] });
expect(param).toEqual('arr[0]:!undefined');
});
it('sets !undefined for removed object properties', () => {
const param = buildArgsParam({ obj: { one: 1 } }, { obj: {} });
expect(param).toEqual('obj.one:!undefined');
});
it('omits unchanged array values (yielding sparse arrays)', () => {
const param = buildArgsParam({ arr: [1, 2, 3] }, { arr: [1, 3, 4] });
expect(param).toEqual('arr[1]:3;arr[2]:4');
});
it('omits nested unchanged object properties and array values', () => {
const param = buildArgsParam(
{ obj: { nested: [{ one: 1 }, { two: 2 }] } },
{ obj: { nested: [{ one: 1 }, { two: 2, three: 3 }] } }
);
expect(param).toEqual('obj.nested[1].three:3');
});
});
});