2
0
mirror of https://github.com/iconify/iconify.git synced 2024-12-22 09:48:54 +00:00

feat: remove browser storage, deprecate exported functions

This commit is contained in:
Vjacheslav Trushkin 2024-12-21 14:12:40 +02:00
parent bfdd3f021d
commit 3fe6adc34e
20 changed files with 45 additions and 2321 deletions

View File

@ -65,8 +65,6 @@ import {
import { sendAPIQuery } from '@iconify/core/lib/api/query';
// Cache
import { initBrowserStorage } from '@iconify/core/lib/browser-storage';
import { toggleBrowserCache } from '@iconify/core/lib/browser-storage/functions';
import type {
IconifyBrowserCacheType,
IconifyBrowserCacheFunctions,
@ -135,16 +133,20 @@ export { IconifyBrowserCacheType };
/**
* Enable cache
*
* @deprecated No longer used
*/
function enableCache(storage: IconifyBrowserCacheType): void {
toggleBrowserCache(storage, true);
//
}
/**
* Disable cache
*
* @deprecated No longer used
*/
function disableCache(storage: IconifyBrowserCacheType): void {
toggleBrowserCache(storage, false);
//
}
/**
@ -160,9 +162,6 @@ setAPIModule('', fetchAPIModule);
* Browser stuff
*/
if (typeof document !== 'undefined' && typeof window !== 'undefined') {
// Set cache and load existing cache
initBrowserStorage();
interface WindowWithIconifyStuff {
IconifyPreload?: IconifyJSON[] | IconifyJSON;
IconifyProviders?: Record<string, PartialIconifyAPIConfig>;

View File

@ -65,8 +65,6 @@ import {
import { sendAPIQuery } from '@iconify/core/lib/api/query';
// Cache
import { initBrowserStorage } from '@iconify/core/lib/browser-storage';
import { toggleBrowserCache } from '@iconify/core/lib/browser-storage/functions';
import type {
IconifyBrowserCacheType,
IconifyBrowserCacheFunctions,
@ -132,16 +130,20 @@ export { IconifyBrowserCacheType };
/**
* Enable cache
*
* @deprecated No longer used
*/
function enableCache(storage: IconifyBrowserCacheType): void {
toggleBrowserCache(storage, true);
//
}
/**
* Disable cache
*
* @deprecated No longer used
*/
function disableCache(storage: IconifyBrowserCacheType): void {
toggleBrowserCache(storage, false);
//
}
/**
@ -157,9 +159,6 @@ setAPIModule('', fetchAPIModule);
* Browser stuff
*/
if (typeof document !== 'undefined' && typeof window !== 'undefined') {
// Set cache and load existing cache
initBrowserStorage();
interface WindowWithIconifyStuff {
IconifyPreload?: IconifyJSON[] | IconifyJSON;
IconifyProviders?: Record<string, PartialIconifyAPIConfig>;

View File

@ -75,8 +75,6 @@ import {
import { sendAPIQuery } from '@iconify/core/lib/api/query';
// Cache
import { initBrowserStorage } from '@iconify/core/lib/browser-storage';
import { toggleBrowserCache } from '@iconify/core/lib/browser-storage/functions';
import type {
IconifyBrowserCacheType,
IconifyBrowserCacheFunctions,
@ -144,16 +142,20 @@ export { IconifyBrowserCacheType };
/**
* Enable cache
*
* @deprecated No longer used
*/
function enableCache(storage: IconifyBrowserCacheType): void {
toggleBrowserCache(storage, true);
//
}
/**
* Disable cache
*
* @deprecated No longer used
*/
function disableCache(storage: IconifyBrowserCacheType): void {
toggleBrowserCache(storage, false);
//
}
/**
@ -169,9 +171,6 @@ setAPIModule('', fetchAPIModule);
* Browser stuff
*/
if (typeof document !== 'undefined' && typeof window !== 'undefined') {
// Set cache and load existing cache
initBrowserStorage();
interface WindowWithIconifyStuff {
IconifyPreload?: IconifyJSON[] | IconifyJSON;
IconifyProviders?: Record<string, PartialIconifyAPIConfig>;

View File

@ -46,8 +46,6 @@ import {
} from '@iconify/core/lib/api/loaders';
// Cache
import { initBrowserStorage } from '@iconify/core/lib/browser-storage';
import { toggleBrowserCache } from '@iconify/core/lib/browser-storage/functions';
import type {
IconifyBrowserCacheType,
IconifyBrowserCacheFunctions,
@ -101,9 +99,6 @@ export function exportFunctions(): IconifyExportedFunctions {
//
}
if (_window) {
// Set cache and load existing cache
initBrowserStorage();
// Load icons from global "IconifyPreload"
if (_window.IconifyPreload !== void 0) {
const preload = _window.IconifyPreload;
@ -171,10 +166,14 @@ export function exportFunctions(): IconifyExportedFunctions {
};
return {
enableCache: (storage: IconifyBrowserCacheType) =>
toggleBrowserCache(storage, true),
disableCache: (storage: IconifyBrowserCacheType) =>
toggleBrowserCache(storage, false),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
enableCache: (storage: IconifyBrowserCacheType) => {
// No longer used
},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
disableCache: (storage: IconifyBrowserCacheType) => {
// No longer used
},
iconLoaded,
iconExists: iconLoaded, // deprecated, kept to avoid breaking changes
getIcon,

View File

@ -62,50 +62,10 @@
"require": "./lib/api/types.cjs",
"import": "./lib/api/types.mjs"
},
"./lib/browser-storage/config": {
"require": "./lib/browser-storage/config.cjs",
"import": "./lib/browser-storage/config.mjs"
},
"./lib/browser-storage/count": {
"require": "./lib/browser-storage/count.cjs",
"import": "./lib/browser-storage/count.mjs"
},
"./lib/browser-storage/data": {
"require": "./lib/browser-storage/data.cjs",
"import": "./lib/browser-storage/data.mjs"
},
"./lib/browser-storage/foreach": {
"require": "./lib/browser-storage/foreach.cjs",
"import": "./lib/browser-storage/foreach.mjs"
},
"./lib/browser-storage/functions": {
"require": "./lib/browser-storage/functions.cjs",
"import": "./lib/browser-storage/functions.mjs"
},
"./lib/browser-storage/global": {
"require": "./lib/browser-storage/global.cjs",
"import": "./lib/browser-storage/global.mjs"
},
"./lib/browser-storage": {
"require": "./lib/browser-storage/index.cjs",
"import": "./lib/browser-storage/index.mjs"
},
"./lib/browser-storage/index": {
"require": "./lib/browser-storage/index.cjs",
"import": "./lib/browser-storage/index.mjs"
},
"./lib/browser-storage/item": {
"require": "./lib/browser-storage/item.cjs",
"import": "./lib/browser-storage/item.mjs"
},
"./lib/browser-storage/mock": {
"require": "./lib/browser-storage/mock.cjs",
"import": "./lib/browser-storage/mock.mjs"
},
"./lib/browser-storage/store": {
"require": "./lib/browser-storage/store.cjs",
"import": "./lib/browser-storage/store.mjs"
},
"./lib/browser-storage/types": {
"require": "./lib/browser-storage/types.cjs",
"import": "./lib/browser-storage/types.mjs"

View File

@ -12,7 +12,6 @@ import { getStorage, addIconSet } from '../storage/storage';
import { listToIcons } from '../icon/list';
import { allowSimpleNames, getIconData } from '../storage/functions';
import { sendAPIQuery } from './query';
import { storeInBrowserStorage } from '../browser-storage/store';
import type { IconStorageWithAPI } from './types';
import { defaultIconProps } from '@iconify/utils/lib/icon/defaults';
@ -91,8 +90,7 @@ function checkIconNamesForAPI(icons: string[]): CheckIconNames {
function parseLoaderResponse(
storage: IconStorageWithAPI,
icons: string[],
data: unknown,
isAPIResponse: boolean
data: unknown
) {
function checkMissing() {
const pending = storage.pendingIcons;
@ -118,11 +116,6 @@ function parseLoaderResponse(
checkMissing();
return;
}
// Cache API response
if (isAPIResponse) {
storeInBrowserStorage(storage, data as IconifyJSON);
}
} catch (err) {
console.error(err);
}
@ -189,7 +182,7 @@ function loadNewIcons(storage: IconStorageWithAPI, icons: string[]): void {
parsePossiblyAsyncResponse(
storage.loadIcons(icons, prefix, provider),
(data) => {
parseLoaderResponse(storage, icons, data, false);
parseLoaderResponse(storage, icons, data);
}
);
return;
@ -208,7 +201,7 @@ function loadNewIcons(storage: IconStorageWithAPI, icons: string[]): void {
},
}
: null;
parseLoaderResponse(storage, [name], iconSet, false);
parseLoaderResponse(storage, [name], iconSet);
});
});
return;
@ -220,7 +213,7 @@ function loadNewIcons(storage: IconStorageWithAPI, icons: string[]): void {
if (invalid.length) {
// Invalid icons
parseLoaderResponse(storage, invalid, null, false);
parseLoaderResponse(storage, invalid, null);
}
if (!valid.length) {
// No valid icons to load
@ -233,7 +226,7 @@ function loadNewIcons(storage: IconStorageWithAPI, icons: string[]): void {
: null;
if (!api) {
// API module not found
parseLoaderResponse(storage, valid, null, false);
parseLoaderResponse(storage, valid, null);
return;
}
@ -241,7 +234,7 @@ function loadNewIcons(storage: IconStorageWithAPI, icons: string[]): void {
const params = api.prepare(provider, prefix, valid);
params.forEach((item) => {
sendAPIQuery(provider, item, (data) => {
parseLoaderResponse(storage, item.icons, data, true);
parseLoaderResponse(storage, item.icons, data);
});
});
});

View File

@ -1,14 +0,0 @@
// Cache version. Bump when structure changes
export const browserCacheVersion = 'iconify2';
// Cache keys
export const browserCachePrefix = 'iconify';
export const browserCacheCountKey = browserCachePrefix + '-count';
export const browserCacheVersionKey = browserCachePrefix + '-version';
// Cache expiration
export const browserStorageHour = 3600000;
export const browserStorageCacheExpiration = 168; // In hours
// Maximum number of stored items
export const browserStorageLimit = 50;

View File

@ -1,24 +0,0 @@
import { browserCacheCountKey } from './config';
import { getStoredItem, setStoredItem } from './item';
import type { BrowserStorageInstance } from './types';
/**
* Change current count for storage
*/
export function setBrowserStorageItemsCount(
storage: BrowserStorageInstance,
value: number
): true | undefined {
return setStoredItem(storage, browserCacheCountKey, value.toString());
}
/**
* Get current count from storage
*/
export function getBrowserStorageItemsCount(
storage: typeof localStorage
): number {
return (
parseInt(getStoredItem(storage, browserCacheCountKey) as string) || 0
);
}

View File

@ -1,26 +0,0 @@
import type { BrowserStorageConfig, BrowserStorageEmptyList } from './types';
/**
* Storage configuration
*/
export const browserStorageConfig: BrowserStorageConfig = {
local: true,
session: true,
};
/**
* List of empty items
*/
export const browserStorageEmptyItems: BrowserStorageEmptyList = {
local: new Set(),
session: new Set(),
};
/**
* Flag to check if storage has been loaded
*/
export let browserStorageStatus = false;
export function setBrowserStorageStatus(status: boolean) {
browserStorageStatus = status;
}

View File

@ -1,108 +0,0 @@
import {
browserCachePrefix,
browserCacheVersion,
browserCacheVersionKey,
browserStorageCacheExpiration,
browserStorageHour,
} from './config';
import {
getBrowserStorageItemsCount,
setBrowserStorageItemsCount,
} from './count';
import { browserStorageEmptyItems } from './data';
import { getBrowserStorage } from './global';
import { getStoredItem, removeStoredItem, setStoredItem } from './item';
import type { BrowserStorageConfig, BrowserStorageItem } from './types';
// Result of callback. false = delete item
type IterateBrowserStorageCallbackResult = true | false;
// Callback
type IterateBrowserStorageCallback = (
item: BrowserStorageItem,
index: number
) => IterateBrowserStorageCallbackResult;
/**
* Iterate items in browser storage
*/
export function iterateBrowserStorage(
key: keyof BrowserStorageConfig,
callback: IterateBrowserStorageCallback
) {
const func = getBrowserStorage(key);
if (!func) {
return;
}
// Get version
const version = getStoredItem(func, browserCacheVersionKey);
if (version !== browserCacheVersion) {
if (version) {
// Version is set, but invalid - remove old entries
const total = getBrowserStorageItemsCount(func);
for (let i = 0; i < total; i++) {
removeStoredItem(func, browserCachePrefix + i.toString());
}
}
// Empty data
setStoredItem(func, browserCacheVersionKey, browserCacheVersion);
setBrowserStorageItemsCount(func, 0);
return;
}
// Minimum time
const minTime =
Math.floor(Date.now() / browserStorageHour) -
browserStorageCacheExpiration;
// Parse item
const parseItem = (index: number): true | undefined => {
const name = browserCachePrefix + index.toString();
const item = getStoredItem(func, name);
if (typeof item !== 'string') {
// Does not exist
return;
}
// Get item, validate it
try {
// Parse, check time stamp
const data = JSON.parse(item) as BrowserStorageItem;
if (
typeof data === 'object' &&
typeof data.cached === 'number' &&
data.cached > minTime &&
typeof data.provider === 'string' &&
typeof data.data === 'object' &&
typeof data.data.prefix === 'string' &&
// Valid item: run callback
callback(data, index)
) {
return true;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (err) {
//
}
// Remove item
removeStoredItem(func, name);
};
let total = getBrowserStorageItemsCount(func);
for (let i = total - 1; i >= 0; i--) {
if (!parseItem(i)) {
if (i === total - 1) {
// Last item - reduce count
total--;
setBrowserStorageItemsCount(func, total);
} else {
// Mark as empty
browserStorageEmptyItems[key].add(i);
}
}
}
}

View File

@ -1,34 +1,29 @@
import { browserStorageConfig } from './data';
import type { BrowserStorageType } from './types';
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { BrowserStorageType } from './types.js';
/**
* Cache types
*
* @deprecated This type is not used anymore
*/
export type IconifyBrowserCacheType = BrowserStorageType | 'all';
/**
* Toggle cache
*
* @deprecated This function is not used anymore
*/
export function toggleBrowserCache(
storage: IconifyBrowserCacheType,
value: boolean
): void {
switch (storage) {
case 'local':
case 'session':
browserStorageConfig[storage] = value;
break;
case 'all':
for (const key in browserStorageConfig) {
browserStorageConfig[key as BrowserStorageType] = value;
}
break;
}
// Not used
}
/**
* Interface for exported functions
*
* @deprecated This type is not used anymore
*/
export interface IconifyBrowserCacheFunctions {
enableCache: (storage: IconifyBrowserCacheType) => void;

View File

@ -1,41 +0,0 @@
import { browserStorageConfig } from './data';
import type { BrowserStorageInstance, BrowserStorageType } from './types';
/**
* Fake window for unit testing
*/
type FakeWindow = Record<string, BrowserStorageInstance>;
let _window: FakeWindow =
typeof window === 'undefined' ? {} : (window as unknown as FakeWindow);
/**
* Get browser storage
*/
export function getBrowserStorage(
key: BrowserStorageType
): BrowserStorageInstance | undefined {
const attr = key + 'Storage';
try {
if (
_window &&
_window[attr] &&
typeof _window[attr].length === 'number'
) {
return _window[attr];
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (err) {
//
}
// Failed - mark as disabled
browserStorageConfig[key] = false;
}
/**
* Mock window for unit testing
*/
export function mockWindow(fakeWindow: FakeWindow): void {
_window = fakeWindow;
}

View File

@ -1,48 +0,0 @@
import { addIconSet, getStorage } from '../storage/storage';
import {
browserStorageConfig,
browserStorageStatus,
setBrowserStorageStatus,
} from './data';
import { iterateBrowserStorage } from './foreach';
import type { BrowserStorageType, IconStorageWithCache } from './types';
/**
* Load icons from cache
*/
export function initBrowserStorage() {
if (browserStorageStatus) {
return;
}
setBrowserStorageStatus(true);
// Load each storage
for (const key in browserStorageConfig) {
iterateBrowserStorage(key as BrowserStorageType, (item) => {
// Add icon set
const iconSet = item.data;
const provider = item.provider;
const prefix = iconSet.prefix;
const storage = getStorage(
provider,
prefix
) as IconStorageWithCache;
if (!addIconSet(storage, iconSet).length) {
// No valid icons
return false;
}
// Store lastModified, -1 if not set to get truthy value
// Smallest of values is stored to fix cache
const lastModified = iconSet.lastModified || -1;
storage.lastModifiedCached = storage.lastModifiedCached
? Math.min(storage.lastModifiedCached, lastModified)
: lastModified;
return true;
});
}
// Check for update
}

View File

@ -1,45 +0,0 @@
import type { BrowserStorageInstance } from './types';
/**
* Get stored item with try..catch
*/
export function getStoredItem(
func: BrowserStorageInstance,
key: string
): string | null | undefined {
try {
return func.getItem(key);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (err) {
//
}
}
/**
* Store item with try..catch
*/
export function setStoredItem(
func: BrowserStorageInstance,
key: string,
value: string
): true | undefined {
try {
func.setItem(key, value);
return true;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (err) {
//
}
}
/**
* Remove item with try..catch
*/
export function removeStoredItem(func: BrowserStorageInstance, key: string) {
try {
func.removeItem(key);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (err) {
//
}
}

View File

@ -1,105 +0,0 @@
import { mockWindow } from './global';
import {
browserStorageConfig,
browserStorageEmptyItems,
setBrowserStorageStatus,
} from './data';
import type { BrowserStorageType } from './types';
/**
* Get next icon set prefix for testing
*/
let prefixCounter = 0;
export function nextPrefix(): string {
return 'fake-storage-' + (prefixCounter++).toString();
}
/**
* Storage class
*/
export class Storage {
canRead = true;
canWrite = true;
items = Object.create(null) as Record<string, string>;
/**
* Get number of items
*/
get length(): number {
if (!this.canRead) {
throw new Error('Restricted storage');
}
return Object.keys(this.items).length;
}
/**
* Get item
*
* @param name
*/
getItem(name: string): string | null {
if (!this.canRead) {
throw new Error('Restricted storage');
}
return name in this.items ? this.items[name] : null;
}
/**
* Set item
*
* @param name
* @param value
*/
setItem(name: string, value: string): void {
if (!this.canWrite) {
throw new Error('Read-only storage');
}
this.items[name] = value;
}
/**
* Remove item
*
* @param name
*/
removeItem(name: string): void {
if (!this.canWrite) {
throw new Error('Read-only storage');
}
delete this.items[name];
}
/**
* Clear everything
*/
clear(): void {
if (!this.canWrite) {
throw new Error('Read-only storage');
}
this.items = Object.create(null) as Record<string, string>;
}
}
/**
* Create fake storage, assign localStorage type
*/
export function createCache(): typeof localStorage {
return new Storage() as unknown as typeof localStorage;
}
/**
* Reset test
*
* @param fakeWindow
*/
export function reset(fakeWindow: Record<string, typeof localStorage>): void {
// Replace window
mockWindow(fakeWindow);
// Reset all data
setBrowserStorageStatus(false);
for (const key in browserStorageConfig) {
browserStorageConfig[key as BrowserStorageType] = true;
browserStorageEmptyItems[key as BrowserStorageType] = new Set();
}
}

View File

@ -1,132 +0,0 @@
import type { IconifyJSON } from '@iconify/types';
import type { IconStorage } from '../storage/storage';
import {
browserCachePrefix,
browserStorageHour,
browserStorageLimit,
} from './config';
import {
getBrowserStorageItemsCount,
setBrowserStorageItemsCount,
} from './count';
import {
browserStorageConfig,
browserStorageEmptyItems,
browserStorageStatus,
} from './data';
import { iterateBrowserStorage } from './foreach';
import { getBrowserStorage } from './global';
import { initBrowserStorage } from './index';
import { setStoredItem } from './item';
import type {
BrowserStorageInstance,
BrowserStorageItem,
BrowserStorageType,
IconStorageWithCache,
} from './types';
/**
* Update lastModified in storage
*
* Returns false if item should not be added to storage because lastModified is too low
*/
export function updateLastModified(
storage: IconStorageWithCache,
lastModified: number
): boolean {
const lastValue = storage.lastModifiedCached;
if (
// Matches or newer
lastValue &&
lastValue >= lastModified
) {
// Nothing to update
return lastValue === lastModified;
}
// Update value
storage.lastModifiedCached = lastModified;
if (lastValue) {
// Old value was set: possibly items are in browser cache
for (const key in browserStorageConfig) {
iterateBrowserStorage(key as BrowserStorageType, (item) => {
const iconSet = item.data;
// Delete items with same provider and prefix
return (
item.provider !== storage.provider ||
iconSet.prefix !== storage.prefix ||
iconSet.lastModified === lastModified
);
});
}
}
return true;
}
/**
* Function to cache icons
*/
export function storeInBrowserStorage(storage: IconStorage, data: IconifyJSON) {
if (!browserStorageStatus) {
initBrowserStorage();
}
function store(key: BrowserStorageType): true | undefined {
let func: BrowserStorageInstance | undefined;
if (!browserStorageConfig[key] || !(func = getBrowserStorage(key))) {
return;
}
// Get item index
const set = browserStorageEmptyItems[key];
let index: number;
if (set.size) {
// Remove item from set
set.delete((index = Array.from(set).shift() as number));
} else {
// Append new item, unless exceeded storage limit
index = getBrowserStorageItemsCount(func);
if (
index >= browserStorageLimit ||
!setBrowserStorageItemsCount(func, index + 1)
) {
return;
}
}
// Create and save item
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider: storage.provider,
data,
};
return setStoredItem(
func,
browserCachePrefix + index.toString(),
JSON.stringify(item)
);
}
// Update lastModified
if (data.lastModified && !updateLastModified(storage, data.lastModified)) {
return;
}
// Do not store empty sets
if (!Object.keys(data.icons).length) {
return;
}
// Remove not_found (clone object to keep old object intact)
if (data.not_found) {
data = Object.assign({}, data);
delete data.not_found;
}
// Attempt to store at localStorage first, then at sessionStorage
if (!store('local')) {
store('session');
}
}

View File

@ -1,30 +1,6 @@
import type { IconifyJSON } from '@iconify/types';
import type { IconStorage } from '../storage/storage';
// Storage types
export type BrowserStorageType = 'local' | 'session';
// localStorage
export type BrowserStorageInstance = typeof localStorage;
// Config
export type BrowserStorageConfig = Record<BrowserStorageType, boolean>;
// List of empty items, for re-use
export type BrowserStorageEmptyList = Record<BrowserStorageType, Set<number>>;
// Stored item
export interface BrowserStorageItem {
cached: number;
provider: string;
data: IconifyJSON;
}
/**
* Add custom stuff to storage
* Storage types
*
* @deprecated This type is not used anymore
*/
export interface IconStorageWithCache extends IconStorage {
// Last modified from browser cache, minimum of available values
// If not set, but icons are cached, value is -1
lastModifiedCached?: number;
}
export type BrowserStorageType = 'local' | 'session';

View File

@ -1,217 +0,0 @@
import { initBrowserStorage } from '../../lib/browser-storage';
import { browserStorageConfig } from '../../lib/browser-storage/data';
import {
browserCacheCountKey,
browserCachePrefix,
browserCacheVersion,
browserCacheVersionKey,
} from '../../lib/browser-storage/config';
import { getBrowserStorageItemsCount } from '../../lib/browser-storage/count';
import { getBrowserStorage } from '../../lib/browser-storage/global';
import { nextPrefix, createCache, reset } from '../../lib/browser-storage/mock';
describe('Testing mocked localStorage', () => {
const provider = '';
it('No usable cache', () => {
reset({});
// Config before tests
expect(browserStorageConfig).toEqual({
local: true,
session: true,
});
// No storage available
expect(getBrowserStorage('local')).toBeUndefined();
expect(getBrowserStorage('session')).toBeUndefined();
// Attempt to load
initBrowserStorage();
// Everything should be disabled
expect(browserStorageConfig).toEqual({
local: false,
session: false,
});
});
it('Empty localStorage', () => {
reset({
localStorage: createCache(),
});
// Config before tests
expect(browserStorageConfig).toEqual({
local: true,
session: true,
});
// Only locaStorage should be available
expect(getBrowserStorage('local')).toBeDefined();
expect(getBrowserStorage('session')).toBeUndefined();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
// Attempt to load
initBrowserStorage();
// sessionStorage should be disabled
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
// Nothing should have loaded
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
});
it('Restricted localStorage', () => {
const prefix = nextPrefix();
const cache = createCache();
// Add one item
cache.setItem(browserCacheVersionKey, browserCacheVersion);
cache.setItem(browserCacheCountKey, '1');
cache.setItem(
browserCachePrefix + '0',
JSON.stringify({
cached: Date.now(),
provider,
data: {
prefix: prefix,
icons: {
foo: {
body: '<g></g>',
},
},
},
})
);
// Prevent reading and writing
cache.canRead = false;
cache.canWrite = false;
// Set cache and test it
reset({
localStorage: cache,
sessionStorage: cache,
});
// Config before tests
expect(browserStorageConfig).toEqual({
local: true,
session: true,
});
// Storage should not be available
expect(getBrowserStorage('local')).toBeUndefined();
expect(getBrowserStorage('session')).toBeUndefined();
// Attempt to load
initBrowserStorage();
// Everything should be disabled because read-only mock throws errors
expect(browserStorageConfig).toEqual({
local: false,
session: false,
});
});
it('localStorage with one item', () => {
const prefix = nextPrefix();
const cache = createCache();
// Add one icon set
cache.setItem(browserCacheVersionKey, browserCacheVersion);
cache.setItem(browserCacheCountKey, '1');
cache.setItem(
browserCachePrefix + '0',
JSON.stringify({
cached: Date.now(),
provider,
data: {
prefix: prefix,
icons: {
foo: {
body: '<g></g>',
},
},
},
})
);
// Set cache and test it
reset({
localStorage: cache,
});
// Config before tests
expect(browserStorageConfig).toEqual({
local: true,
session: true,
});
// localStorage should be available
expect(getBrowserStorage('local')).toBeDefined();
expect(getBrowserStorage('session')).toBeUndefined();
// Attempt to load
initBrowserStorage();
// sessionStorage should be disabled
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
// One item should be in localStorage
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
1
);
});
it('localStorage and sessionStorage', () => {
reset({
localStorage: createCache(),
sessionStorage: createCache(),
});
// Config before tests
expect(browserStorageConfig).toEqual({
local: true,
session: true,
});
// Storage should be available
expect(getBrowserStorage('local')).toBeDefined();
expect(getBrowserStorage('session')).toBeDefined();
// Storage should be empty
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('session')!)).toBe(
0
);
// Attempt to load
initBrowserStorage();
// Everything should be working
expect(browserStorageConfig).toEqual({
local: true,
session: true,
});
});
});

View File

@ -1,555 +0,0 @@
import type { IconifyJSON } from '@iconify/types';
import type {
BrowserStorageItem,
IconStorageWithCache,
} from '../../lib/browser-storage/types';
import { initBrowserStorage } from '../../lib/browser-storage';
import {
browserStorageConfig,
browserStorageEmptyItems,
} from '../../lib/browser-storage/data';
import { getBrowserStorageItemsCount } from '../../lib/browser-storage/count';
import { getBrowserStorage } from '../../lib/browser-storage/global';
import { getStorage, iconInStorage } from '../../lib/storage/storage';
import { nextPrefix, createCache, reset } from '../../lib/browser-storage/mock';
import {
browserCacheCountKey,
browserCachePrefix,
browserCacheVersion,
browserCacheVersionKey,
browserStorageHour,
browserStorageCacheExpiration,
} from '../../lib/browser-storage/config';
import { getStoredItem } from '../../lib/browser-storage/item';
describe('Testing loading from localStorage', () => {
const provider = '';
it('Valid icon set', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix) as IconStorageWithCache;
// Add one icon set
cache.setItem(browserCacheVersionKey, browserCacheVersion);
cache.setItem(browserCacheCountKey, '1');
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: {
prefix: prefix,
icons: {
foo: {
body: '<g></g>',
},
},
},
};
cache.setItem(browserCachePrefix + '0', JSON.stringify(item));
// Set cache
reset({
localStorage: cache,
});
// Only locaStorage should be available
expect(getBrowserStorage('local')).toBeDefined();
expect(getBrowserStorage('session')).toBeUndefined();
// 1 icon
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
1
);
// Check icon storage
expect(iconInStorage(storage, 'foo')).toBe(false);
expect(storage.lastModifiedCached).toBeUndefined();
// Load localStorage
initBrowserStorage();
// Icon should exist now and lastModified should be set
expect(iconInStorage(storage, 'foo')).toBe(true);
expect(storage.lastModifiedCached).toBe(-1);
// Check data
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
});
it('Different provider', () => {
const provider = nextPrefix();
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix) as IconStorageWithCache;
const defaultStorage = getStorage('', prefix) as IconStorageWithCache;
const lastModified = 12345;
// Add one icon set
cache.setItem(browserCacheVersionKey, browserCacheVersion);
cache.setItem(browserCacheCountKey, '1');
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: {
prefix: prefix,
lastModified,
icons: {
foo: {
body: '<g></g>',
},
},
},
};
cache.setItem(browserCachePrefix + '0', JSON.stringify(item));
expect(getStoredItem(cache, browserCachePrefix + '0')).toBeTruthy();
// Set cache
reset({
localStorage: cache,
});
// Check icon storage
expect(iconInStorage(storage, 'foo')).toBe(false);
expect(storage.lastModifiedCached).toBeUndefined();
// Check default provider
expect(iconInStorage(defaultStorage, 'foo')).toBe(false);
expect(defaultStorage.lastModifiedCached).toBeUndefined();
// Load localStorage
initBrowserStorage();
// Icon should exist now
expect(iconInStorage(storage, 'foo')).toBe(true);
expect(iconInStorage(defaultStorage, 'foo')).toBe(false);
// Check data
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
expect(storage.lastModifiedCached).toBe(lastModified);
expect(defaultStorage.lastModifiedCached).toBeUndefined();
});
it('Expired icon set', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix) as IconStorageWithCache;
const lastModified = 12345;
// Add one icon set
cache.setItem(browserCacheVersionKey, browserCacheVersion);
cache.setItem(browserCacheCountKey, '1');
const item: BrowserStorageItem = {
// Expiration date
cached:
Math.floor(Date.now() / browserStorageHour) -
browserStorageCacheExpiration -
1,
provider,
data: {
prefix: prefix,
lastModified,
icons: {
foo: {
body: '<g></g>',
},
},
},
};
cache.setItem(browserCachePrefix + '0', JSON.stringify(item));
expect(getStoredItem(cache, browserCachePrefix + '0')).toBeTruthy();
// Set cache
reset({
localStorage: cache,
});
// Counter should be 1 before parsing it
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
1
);
// Check icon storage
expect(iconInStorage(storage, 'foo')).toBe(false);
// Load localStorage
initBrowserStorage();
// Icon should not have loaded
expect(iconInStorage(storage, 'foo')).toBe(false);
expect(storage.lastModifiedCached).toBeUndefined();
// Check data
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// Counter should have changed to 0
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
});
it('Bad icon set', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix);
// Add one icon set
cache.setItem(browserCacheVersionKey, browserCacheVersion);
cache.setItem(browserCacheCountKey, '1');
cache.setItem(
browserCachePrefix + '0',
JSON.stringify({
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: {
prefix: prefix,
icons: {
foo: {
// Missing 'body' property
width: 20,
},
},
},
})
);
expect(getStoredItem(cache, browserCachePrefix + '0')).toBeTruthy();
// Set cache
reset({
localStorage: cache,
});
// Counter should be 1 before parsing it
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
1
);
// Check icon storage
expect(iconInStorage(storage, 'foo')).toBe(false);
// Load localStorage
initBrowserStorage();
// Icon should not have loaded
expect(iconInStorage(storage, 'foo')).toBe(false);
// Check data
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// Counter should have changed to 0
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
// Item should have been deleted
expect(getStoredItem(cache, browserCachePrefix + '0')).toBeFalsy();
});
it('Wrong counter', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix);
// Add one icon set
cache.setItem(browserCacheVersionKey, browserCacheVersion);
cache.setItem(browserCacheCountKey, '0'); // Should be at least "1"
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: {
prefix: prefix,
icons: {
foo: {
body: '<g></g>',
},
},
},
};
cache.setItem(browserCachePrefix + '0', JSON.stringify(item));
// Set cache
reset({
localStorage: cache,
});
// Counter should be 0
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
// Check icon storage
expect(iconInStorage(storage, 'foo')).toBe(false);
// Load localStorage
initBrowserStorage();
// Icon should not have loaded
expect(iconInStorage(storage, 'foo')).toBe(false);
// Check data
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
});
it('Missing entries at the end', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix);
// Add one icon set
cache.setItem(browserCacheVersionKey, browserCacheVersion);
cache.setItem(browserCacheCountKey, '5');
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: {
prefix: prefix,
icons: {
foo: {
body: '<g></g>',
},
},
},
};
cache.setItem(browserCachePrefix + '0', JSON.stringify(item));
// Set cache
reset({
localStorage: cache,
});
// Counter should be 5 before validation
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
5
);
// Check icon storage
expect(iconInStorage(storage, 'foo')).toBe(false);
// Load localStorage
initBrowserStorage();
// Icon should exist now
expect(iconInStorage(storage, 'foo')).toBe(true);
// Check data
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// Counter should be 1 after validation
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
1
);
});
it('Missing entries', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix);
// Add two icon sets
cache.setItem(browserCacheVersionKey, browserCacheVersion);
cache.setItem(browserCacheCountKey, '5');
// Missing: 0, 2, 3
const item1: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: {
prefix: prefix,
icons: {
foo1: {
body: '<g></g>',
},
},
},
};
const item4: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: {
prefix: prefix,
icons: {
foo4: {
body: '<g></g>',
},
},
},
};
cache.setItem(browserCachePrefix + '1', JSON.stringify(item1));
cache.setItem(browserCachePrefix + '4', JSON.stringify(item4));
// Set cache
reset({
localStorage: cache,
});
// Counter should be 5 before validation
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
5
);
// Check icon storage
expect(iconInStorage(storage, 'foo1')).toBe(false);
expect(iconInStorage(storage, 'foo4')).toBe(false);
// Load localStorage
initBrowserStorage();
// Icons should exist now
expect(iconInStorage(storage, 'foo1')).toBe(true);
expect(iconInStorage(storage, 'foo4')).toBe(true);
// Check data
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set([3, 2, 0]), // reserse order
session: new Set(),
});
// Counter should be 5 after validation
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
5
);
});
it('Using both storage options', () => {
const prefix = nextPrefix();
const cache1 = createCache();
const cache2 = createCache();
const storage = getStorage(provider, prefix);
// Add few icon sets
cache1.setItem(browserCacheVersionKey, browserCacheVersion);
cache2.setItem(browserCacheVersionKey, browserCacheVersion);
cache1.setItem(browserCacheCountKey, '6');
cache2.setItem(browserCacheCountKey, '3');
// Create 5 items
const icons: IconifyJSON[] = [];
const items: BrowserStorageItem[] = [];
for (let i = 0; i < 6; i++) {
const icon: IconifyJSON = {
prefix: prefix,
icons: {
['foo' + i.toString()]: {
body: '<g></g>',
},
},
};
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon,
};
icons.push(icon);
items.push(item);
}
// Add items 1,3,5 to localStorage
[1, 3, 5].forEach((index) => {
cache1.setItem(
browserCachePrefix + index.toString(),
JSON.stringify(items[index])
);
});
// Add items 0 and 2 to sessionStorage
[0, 2].forEach((index) => {
cache2.setItem(
browserCachePrefix + index.toString(),
JSON.stringify(items[index])
);
});
// Set cache
reset({
localStorage: cache1,
sessionStorage: cache2,
});
// Check icon storage
for (let i = 0; i < 6; i++) {
expect(iconInStorage(storage, 'foo' + i.toString())).toBe(false);
}
// Load localStorage
initBrowserStorage();
// Icons should exist now, except for number 4
for (let i = 0; i < 6; i++) {
expect(iconInStorage(storage, 'foo' + i.toString())).toBe(i !== 4);
}
// Check data
expect(browserStorageConfig).toEqual({
local: true,
session: true,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set([4, 2, 0]),
session: new Set([1]),
});
});
});

