Skip to content

Commit

Permalink
jest mocking improvements, type fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Dosant committed Nov 28, 2019
1 parent db03365 commit 4b95bc8
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 56 deletions.
4 changes: 1 addition & 3 deletions src/legacy/ui/public/state_management/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon

// We need to strip out Angular-specific properties.
const json = angular.toJson(state);
const hash = createStateHash(json, hash => {
return this._hashedItemStore.getItem(hash);
});
const hash = createStateHash(json);
const isItemSet = this._hashedItemStore.setItem(hash, json);

if (isItemSet) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,21 @@
* under the License.
*/

import { StubBrowserStorage } from 'test_utils/stub_browser_storage';
import { mockSessionStorage } from '../state_storage/mock';
import { HashedItemStore } from '../state_storage/hashed_item_store';
const originalSessionStorage: Storage = (window as any).sessionStorage;
function mockSessionStorage() {
(window.sessionStorage as any) = new StubBrowserStorage();
}
function restoreSessionStorage() {
(window.sessionStorage as any) = originalSessionStorage;
}
mockSessionStorage();
import { hashUrl, unhashUrl } from './hash_unhash_url';

describe('hash unhash url', () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { hashUrl, unhashUrl } = require('./hash_unhash_url');

afterAll(() => {
restoreSessionStorage();
});

beforeEach(() => {
window.sessionStorage.clear();
(window.sessionStorage as StubBrowserStorage).setStubbedSizeLimit(5000000);
mockSessionStorage.clear();
mockSessionStorage.setStubbedSizeLimit(5000000);
});

describe('hash url', () => {
describe('does nothing', () => {
it('if missing input', () => {
expect(() => {
// @ts-ignore
hashUrl();
}).not.toThrowError();
});
Expand Down Expand Up @@ -109,8 +96,8 @@ describe('hash unhash url', () => {
expect(result).toMatchInlineSnapshot(
`"https://localhost:5601/app/kibana#/discover?foo=bar&_g=h@4e60e02"`
);
expect(sessionStorage.getItem('kbn.hashedItemsIndex.v1')).toBeTruthy();
expect(sessionStorage.getItem('h@4e60e02')).toEqual(JSON.stringify({ yes: true }));
expect(mockSessionStorage.getItem('kbn.hashedItemsIndex.v1')).toBeTruthy();
expect(mockSessionStorage.getItem('h@4e60e02')).toEqual(JSON.stringify({ yes: true }));
});

it('if uses multiple states params', () => {
Expand All @@ -125,15 +112,15 @@ describe('hash unhash url', () => {
expect(result).toMatchInlineSnapshot(
`"https://localhost:5601/app/kibana#/discover?foo=bar&_g=h@4e60e02&_a=h@61fa078&_b=(yes:!f)"`
);
expect(sessionStorage.getItem('h@4e60e02')).toEqual(JSON.stringify({ yes: true }));
expect(sessionStorage.getItem('h@61fa078')).toEqual(JSON.stringify({ yes: false }));
expect(mockSessionStorage.getItem('h@4e60e02')).toEqual(JSON.stringify({ yes: true }));
expect(mockSessionStorage.getItem('h@61fa078')).toEqual(JSON.stringify({ yes: false }));
if (!HashedItemStore.PERSISTED_INDEX_KEY) {
// This is very brittle and depends upon HashedItemStore implementation details,
// so let's protect ourselves from accidentally breaking this test.
throw new Error('Missing HashedItemStore.PERSISTED_INDEX_KEY');
}
expect(sessionStorage.getItem(HashedItemStore.PERSISTED_INDEX_KEY)).toBeTruthy();
expect(sessionStorage.length).toBe(3);
expect(mockSessionStorage.getItem(HashedItemStore.PERSISTED_INDEX_KEY)).toBeTruthy();
expect(mockSessionStorage.length).toBe(3);
});

it('hashes only whitelisted properties', () => {
Expand All @@ -149,14 +136,14 @@ describe('hash unhash url', () => {
`"https://localhost:5601/app/kibana#/discover?foo=bar&_g=h@4e60e02&_a=h@61fa078&_someother=(yes:!f)"`
);

expect(sessionStorage.length).toBe(3); // 2 hashes + HashedItemStoreSingleton.PERSISTED_INDEX_KEY
expect(mockSessionStorage.length).toBe(3); // 2 hashes + HashedItemStoreSingleton.PERSISTED_INDEX_KEY
});
});

it('throws error if unable to hash url', () => {
const stateParamKey1 = '_g';
const stateParamValue1 = '(yes:!t)';
(window.sessionStorage as StubBrowserStorage).setStubbedSizeLimit(1);
mockSessionStorage.setStubbedSizeLimit(1);

const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey1}=${stateParamValue1}`;
expect(() => hashUrl(url)).toThrowError();
Expand All @@ -167,7 +154,7 @@ describe('hash unhash url', () => {
describe('does nothing', () => {
it('if missing input', () => {
expect(() => {
unhashUrl();
// @ts-ignore
}).not.toThrowError();
});

Expand Down Expand Up @@ -219,7 +206,7 @@ describe('hash unhash url', () => {
const stateParamKey = '_g';
const stateParamValueHashed = 'h@4e60e02';
const state = { yes: true };
sessionStorage.setItem(stateParamValueHashed, JSON.stringify(state));
mockSessionStorage.setItem(stateParamValueHashed, JSON.stringify(state));

const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey}=${stateParamValueHashed}`;
const result = unhashUrl(url);
Expand All @@ -237,8 +224,8 @@ describe('hash unhash url', () => {
const stateParamValueHashed2 = 'h@61fa078';
const state2 = { yes: false };

sessionStorage.setItem(stateParamValueHashed1, JSON.stringify(state1));
sessionStorage.setItem(stateParamValueHashed2, JSON.stringify(state2));
mockSessionStorage.setItem(stateParamValueHashed1, JSON.stringify(state1));
mockSessionStorage.setItem(stateParamValueHashed2, JSON.stringify(state2));

const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey1}=${stateParamValueHashed1}&${stateParamKey2}=${stateParamValueHashed2}`;
const result = unhashUrl(url);
Expand All @@ -260,9 +247,9 @@ describe('hash unhash url', () => {
const stateParamValueHashed3 = 'h@61fa078';
const state3 = { yes: false };

sessionStorage.setItem(stateParamValueHashed1, JSON.stringify(state1));
sessionStorage.setItem(stateParamValueHashed2, JSON.stringify(state2));
sessionStorage.setItem(stateParamValueHashed3, JSON.stringify(state3));
mockSessionStorage.setItem(stateParamValueHashed1, JSON.stringify(state1));
mockSessionStorage.setItem(stateParamValueHashed2, JSON.stringify(state2));
mockSessionStorage.setItem(stateParamValueHashed3, JSON.stringify(state3));

const url = `https://localhost:5601/app/kibana#/discover?foo=bar&${stateParamKey1}=${stateParamValueHashed1}&${stateParamKey2}=${stateParamValueHashed2}&${stateParamKey3}=${stateParamValueHashed3}`;
const result = unhashUrl(url);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,13 @@
*/

import { encode as encodeRison } from 'rison-node';
import { StubBrowserStorage } from 'test_utils/stub_browser_storage';
const originalSessionStorage: Storage = (window as any).sessionStorage;
function mockSessionStorage() {
(window.sessionStorage as any) = new StubBrowserStorage();
}
function restoreSessionStorage() {
(window.sessionStorage as any) = originalSessionStorage;
}
mockSessionStorage();
import { mockSessionStorage } from '../state_storage/mock';
import { createStateHash, isStateHash } from '../state_hashing';

describe('stateHash', () => {
beforeEach(() => {
sessionStorage.clear();
mockSessionStorage.clear();
});
afterAll(() => {
restoreSessionStorage();
});
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { createStateHash, isStateHash } = require('./state_hash');

describe('#createStateHash', () => {
it('returns a hash', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import { HashedItemStoreSingleton } from '../state_storage';
// This prefix is used to identify hash strings that have been encoded in the URL.
const HASH_PREFIX = 'h@';

export function createStateHash(json: string) {
export function createStateHash(
json: string,
existingJsonProvider?: (hash: string) => string | null // TODO: temp while state.js relies on this in tests
) {
if (typeof json !== 'string') {
throw new Error('createHash only accepts strings (JSON).');
}
Expand All @@ -37,7 +40,9 @@ export function createStateHash(json: string) {
// b) or has been used already, but with the JSON we're currently hashing.
for (let i = 7; i < hash.length; i++) {
shortenedHash = hash.slice(0, i);
const existingJson = HashedItemStoreSingleton.getItem(shortenedHash);
const existingJson = existingJsonProvider
? existingJsonProvider(shortenedHash)
: HashedItemStoreSingleton.getItem(shortenedHash);
if (existingJson === null || existingJson === json) break;
}

Expand Down
37 changes: 37 additions & 0 deletions src/legacy/ui/public/state_management/state_storage/mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { StubBrowserStorage } from 'test_utils/stub_browser_storage';
import { HashedItemStore } from './hashed_item_store';

/**
* Useful for mocking state_storage from jest,
*
* import { mockSessionStorage } from '../state_storage/mock;
*
* And all tests in the test file will use HashedItemStoreSingleton
* with underlying mockSessionStorage we have access to
*/
export const mockSessionStorage = new StubBrowserStorage();
const mockHashedItemStore = new HashedItemStore(mockSessionStorage);
jest.mock('../state_storage', () => {
return {
HashedItemStoreSingleton: mockHashedItemStore,
};
});
2 changes: 1 addition & 1 deletion test/functional/apps/home/_navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) {
await browser.get(`${basePath}/app/kibana#/home`, false);
await retry.waitFor(
'navigation to home app',
async () => (await browser.getCurrentUrl()) === `${basePath}/app/kibana#/home?_g=()`
async () => (await browser.getCurrentUrl()) === `${basePath}/app/kibana#/home`
);

await browser.get(`${basePath}/app/kibana#/home?_g=()&a=b/c`, false);
Expand Down
4 changes: 2 additions & 2 deletions test/functional/apps/visualize/_area_chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export default function ({ getService, getPageObjects }) {
describe('embedded mode', () => {
it('should hide side editor if embed is set to true in url', async () => {
const url = await browser.getCurrentUrl();
const embedUrl = url.split('/visualize/').pop().replace('?_g=', '?embed=true&_g=');
const embedUrl = url.split('/visualize/').pop() + '&embed=true';
await PageObjects.common.navigateToUrl('visualize', embedUrl);
await PageObjects.header.waitUntilLoadingHasFinished();
const sideEditorExists = await PageObjects.visualize.getSideEditorExists();
Expand All @@ -245,7 +245,7 @@ export default function ({ getService, getPageObjects }) {

after(async () => {
const url = await browser.getCurrentUrl();
const embedUrl = url.split('/visualize/').pop().replace('?embed=true&', '?');
const embedUrl = url.split('/visualize/').pop().replace('embed=true', '');
await PageObjects.common.navigateToUrl('visualize', embedUrl);
});
});
Expand Down
26 changes: 26 additions & 0 deletions x-pack/typings/rison_node.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

declare module 'rison-node' {
export type RisonValue = null | boolean | number | string | RisonObject | RisonArray;

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface RisonArray extends Array<RisonValue> {}

export interface RisonObject {
[key: string]: RisonValue;
}

export const decode: (input: string) => RisonValue;

// eslint-disable-next-line @typescript-eslint/camelcase
export const decode_object: (input: string) => RisonObject;

export const encode: <Input extends RisonValue>(input: Input) => string;

// eslint-disable-next-line @typescript-eslint/camelcase
export const encode_object: <Input extends RisonObject>(input: Input) => string;
}

0 comments on commit 4b95bc8

Please sign in to comment.