View File

@ -1,881 +0,0 @@
import type { IconifyJSON } from '@iconify/types';
import type {
BrowserStorageItem,
IconStorageWithCache,
} from '../../lib/browser-storage/types';
import { storeInBrowserStorage } from '../../lib/browser-storage/store';
import { initBrowserStorage } from '../../lib/browser-storage';
import {
browserStorageConfig,
browserStorageEmptyItems,
} from '../../lib/browser-storage/data';
import { getBrowserStorageItemsCount } from '../../lib/browser-storage/count';
import { getBrowserStorage } from '../../lib/browser-storage/global';
import { getStorage, iconInStorage } from '../../lib/storage/storage';
import { nextPrefix, createCache, reset } from '../../lib/browser-storage/mock';
import {
browserCacheCountKey,
browserCachePrefix,
browserCacheVersion,
browserCacheVersionKey,
browserStorageHour,
browserStorageCacheExpiration,
} from '../../lib/browser-storage/config';
describe('Testing saving to localStorage', () => {
const provider = '';
it('One icon set', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix) as IconStorageWithCache;
// Add one icon set
const icon: IconifyJSON = {
prefix: prefix,
icons: {
foo: {
body: '<g></g>',
},
},
};
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon,
};
// Set cache
reset({
localStorage: cache,
});
// Check icon storage
expect(iconInStorage(storage, 'foo')).toBe(false);
expect(storage.lastModifiedCached).toBeUndefined();
// Counter should be 0
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
// Save item
storeInBrowserStorage(storage, icon);
// Storing in cache should not add item to storage
expect(iconInStorage(storage, 'foo')).toBe(false);
// lastModified is missing, so should not have updated
expect(storage.lastModifiedCached).toBeUndefined();
// Check data that should have been updated because storeCache()
// should call load function before first execution
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// Check cache
expect(cache.getItem(browserCachePrefix + '0')).toBe(
JSON.stringify(item)
);
expect(cache.getItem(browserCacheCountKey)).toBe('1');
expect(cache.getItem(browserCacheVersionKey)).toBe(browserCacheVersion);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
1
);
});
it('Multiple icon sets', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix) as IconStorageWithCache;
const lastModified = 12345;
// Add icon sets
const icon0: IconifyJSON = {
prefix: prefix,
lastModified,
icons: {
foo0: {
body: '<g></g>',
},
},
};
const item0: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon0,
};
const icon1: IconifyJSON = {
prefix: prefix,
lastModified,
icons: {
foo: {
body: '<g></g>',
},
},
};
const item1: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon1,
};
// Set cache
reset({
localStorage: cache,
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
expect(storage.lastModifiedCached).toBeUndefined();
// Save items
storeInBrowserStorage(storage, icon0);
storeInBrowserStorage(storage, icon1);
// Check data that should have been updated because storeCache()
// should call load function before first execution
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// lastModified should be set
expect(storage.lastModifiedCached).toBe(lastModified);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
2
);
// Check cache
expect(cache.getItem(browserCachePrefix + '0')).toBe(
JSON.stringify(item0)
);
expect(cache.getItem(browserCachePrefix + '1')).toBe(
JSON.stringify(item1)
);
expect(cache.getItem(browserCacheCountKey)).toBe('2');
expect(cache.getItem(browserCacheVersionKey)).toBe(browserCacheVersion);
});
it('Multiple icon sets, first is outdated', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix) as IconStorageWithCache;
const lastModified1 = 1234;
const lastModified2 = 12345;
// Add icon sets
const icon0: IconifyJSON = {
prefix: prefix,
lastModified: lastModified1,
icons: {
foo0: {
body: '<g></g>',
},
},
};
// lastModified is newer than first entry: first entry should be deleted
const icon1: IconifyJSON = {
prefix: prefix,
lastModified: lastModified2,
icons: {
foo: {
body: '<g></g>',
},
},
};
const item1: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon1,
};
// Set cache
reset({
localStorage: cache,
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
expect(storage.lastModifiedCached).toBeUndefined();
// Save items
storeInBrowserStorage(storage, icon0);
storeInBrowserStorage(storage, icon1);
// Check data that should have been updated because storeCache()
// should call load function before first execution
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// lastModified should be set to max value
expect(storage.lastModifiedCached).toBe(lastModified2);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
1
);
// Check cache
expect(cache.getItem(browserCachePrefix + '0')).toBe(
// Second item!
JSON.stringify(item1)
);
expect(cache.getItem(browserCachePrefix + '1')).toBeFalsy();
expect(cache.getItem(browserCacheCountKey)).toBe('1');
expect(cache.getItem(browserCacheVersionKey)).toBe(browserCacheVersion);
});
it('Multiple icon sets, second set is outdated', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix) as IconStorageWithCache;
const lastModified1 = 12345;
const lastModified2 = 1234;
// Add icon sets
const icon0: IconifyJSON = {
prefix: prefix,
lastModified: lastModified1,
icons: {
foo0: {
body: '<g></g>',
},
},
};
const item0: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon0,
};
// Icon set with lastModified lower than previous entry should not be stored
const icon1: IconifyJSON = {
prefix: prefix,
lastModified: lastModified2,
icons: {
foo: {
body: '<g></g>',
},
},
};
// Set cache
reset({
localStorage: cache,
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
0
);
expect(storage.lastModifiedCached).toBeUndefined();
// Save items
storeInBrowserStorage(storage, icon0);
storeInBrowserStorage(storage, icon1);
// Check data that should have been updated because storeCache()
// should call load function before first execution
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// lastModified should be set to maximum value
expect(storage.lastModifiedCached).toBe(lastModified1);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
1
);
// Check cache
expect(cache.getItem(browserCachePrefix + '0')).toBe(
JSON.stringify(item0)
);
expect(cache.getItem(browserCachePrefix + '1')).toBeFalsy();
expect(cache.getItem(browserCacheCountKey)).toBe('1');
expect(cache.getItem(browserCacheVersionKey)).toBe(browserCacheVersion);
});
it('Adding icon set on unused spot', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix);
// Add icon sets
const icon0: IconifyJSON = {
prefix: prefix,
icons: {
foo0: {
body: '<g></g>',
},
},
};
const item0: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon0,
};
const icon1: IconifyJSON = {
prefix: prefix,
icons: {
foo: {
body: '<g></g>',
},
},
};
const item1: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon1,
};
// Add item
cache.setItem(browserCacheVersionKey, browserCacheVersion);
cache.setItem(browserCacheCountKey, '2');
cache.setItem(browserCachePrefix + '1', JSON.stringify(item1));
// Set cache
reset({
localStorage: cache,
});
// Load data
initBrowserStorage();
// Check data
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set([0]),
session: new Set(),
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
2
);
// Save items
storeInBrowserStorage(storage, icon0);
// Check data
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
2
);
// Check cache
expect(cache.getItem(browserCachePrefix + '0')).toBe(
JSON.stringify(item0)
);
expect(cache.getItem(browserCachePrefix + '1')).toBe(
JSON.stringify(item1)
);
expect(cache.getItem(browserCacheCountKey)).toBe('2');
expect(cache.getItem(browserCacheVersionKey)).toBe(browserCacheVersion);
});
it('Adding multiple icon sets to existing data', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix);
// Add icon sets
const icons: IconifyJSON[] = [];
const items: BrowserStorageItem[] = [];
for (let i = 0; i < 12; i++) {
const icon: IconifyJSON = {
prefix: prefix,
icons: {
['foo' + i.toString()]: {
body: '<g></g>',
},
},
};
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon,
};
// Make items 2 and 4 expire
if (i === 2 || i === 4) {
item.cached -= browserStorageCacheExpiration + 1;
}
// Change expiration for items 6 and 8 to almost expire
if (i === 6 || i === 8) {
item.cached -= browserStorageCacheExpiration - 1;
}
icons.push(icon);
items.push(item);
// Skip items 1, 5, 9+
if (i !== 1 && i !== 5 && i < 9) {
cache.setItem(
browserCachePrefix + i.toString(),
JSON.stringify(item)
);
}
}
cache.setItem(browserCacheVersionKey, browserCacheVersion);
cache.setItem(browserCacheCountKey, '10');
// Set cache
reset({
sessionStorage: cache,
});
// Load data
initBrowserStorage();
// Check data
expect(browserStorageConfig).toEqual({
local: false,
session: true,
});
// Counter should have changed to 9 after validation because last item is missing
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('session')!)).toBe(
9
);
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
// mix of expired and skipped items
// reverse order, 9 should not be there because it is last item
session: new Set([5, 4, 2, 1]),
});
expect(cache.getItem(browserCacheCountKey)).toBe('9');
// Check cached items
[0, 3, 6, 7, 8].forEach((index) => {
expect(cache.getItem(browserCachePrefix + index.toString())).toBe(
JSON.stringify(items[index])
);
});
// Check expired items - should have been deleted
// Also check items that weren't supposed to be added
[2, 4, 1, 5, 9, 10, 11, 12, 13].forEach((index) => {
expect(
cache.getItem(browserCachePrefix + index.toString())
).toBeNull();
});
// Add item 5
storeInBrowserStorage(storage, icons[5]);
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set([4, 2, 1]),
});
expect(cache.getItem(browserCacheCountKey)).toBe('9');
// Add items 4, 2, 1
const list = [4, 2, 1];
list.slice(0).forEach((index) => {
expect(list.shift()).toBe(index);
storeInBrowserStorage(storage, icons[index]);
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(list),
});
expect(cache.getItem(browserCacheCountKey)).toBe('9');
});
// Add item 10
storeInBrowserStorage(storage, icons[10]);
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
expect(cache.getItem(browserCacheCountKey)).toBe('10');
// Add item 11
storeInBrowserStorage(storage, icons[11]);
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
expect(cache.getItem(browserCacheCountKey)).toBe('11');
});
it('Overwrite outdated data', () => {
const prefix = nextPrefix();
const cache = createCache();
const storage = getStorage(provider, prefix);
// Add data in old format
cache.setItem(browserCacheVersionKey, '1.0.6');
cache.setItem(browserCacheCountKey, '3');
for (let i = 0; i < 3; i++) {
cache.setItem(
browserCachePrefix + i.toString(),
JSON.stringify({
prefix: prefix,
icons: {
['foo' + i.toString()]: {
body: '<g></g>',
},
},
})
);
}
// Set cache
reset({
localStorage: cache,
});
// Check icon storage
expect(iconInStorage(storage, 'foo1')).toBe(false);
// Load cache
initBrowserStorage();
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// Add one icon set
const icon: IconifyJSON = {
prefix: prefix,
icons: {
foo: {
body: '<g></g>',
},
},
};
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon,
};
// Save item
storeInBrowserStorage(storage, icon);
// Storing in cache should not add item to storage
expect(iconInStorage(storage, 'foo')).toBe(false);
// Check data that should have been updated because storeCache()
// should call load function before first execution
expect(browserStorageConfig).toEqual({
local: true,
session: false,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// Check cache
expect(cache.getItem(browserCachePrefix + '0')).toBe(
JSON.stringify(item)
);
expect(cache.getItem(browserCacheCountKey)).toBe('1');
expect(cache.getItem(browserCacheVersionKey)).toBe(browserCacheVersion);
});
it('Using both storage options', () => {
const prefix = nextPrefix();
const cache1 = createCache();
const cache2 = createCache();
const storage = getStorage(provider, prefix);
// Add icon sets to localStorage
cache1.setItem(browserCacheVersionKey, browserCacheVersion);
cache1.setItem(browserCacheCountKey, '3');
[0, 1, 2].forEach((index) => {
const icon: IconifyJSON = {
prefix: prefix,
icons: {
['foo' + index.toString()]: {
body: '<g></g>',
},
},
};
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon,
};
cache1.setItem(
browserCachePrefix + index.toString(),
JSON.stringify(item)
);
});
// Add icon sets to sessionStorage
cache2.setItem(browserCacheVersionKey, browserCacheVersion);
cache2.setItem(browserCacheCountKey, '4');
[0, 1, 2, 3].forEach((index) => {
const icon: IconifyJSON = {
prefix: prefix,
icons: {
['bar' + index.toString()]: {
body: '<g></g>',
},
},
};
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon,
};
cache2.setItem(
browserCachePrefix + index.toString(),
JSON.stringify(item)
);
});
// Set cache
reset({
localStorage: cache1,
sessionStorage: cache2,
});
// Load data
initBrowserStorage();
// Check data
expect(browserStorageConfig).toEqual({
local: true,
session: true,
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
3
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('session')!)).toBe(
4
);
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// Check icon storage
for (let i = 0; i < 3; i++) {
expect(iconInStorage(storage, 'foo' + i.toString())).toBe(true);
}
for (let i = 0; i < 4; i++) {
expect(iconInStorage(storage, 'bar' + i.toString())).toBe(true);
}
// Add new item to localStorage
const icon: IconifyJSON = {
prefix: prefix,
icons: {
'new-icon': {
body: '<g></g>',
},
},
};
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon,
};
storeInBrowserStorage(storage, icon);
// Check data
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
4 // +1
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('session')!)).toBe(
4
);
// Check cache
expect(cache1.getItem(browserCachePrefix + '3')).toBe(
JSON.stringify(item)
);
});
it('Using both storage options, but localStorage is read only', () => {
const prefix = nextPrefix();
const cache1 = createCache();
const cache2 = createCache();
const storage = getStorage(provider, prefix);
// Add icon sets to localStorage
cache1.setItem(browserCacheVersionKey, browserCacheVersion);
cache1.setItem(browserCacheCountKey, '3');
[0, 1, 2].forEach((index) => {
const icon: IconifyJSON = {
prefix: prefix,
icons: {
['foo' + index.toString()]: {
body: '<g></g>',
},
},
};
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon,
};
cache1.setItem(
browserCachePrefix + index.toString(),
JSON.stringify(item)
);
});
// Add icon sets to sessionStorage
cache2.setItem(browserCacheVersionKey, browserCacheVersion);
cache2.setItem(browserCacheCountKey, '4');
[0, 1, 2, 3].forEach((index) => {
const icon: IconifyJSON = {
prefix: prefix,
icons: {
['bar' + index.toString()]: {
body: '<g></g>',
},
},
};
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon,
};
cache2.setItem(
browserCachePrefix + index.toString(),
JSON.stringify(item)
);
});
// Set cache
reset({
localStorage: cache1,
sessionStorage: cache2,
});
// Load data
initBrowserStorage();
// Check data
expect(browserStorageConfig).toEqual({
local: true,
session: true,
});
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
3
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('session')!)).toBe(
4
);
// Check icon storage
for (let i = 0; i < 3; i++) {
expect(iconInStorage(storage, 'foo' + i.toString())).toBe(true);
}
for (let i = 0; i < 4; i++) {
expect(iconInStorage(storage, 'bar' + i.toString())).toBe(true);
}
// Set localStorage to read-only
cache1.canWrite = false;
// Add new item to localStorage
const icon: IconifyJSON = {
prefix: prefix,
icons: {
'new-icon': {
body: '<g></g>',
},
},
};
const item: BrowserStorageItem = {
cached: Math.floor(Date.now() / browserStorageHour),
provider,
data: icon,
};
storeInBrowserStorage(storage, icon);
// Check data
expect(browserStorageEmptyItems).toEqual({
local: new Set(),
session: new Set(),
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('local')!)).toBe(
3
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(getBrowserStorageItemsCount(getBrowserStorage('session')!)).toBe(
5 // +1
);
// Check cache
expect(cache2.getItem(browserCachePrefix + '4')).toBe(
JSON.stringify(item)
);
});
});