Use localStorage with Web Storage API (#23172)
parent
008647aa7a
commit
8f19c0abb0
|
@ -26,7 +26,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||
|
||||
@state() private _switching = false;
|
||||
|
||||
private _hidden = localStorage.hide_demo_card;
|
||||
private _hidden = window.localStorage.getItem("hide_demo_card");
|
||||
|
||||
public getCardSize() {
|
||||
return this._hidden ? 0 : 2;
|
||||
|
|
|
@ -122,7 +122,10 @@ class HaLandingPage extends LandingPageBaseElement {
|
|||
if (language !== this.language && language) {
|
||||
this.language = language;
|
||||
try {
|
||||
localStorage.setItem("selectedLanguage", JSON.stringify(language));
|
||||
window.localStorage.setItem(
|
||||
"selectedLanguage",
|
||||
JSON.stringify(language)
|
||||
);
|
||||
} catch (err: any) {
|
||||
// Ignore
|
||||
}
|
||||
|
|
|
@ -213,6 +213,7 @@
|
|||
"gulp-rename": "2.0.0",
|
||||
"html-minifier-terser": "7.2.0",
|
||||
"husky": "9.1.7",
|
||||
"jsdom": "25.0.1",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "15.2.10",
|
||||
"lit-analyzer": "2.0.3",
|
||||
|
|
|
@ -327,7 +327,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||
this.language = language;
|
||||
|
||||
try {
|
||||
localStorage.setItem("selectedLanguage", JSON.stringify(language));
|
||||
window.localStorage.setItem("selectedLanguage", JSON.stringify(language));
|
||||
} catch (err: any) {
|
||||
// Ignore
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import type { AuthData } from "home-assistant-js-websocket";
|
||||
import { extractSearchParam } from "../url/search-params";
|
||||
|
||||
const storage = window.localStorage || {};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__tokenCache: {
|
||||
|
@ -38,9 +36,15 @@ export function saveTokens(tokens: AuthData | null) {
|
|||
|
||||
if (tokenCache.writeEnabled) {
|
||||
try {
|
||||
storage.hassTokens = JSON.stringify(tokens);
|
||||
window.localStorage.setItem("hassTokens", JSON.stringify(tokens));
|
||||
} catch (err: any) {
|
||||
// write failed, ignore it. Happens if storage is full or private mode.
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
"Failed to store tokens; Are you in private mode or is your storage full?"
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error storing tokens:", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,12 +55,11 @@ export function enableWrite() {
|
|||
saveTokens(tokenCache.tokens);
|
||||
}
|
||||
}
|
||||
|
||||
export function loadTokens() {
|
||||
if (tokenCache.tokens === undefined) {
|
||||
try {
|
||||
// Delete the old token cache.
|
||||
delete storage.tokens;
|
||||
const tokens = storage.hassTokens;
|
||||
const tokens = window.localStorage.getItem("hassTokens");
|
||||
if (tokens) {
|
||||
tokenCache.tokens = JSON.parse(tokens);
|
||||
tokenCache.writeEnabled = true;
|
||||
|
|
|
@ -4,10 +4,11 @@ import type { HomeAssistant, PanelInfo } from "../types";
|
|||
/** Panel to show when no panel is picked. */
|
||||
export const DEFAULT_PANEL = "lovelace";
|
||||
|
||||
export const getStorageDefaultPanelUrlPath = (): string =>
|
||||
localStorage.defaultPanel
|
||||
? JSON.parse(localStorage.defaultPanel)
|
||||
: DEFAULT_PANEL;
|
||||
export const getStorageDefaultPanelUrlPath = (): string => {
|
||||
const defaultPanel = window.localStorage.getItem("defaultPanel");
|
||||
|
||||
return defaultPanel ? JSON.parse(defaultPanel) : DEFAULT_PANEL;
|
||||
};
|
||||
|
||||
export const setDefaultPanel = (
|
||||
element: HTMLElement,
|
||||
|
|
|
@ -482,7 +482,10 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||
storeState(this.hass!);
|
||||
} else {
|
||||
try {
|
||||
localStorage.setItem("selectedLanguage", JSON.stringify(language));
|
||||
window.localStorage.setItem(
|
||||
"selectedLanguage",
|
||||
JSON.stringify(language)
|
||||
);
|
||||
} catch (err: any) {
|
||||
// Ignore
|
||||
}
|
||||
|
|
|
@ -481,13 +481,14 @@ export class HaAutomationTrace extends LitElement {
|
|||
if (!traceText) {
|
||||
return;
|
||||
}
|
||||
localStorage.devTrace = traceText;
|
||||
window.localStorage.setItem("devTrace", traceText);
|
||||
this._loadLocalTrace(traceText);
|
||||
}
|
||||
|
||||
private _loadLocalStorageTrace() {
|
||||
if (localStorage.devTrace) {
|
||||
this._loadLocalTrace(localStorage.devTrace);
|
||||
const devTrace = window.localStorage.getItem("devTrace");
|
||||
if (devTrace) {
|
||||
this._loadLocalTrace(devTrace);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -487,13 +487,15 @@ export class HaScriptTrace extends LitElement {
|
|||
if (!traceText) {
|
||||
return;
|
||||
}
|
||||
localStorage.devTrace = traceText;
|
||||
|
||||
window.localStorage.setItem("devTrace", traceText);
|
||||
this._loadLocalTrace(traceText);
|
||||
}
|
||||
|
||||
private _loadLocalStorageTrace() {
|
||||
if (localStorage.devTrace) {
|
||||
this._loadLocalTrace(localStorage.devTrace);
|
||||
const devTrace = window.localStorage.getItem("devTrace");
|
||||
if (devTrace) {
|
||||
this._loadLocalTrace(devTrace);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,16 +10,24 @@ const STORED_STATE = [
|
|||
"enableShortcuts",
|
||||
"defaultPanel",
|
||||
];
|
||||
const STORAGE = window.localStorage || {};
|
||||
|
||||
export function storeState(hass: HomeAssistant) {
|
||||
try {
|
||||
STORED_STATE.forEach((key) => {
|
||||
const value = hass[key];
|
||||
STORAGE[key] = JSON.stringify(value === undefined ? null : value);
|
||||
window.localStorage.setItem(
|
||||
key,
|
||||
JSON.stringify(value === undefined ? null : value)
|
||||
);
|
||||
});
|
||||
} catch (err: any) {
|
||||
// Safari throws exception in private mode
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
"Cannot store state; Are you in private mode or is your storage full?"
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,8 +35,9 @@ export function getState() {
|
|||
const state = {};
|
||||
|
||||
STORED_STATE.forEach((key) => {
|
||||
if (key in STORAGE) {
|
||||
let value = JSON.parse(STORAGE[key]);
|
||||
const storageItem = window.localStorage.getItem(key);
|
||||
if (storageItem !== null) {
|
||||
let value = JSON.parse(storageItem);
|
||||
// selectedTheme went from string to object on 20200718
|
||||
if (key === "selectedTheme" && typeof value === "string") {
|
||||
value = { theme: value };
|
||||
|
@ -44,8 +53,5 @@ export function getState() {
|
|||
}
|
||||
|
||||
export function clearState() {
|
||||
// STORAGE is an object if localStorage not available.
|
||||
if (STORAGE.clear) {
|
||||
STORAGE.clear();
|
||||
}
|
||||
window.localStorage.clear();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
let askWrite;
|
||||
|
||||
describe("token_storage.askWrite", () => {
|
||||
afterEach(() => {
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
test("askWrite", async () => {
|
||||
vi.stubGlobal(
|
||||
"window.__tokenCache",
|
||||
(window.__tokenCache = {
|
||||
tokens: undefined,
|
||||
writeEnabled: true,
|
||||
})
|
||||
);
|
||||
|
||||
({ askWrite } = await import("../../../../src/common/auth/token_storage"));
|
||||
expect(askWrite()).toBe(false);
|
||||
});
|
||||
|
||||
test("askWrite prefilled token", async () => {
|
||||
vi.stubGlobal(
|
||||
"window.__tokenCache",
|
||||
(window.__tokenCache = {
|
||||
tokens: {
|
||||
access_token: "test",
|
||||
expires: 1800,
|
||||
expires_in: 1800,
|
||||
hassUrl: "http://localhost",
|
||||
refresh_token: "refresh",
|
||||
clientId: "client",
|
||||
},
|
||||
writeEnabled: undefined,
|
||||
})
|
||||
);
|
||||
|
||||
({ askWrite } = await import("../../../../src/common/auth/token_storage"));
|
||||
expect(askWrite()).toBe(true);
|
||||
});
|
||||
|
||||
test("askWrite prefilled token, write enabled", async () => {
|
||||
vi.stubGlobal(
|
||||
"window.__tokenCache",
|
||||
(window.__tokenCache = {
|
||||
tokens: {
|
||||
access_token: "test",
|
||||
expires: 1800,
|
||||
expires_in: 1800,
|
||||
hassUrl: "http://localhost",
|
||||
refresh_token: "refresh",
|
||||
clientId: "client",
|
||||
},
|
||||
writeEnabled: true,
|
||||
})
|
||||
);
|
||||
|
||||
({ askWrite } = await import("../../../../src/common/auth/token_storage"));
|
||||
expect(askWrite()).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,140 @@
|
|||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import type { AuthData } from "home-assistant-js-websocket";
|
||||
import { FallbackStorage } from "../../../test_helper/local-storage-fallback";
|
||||
|
||||
let saveTokens;
|
||||
|
||||
describe("token_storage.saveTokens", () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage = new FallbackStorage();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetModules();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
test("saveTokens", async () => {
|
||||
const tokens: AuthData = {
|
||||
access_token: "test",
|
||||
expires: 1800,
|
||||
expires_in: 1800,
|
||||
hassUrl: "http://localhost",
|
||||
refresh_token: "refresh",
|
||||
clientId: "client",
|
||||
};
|
||||
|
||||
vi.stubGlobal(
|
||||
"window.__tokenCache",
|
||||
(window.__tokenCache = {
|
||||
tokens: undefined,
|
||||
writeEnabled: undefined,
|
||||
})
|
||||
);
|
||||
|
||||
({ saveTokens } = await import(
|
||||
"../../../../src/common/auth/token_storage"
|
||||
));
|
||||
saveTokens(tokens);
|
||||
expect(window.__tokenCache.tokens).toEqual(tokens);
|
||||
});
|
||||
|
||||
test("saveTokens write enabled", async () => {
|
||||
const tokens: AuthData = {
|
||||
access_token: "test",
|
||||
expires: 1800,
|
||||
expires_in: 1800,
|
||||
hassUrl: "http://localhost",
|
||||
refresh_token: "refresh",
|
||||
clientId: "client",
|
||||
};
|
||||
|
||||
vi.stubGlobal(
|
||||
"window.__tokenCache",
|
||||
(window.__tokenCache = {
|
||||
tokens: undefined,
|
||||
writeEnabled: undefined,
|
||||
})
|
||||
);
|
||||
|
||||
const extractSearchParamSpy = vi.fn().mockReturnValue("true");
|
||||
|
||||
vi.doMock("../../../../src/common/url/search-params", () => ({
|
||||
extractSearchParam: extractSearchParamSpy,
|
||||
}));
|
||||
const setItemSpy = vi.fn();
|
||||
window.localStorage.setItem = setItemSpy;
|
||||
|
||||
({ saveTokens } = await import(
|
||||
"../../../../src/common/auth/token_storage"
|
||||
));
|
||||
saveTokens(tokens);
|
||||
expect(window.__tokenCache.tokens).toEqual(tokens);
|
||||
expect(window.__tokenCache.writeEnabled).toBe(true);
|
||||
expect(extractSearchParamSpy).toHaveBeenCalledOnce();
|
||||
expect(extractSearchParamSpy).toHaveBeenCalledWith("storeToken");
|
||||
expect(setItemSpy).toHaveBeenCalledOnce();
|
||||
expect(setItemSpy).toHaveBeenCalledWith(
|
||||
"hassTokens",
|
||||
JSON.stringify(tokens)
|
||||
);
|
||||
});
|
||||
|
||||
test("saveTokens write enabled full storage", async () => {
|
||||
const tokens: AuthData = {
|
||||
access_token: "test",
|
||||
expires: 1800,
|
||||
expires_in: 1800,
|
||||
hassUrl: "http://localhost",
|
||||
refresh_token: "refresh",
|
||||
clientId: "client",
|
||||
};
|
||||
|
||||
vi.stubGlobal(
|
||||
"window.__tokenCache",
|
||||
(window.__tokenCache = {
|
||||
tokens: undefined,
|
||||
writeEnabled: true,
|
||||
})
|
||||
);
|
||||
|
||||
const extractSearchParamSpy = vi.fn();
|
||||
|
||||
vi.doMock("../../../../src/common/url/search-params", () => ({
|
||||
extractSearchParam: extractSearchParamSpy,
|
||||
}));
|
||||
|
||||
const setItemSpy = vi.fn(() => {
|
||||
throw new Error("Full storage");
|
||||
});
|
||||
|
||||
window.localStorage.setItem = setItemSpy;
|
||||
|
||||
// eslint-disable-next-line no-global-assign
|
||||
console = {
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
} as unknown as Console;
|
||||
|
||||
({ saveTokens } = await import(
|
||||
"../../../../src/common/auth/token_storage"
|
||||
));
|
||||
saveTokens(tokens);
|
||||
expect(window.__tokenCache.tokens).toEqual(tokens);
|
||||
expect(window.__tokenCache.writeEnabled).toBe(true);
|
||||
expect(extractSearchParamSpy).toBeCalledTimes(0);
|
||||
expect(setItemSpy).toHaveBeenCalledOnce();
|
||||
expect(setItemSpy).toHaveBeenCalledWith(
|
||||
"hassTokens",
|
||||
JSON.stringify(tokens)
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.warn).toHaveBeenCalledOnce();
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.warn).toHaveBeenCalledWith(
|
||||
"Failed to store tokens; Are you in private mode or is your storage full?"
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.error).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,110 @@
|
|||
import { describe, it, expect, test, vi, afterEach, beforeEach } from "vitest";
|
||||
import type { AuthData } from "home-assistant-js-websocket";
|
||||
import { FallbackStorage } from "../../../test_helper/local-storage-fallback";
|
||||
|
||||
describe("token_storage", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal(
|
||||
"window.__tokenCache",
|
||||
(window.__tokenCache = {
|
||||
tokens: undefined,
|
||||
writeEnabled: undefined,
|
||||
})
|
||||
);
|
||||
window.localStorage = new FallbackStorage();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
test("initialize tokenCache", async () => {
|
||||
vi.stubGlobal(
|
||||
"window.__tokenCache",
|
||||
(window.__tokenCache = undefined as any)
|
||||
);
|
||||
|
||||
await import("../../../../src/common/auth/token_storage");
|
||||
|
||||
expect(window.__tokenCache).toEqual({
|
||||
tokens: undefined,
|
||||
writeEnabled: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test("should load tokens", async () => {
|
||||
const tokens: AuthData = {
|
||||
access_token: "test",
|
||||
expires: 1800,
|
||||
expires_in: 1800,
|
||||
hassUrl: "http://localhost",
|
||||
refresh_token: "refresh",
|
||||
clientId: "client",
|
||||
};
|
||||
|
||||
const getItemSpy = vi.fn(() => JSON.stringify(tokens));
|
||||
window.localStorage.getItem = getItemSpy;
|
||||
|
||||
const { loadTokens } = await import(
|
||||
"../../../../src/common/auth/token_storage"
|
||||
);
|
||||
|
||||
const loadedTokens = loadTokens();
|
||||
expect(loadedTokens).toEqual(tokens);
|
||||
|
||||
expect(window.__tokenCache.tokens).toEqual(tokens);
|
||||
expect(window.__tokenCache.writeEnabled).toBe(true);
|
||||
expect(getItemSpy).toHaveBeenCalledOnce();
|
||||
expect(getItemSpy).toHaveBeenCalledWith("hassTokens");
|
||||
});
|
||||
|
||||
test("should load null tokens", async () => {
|
||||
const getItemSpy = vi.fn(() => "hello");
|
||||
window.localStorage.getItem = getItemSpy;
|
||||
|
||||
const { loadTokens } = await import(
|
||||
"../../../../src/common/auth/token_storage"
|
||||
);
|
||||
|
||||
const loadedTokens = loadTokens();
|
||||
expect(loadedTokens).toEqual(null);
|
||||
|
||||
expect(window.__tokenCache.tokens).toEqual(null);
|
||||
expect(window.__tokenCache.writeEnabled).toBe(undefined);
|
||||
expect(getItemSpy).toHaveBeenCalledOnce();
|
||||
expect(getItemSpy).toHaveBeenCalledWith("hassTokens");
|
||||
});
|
||||
|
||||
it("should enable write", async () => {
|
||||
const { enableWrite } = await import(
|
||||
"../../../../src/common/auth/token_storage"
|
||||
);
|
||||
enableWrite();
|
||||
expect(window.__tokenCache.writeEnabled).toBe(true);
|
||||
});
|
||||
|
||||
it("should enable write with tokens", async () => {
|
||||
vi.stubGlobal(
|
||||
"window.__tokenCache",
|
||||
(window.__tokenCache = {
|
||||
tokens: "testToken" as any,
|
||||
})
|
||||
);
|
||||
|
||||
const setItemSpy = vi.fn();
|
||||
window.localStorage.setItem = setItemSpy;
|
||||
|
||||
const { enableWrite } = await import(
|
||||
"../../../../src/common/auth/token_storage"
|
||||
);
|
||||
|
||||
enableWrite();
|
||||
expect(window.__tokenCache.writeEnabled).toBe(true);
|
||||
expect(setItemSpy).toHaveBeenCalledOnce();
|
||||
expect(setItemSpy).toHaveBeenCalledWith(
|
||||
"hassTokens",
|
||||
JSON.stringify("testToken")
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,52 @@
|
|||
import { describe, test, expect, beforeEach } from "vitest";
|
||||
import { FallbackStorage } from "./local-storage-fallback";
|
||||
|
||||
describe("FallbackStorage", () => {
|
||||
let storage;
|
||||
|
||||
beforeEach(() => {
|
||||
storage = new FallbackStorage();
|
||||
});
|
||||
|
||||
test("should set and get an item", () => {
|
||||
storage.setItem("key1", "value1");
|
||||
expect(storage.getItem("key1")).toBe("value1");
|
||||
});
|
||||
|
||||
test("should return null for non-existing item", () => {
|
||||
expect(storage.getItem("nonExistingKey")).toBeNull();
|
||||
});
|
||||
|
||||
test("should remove an item", () => {
|
||||
storage.setItem("key2", "value2");
|
||||
storage.removeItem("key2");
|
||||
expect(storage.getItem("key2")).toBeNull();
|
||||
});
|
||||
|
||||
test("should clear all items", () => {
|
||||
storage.setItem("key3", "value3");
|
||||
storage.setItem("key4", "value4");
|
||||
storage.clear();
|
||||
expect(storage.getItem("key3")).toBeNull();
|
||||
expect(storage.getItem("key4")).toBeNull();
|
||||
});
|
||||
|
||||
test("should return the correct key for an index", () => {
|
||||
storage.setItem("key5", "value5");
|
||||
storage.setItem("key6", "value6");
|
||||
expect(storage.key(0)).toBe("key5");
|
||||
expect(storage.key(1)).toBe("key6");
|
||||
});
|
||||
|
||||
test("should throw TypeError if key method is called without arguments", () => {
|
||||
expect(() => storage.key()).toThrow(TypeError);
|
||||
});
|
||||
|
||||
test("should return the correct length", () => {
|
||||
storage.setItem("key7", "value7");
|
||||
storage.setItem("key8", "value8");
|
||||
expect(storage.length).toBe(2);
|
||||
storage.removeItem("key7");
|
||||
expect(storage.length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
export class FallbackStorage implements Storage {
|
||||
private valuesMap = new Map();
|
||||
|
||||
getItem(key) {
|
||||
const stringKey = String(key);
|
||||
if (this.valuesMap.has(key)) {
|
||||
return String(this.valuesMap.get(stringKey));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
setItem(key, val) {
|
||||
this.valuesMap.set(String(key), String(val));
|
||||
}
|
||||
|
||||
removeItem(key) {
|
||||
this.valuesMap.delete(key);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.valuesMap.clear();
|
||||
}
|
||||
|
||||
key(i) {
|
||||
if (arguments.length === 0) {
|
||||
// this is a TypeError implemented on Chrome, Firefox throws Not enough arguments to Storage.key.
|
||||
throw new TypeError(
|
||||
"Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present."
|
||||
);
|
||||
}
|
||||
const arr = Array.from(this.valuesMap.keys());
|
||||
return arr[i];
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.valuesMap.size;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
import { describe, expect, afterEach, vi, test, beforeEach } from "vitest";
|
||||
import type { HomeAssistant } from "../../src/types";
|
||||
import { FallbackStorage } from "../test_helper/local-storage-fallback";
|
||||
|
||||
describe("ha-pref-storage", () => {
|
||||
const mockHass = {
|
||||
dockedSidebar: "auto",
|
||||
selectedTheme: { theme: "default" },
|
||||
unknownKey: "unknownValue",
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
window.localStorage = new FallbackStorage();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetModules();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
test("storeState", async () => {
|
||||
const { storeState } = await import("../../src/util/ha-pref-storage");
|
||||
|
||||
window.localStorage.setItem = vi.fn();
|
||||
|
||||
storeState(mockHass as unknown as HomeAssistant);
|
||||
expect(window.localStorage.setItem).toHaveBeenCalledTimes(8);
|
||||
expect(window.localStorage.setItem).toHaveBeenCalledWith(
|
||||
"dockedSidebar",
|
||||
JSON.stringify("auto")
|
||||
);
|
||||
expect(window.localStorage.setItem).toHaveBeenCalledWith(
|
||||
"selectedTheme",
|
||||
JSON.stringify({ theme: "default" })
|
||||
);
|
||||
expect(window.localStorage.setItem).toHaveBeenCalledWith(
|
||||
"selectedLanguage",
|
||||
JSON.stringify(null)
|
||||
);
|
||||
expect(window.localStorage.setItem).not.toHaveBeenCalledWith(
|
||||
"unknownKey",
|
||||
JSON.stringify("unknownValue")
|
||||
);
|
||||
});
|
||||
|
||||
test("storeState fails", async () => {
|
||||
const { storeState } = await import("../../src/util/ha-pref-storage");
|
||||
|
||||
window.localStorage.setItem = vi.fn((key) => {
|
||||
if (key === "selectedTheme") {
|
||||
throw new Error("Test error");
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-global-assign
|
||||
console = {
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
} as unknown as Console;
|
||||
|
||||
storeState(mockHass as unknown as HomeAssistant);
|
||||
expect(window.localStorage.setItem).toHaveBeenCalledTimes(2);
|
||||
expect(window.localStorage.setItem).toHaveBeenCalledWith(
|
||||
"dockedSidebar",
|
||||
JSON.stringify("auto")
|
||||
);
|
||||
expect(window.localStorage.setItem).toHaveBeenCalledWith(
|
||||
"selectedTheme",
|
||||
JSON.stringify({ theme: "default" })
|
||||
);
|
||||
expect(window.localStorage.setItem).not.toHaveBeenCalledWith(
|
||||
"selectedLanguage",
|
||||
JSON.stringify(null)
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.warn).toHaveBeenCalledOnce();
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.warn).toHaveBeenCalledWith(
|
||||
"Cannot store state; Are you in private mode or is your storage full?"
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.error).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test("getState", async () => {
|
||||
const { getState } = await import("../../src/util/ha-pref-storage");
|
||||
|
||||
window.localStorage.setItem("selectedTheme", JSON.stringify("test"));
|
||||
window.localStorage.setItem("dockedSidebar", JSON.stringify(true));
|
||||
window.localStorage.setItem("selectedLanguage", JSON.stringify("german"));
|
||||
|
||||
// should not be in state
|
||||
window.localStorage.setItem("testEntry", JSON.stringify("this is a test"));
|
||||
|
||||
const state = getState();
|
||||
expect(state).toEqual({
|
||||
dockedSidebar: "docked",
|
||||
selectedTheme: { theme: "test" },
|
||||
selectedLanguage: "german",
|
||||
});
|
||||
});
|
||||
|
||||
test("clearState", async () => {
|
||||
const { clearState } = await import("../../src/util/ha-pref-storage");
|
||||
|
||||
window.localStorage.setItem("test", "test");
|
||||
|
||||
expect(window.localStorage.length).toEqual(1);
|
||||
|
||||
clearState();
|
||||
expect(window.localStorage.length).toEqual(0);
|
||||
});
|
||||
});
|
|
@ -2,13 +2,21 @@ import { defineConfig } from "vitest/config";
|
|||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: "jsdom", // to run in browser-like environment
|
||||
env: {
|
||||
TZ: "Etc/UTC",
|
||||
IS_TEST: "true",
|
||||
},
|
||||
setupFiles: ["./test/setup.ts"],
|
||||
coverage: {
|
||||
include: ["src/data/**/*", "src/common/**/*"],
|
||||
include: [
|
||||
"src/data/**/*",
|
||||
"src/common/**/*",
|
||||
"src/external_app/**/*",
|
||||
"src/hassio/**/*",
|
||||
"src/panels/**/*",
|
||||
"src/util/**/*",
|
||||
],
|
||||
reporter: ["text", "html"],
|
||||
provider: "v8",
|
||||
reportsDirectory: "test/coverage",
|
||||
|
|
210
yarn.lock
210
yarn.lock
|
@ -6888,6 +6888,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cssstyle@npm:^4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "cssstyle@npm:4.1.0"
|
||||
dependencies:
|
||||
rrweb-cssom: "npm:^0.7.1"
|
||||
checksum: 10/8ca9e2d1f1b24f93bb5f3f20a7a1e271e58060957880e985ee55614e196a798ffab309ec6bac105af8a439a6764546761813835ebb7f929d60823637ee838a8f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"csstype@npm:^3.1.0":
|
||||
version: 3.1.3
|
||||
resolution: "csstype@npm:3.1.3"
|
||||
|
@ -6895,6 +6904,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"data-urls@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "data-urls@npm:5.0.0"
|
||||
dependencies:
|
||||
whatwg-mimetype: "npm:^4.0.0"
|
||||
whatwg-url: "npm:^14.0.0"
|
||||
checksum: 10/5c40568c31b02641a70204ff233bc4e42d33717485d074244a98661e5f2a1e80e38fe05a5755dfaf2ee549f2ab509d6a3af2a85f4b2ad2c984e5d176695eaf46
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"data-view-buffer@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "data-view-buffer@npm:1.0.1"
|
||||
|
@ -6995,6 +7014,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decimal.js@npm:^10.4.3":
|
||||
version: 10.4.3
|
||||
resolution: "decimal.js@npm:10.4.3"
|
||||
checksum: 10/de663a7bc4d368e3877db95fcd5c87b965569b58d16cdc4258c063d231ca7118748738df17cd638f7e9dd0be8e34cec08d7234b20f1f2a756a52fc5a38b188d0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"deep-clone-simple@npm:1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "deep-clone-simple@npm:1.1.1"
|
||||
|
@ -9240,6 +9266,7 @@ __metadata:
|
|||
idb-keyval: "npm:6.2.1"
|
||||
intl-messageformat: "npm:10.7.7"
|
||||
js-yaml: "npm:4.1.0"
|
||||
jsdom: "npm:25.0.1"
|
||||
jszip: "npm:3.10.1"
|
||||
leaflet: "npm:1.9.4"
|
||||
leaflet-draw: "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
|
@ -9328,6 +9355,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html-encoding-sniffer@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "html-encoding-sniffer@npm:4.0.0"
|
||||
dependencies:
|
||||
whatwg-encoding: "npm:^3.1.1"
|
||||
checksum: 10/e86efd493293a5671b8239bd099d42128433bb3c7b0fdc7819282ef8e118a21f5dead0ad6f358e024a4e5c84f17ebb7a9b36075220fac0a6222b207248bede6f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html-entities@npm:^2.4.0":
|
||||
version: 2.5.2
|
||||
resolution: "html-entities@npm:2.5.2"
|
||||
|
@ -9422,7 +9458,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-proxy-agent@npm:^7.0.0":
|
||||
"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.2":
|
||||
version: 7.0.2
|
||||
resolution: "http-proxy-agent@npm:7.0.2"
|
||||
dependencies:
|
||||
|
@ -9461,7 +9497,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"https-proxy-agent@npm:^7.0.1":
|
||||
"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.5":
|
||||
version: 7.0.5
|
||||
resolution: "https-proxy-agent@npm:7.0.5"
|
||||
dependencies:
|
||||
|
@ -9510,7 +9546,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3":
|
||||
"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3":
|
||||
version: 0.6.3
|
||||
resolution: "iconv-lite@npm:0.6.3"
|
||||
dependencies:
|
||||
|
@ -9949,7 +9985,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-potential-custom-element-name@npm:^1.0.0":
|
||||
"is-potential-custom-element-name@npm:^1.0.0, is-potential-custom-element-name@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "is-potential-custom-element-name@npm:1.0.1"
|
||||
checksum: 10/ced7bbbb6433a5b684af581872afe0e1767e2d1146b2207ca0068a648fb5cab9d898495d1ac0583524faaf24ca98176a7d9876363097c2d14fee6dd324f3a1ab
|
||||
|
@ -10255,6 +10291,40 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsdom@npm:25.0.1":
|
||||
version: 25.0.1
|
||||
resolution: "jsdom@npm:25.0.1"
|
||||
dependencies:
|
||||
cssstyle: "npm:^4.1.0"
|
||||
data-urls: "npm:^5.0.0"
|
||||
decimal.js: "npm:^10.4.3"
|
||||
form-data: "npm:^4.0.0"
|
||||
html-encoding-sniffer: "npm:^4.0.0"
|
||||
http-proxy-agent: "npm:^7.0.2"
|
||||
https-proxy-agent: "npm:^7.0.5"
|
||||
is-potential-custom-element-name: "npm:^1.0.1"
|
||||
nwsapi: "npm:^2.2.12"
|
||||
parse5: "npm:^7.1.2"
|
||||
rrweb-cssom: "npm:^0.7.1"
|
||||
saxes: "npm:^6.0.0"
|
||||
symbol-tree: "npm:^3.2.4"
|
||||
tough-cookie: "npm:^5.0.0"
|
||||
w3c-xmlserializer: "npm:^5.0.0"
|
||||
webidl-conversions: "npm:^7.0.0"
|
||||
whatwg-encoding: "npm:^3.1.1"
|
||||
whatwg-mimetype: "npm:^4.0.0"
|
||||
whatwg-url: "npm:^14.0.0"
|
||||
ws: "npm:^8.18.0"
|
||||
xml-name-validator: "npm:^5.0.0"
|
||||
peerDependencies:
|
||||
canvas: ^2.11.2
|
||||
peerDependenciesMeta:
|
||||
canvas:
|
||||
optional: true
|
||||
checksum: 10/e6bf7250ddd2fbcf68da0ea041a0dc63545dc4bf77fa3ff40a46ae45b1dac1ca55b87574ab904d1f8baeeb547c52cec493a22f545d7d413b320011f41150ec49
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsesc@npm:^3.0.2, jsesc@npm:~3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "jsesc@npm:3.0.2"
|
||||
|
@ -11389,6 +11459,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nwsapi@npm:^2.2.12":
|
||||
version: 2.2.16
|
||||
resolution: "nwsapi@npm:2.2.16"
|
||||
checksum: 10/1e5e086cdd4ca4a45f414d37f49bf0ca81d84ed31c6871ac68f531917d2910845db61f77c6d844430dc90fda202d43fce9603024e74038675de95229eb834dba
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object-assign@npm:^4":
|
||||
version: 4.1.1
|
||||
resolution: "object-assign@npm:4.1.1"
|
||||
|
@ -12220,7 +12297,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"punycode@npm:2.3.1, punycode@npm:^2.1.0":
|
||||
"punycode@npm:2.3.1, punycode@npm:^2.1.0, punycode@npm:^2.3.1":
|
||||
version: 2.3.1
|
||||
resolution: "punycode@npm:2.3.1"
|
||||
checksum: 10/febdc4362bead22f9e2608ff0171713230b57aff9dddc1c273aa2a651fbd366f94b7d6a71d78342a7c0819906750351ca7f2edd26ea41b626d87d6a13d1bd059
|
||||
|
@ -12673,8 +12750,8 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"rollup@npm:^2.43.1":
|
||||
version: 2.79.2
|
||||
resolution: "rollup@npm:2.79.2"
|
||||
version: 2.79.1
|
||||
resolution: "rollup@npm:2.79.1"
|
||||
dependencies:
|
||||
fsevents: "npm:~2.3.2"
|
||||
dependenciesMeta:
|
||||
|
@ -12682,7 +12759,7 @@ __metadata:
|
|||
optional: true
|
||||
bin:
|
||||
rollup: dist/bin/rollup
|
||||
checksum: 10/095ba0a82811b1866a76d826987743278db0a87c45092656986bfff490326b66187d5f9ff0c24cf8d5682bc470aa00c36654e0044d6b6335ac0c1201b8280880
|
||||
checksum: 10/df087b701304432f30922bbee5f534ab189aa6938bd383b5686c03147e0d00cd1789ea10a462361326ce6b6ebe448ce272ad3f3cc40b82eeb3157df12f33663c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -12764,6 +12841,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rrweb-cssom@npm:^0.7.1":
|
||||
version: 0.7.1
|
||||
resolution: "rrweb-cssom@npm:0.7.1"
|
||||
checksum: 10/e80cf25c223a823921d7ab57c0ce78f5b7ebceab857b400cce99dd4913420ce679834bc5707e8ada47d062e21ad368108a9534c314dc8d72c20aa4a4fa0ed16a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rslog@npm:^1.2.3":
|
||||
version: 1.2.3
|
||||
resolution: "rslog@npm:1.2.3"
|
||||
|
@ -12852,6 +12936,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"saxes@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "saxes@npm:6.0.0"
|
||||
dependencies:
|
||||
xmlchars: "npm:^2.2.0"
|
||||
checksum: 10/97b50daf6ca3a153e89842efa18a862e446248296622b7473c169c84c823ee8a16e4a43bac2f73f11fc8cb9168c73fbb0d73340f26552bac17970e9052367aa9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"schema-utils@npm:^3.1.1":
|
||||
version: 3.3.0
|
||||
resolution: "schema-utils@npm:3.3.0"
|
||||
|
@ -13721,6 +13814,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"symbol-tree@npm:^3.2.4":
|
||||
version: 3.2.4
|
||||
resolution: "symbol-tree@npm:3.2.4"
|
||||
checksum: 10/c09a00aadf279d47d0c5c46ca3b6b2fbaeb45f0a184976d599637d412d3a70bbdc043ff33effe1206dea0e36e0ad226cb957112e7ce9a4bf2daedf7fa4f85c53
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"systemjs@npm:6.15.1":
|
||||
version: 6.15.1
|
||||
resolution: "systemjs@npm:6.15.1"
|
||||
|
@ -13966,6 +14066,24 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tldts-core@npm:^6.1.65":
|
||||
version: 6.1.65
|
||||
resolution: "tldts-core@npm:6.1.65"
|
||||
checksum: 10/06ae49ae935674163d806aa14abf6bb49a6099878ef3940de035c6bf577cf31b78122df02d2e82d84d0a59faf8a837235d537ee7cbf9a5b0b97b44b7a5f19799
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tldts@npm:^6.1.32":
|
||||
version: 6.1.65
|
||||
resolution: "tldts@npm:6.1.65"
|
||||
dependencies:
|
||||
tldts-core: "npm:^6.1.65"
|
||||
bin:
|
||||
tldts: bin/cli.js
|
||||
checksum: 10/dc8afe3ac8af4f2dc4b4307d447b6877ee5942e4b8164ae170edf034a318241ca7f9bdf89d4560204363141344959aa4d28edc532397b210532e160acbf77b6a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"to-regex-range@npm:^5.0.1":
|
||||
version: 5.0.1
|
||||
resolution: "to-regex-range@npm:5.0.1"
|
||||
|
@ -14015,6 +14133,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tough-cookie@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "tough-cookie@npm:5.0.0"
|
||||
dependencies:
|
||||
tldts: "npm:^6.1.32"
|
||||
checksum: 10/a98d3846ed386e399e8b470c1eb08a6a296944246eabc55c9fe79d629bd2cdaa62f5a6572f271fe0060987906bd20468d72a219a3b4cbe51086bea48d2d677b6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tr46@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "tr46@npm:1.0.1"
|
||||
|
@ -14024,6 +14151,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tr46@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "tr46@npm:5.0.0"
|
||||
dependencies:
|
||||
punycode: "npm:^2.3.1"
|
||||
checksum: 10/29155adb167d048d3c95d181f7cb5ac71948b4e8f3070ec455986e1f34634acae50ae02a3c8d448121c3afe35b76951cd46ed4c128fd80264280ca9502237a3e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tree-dump@npm:^1.0.1":
|
||||
version: 1.0.2
|
||||
resolution: "tree-dump@npm:1.0.2"
|
||||
|
@ -14836,6 +14972,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"w3c-xmlserializer@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "w3c-xmlserializer@npm:5.0.0"
|
||||
dependencies:
|
||||
xml-name-validator: "npm:^5.0.0"
|
||||
checksum: 10/d78f59e6b4f924aa53b6dfc56949959229cae7fe05ea9374eb38d11edcec01398b7f5d7a12576bd5acc57ff446abb5c9115cd83b9d882555015437cf858d42f0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wbuf@npm:^1.1.0, wbuf@npm:^1.7.3":
|
||||
version: 1.7.3
|
||||
resolution: "wbuf@npm:1.7.3"
|
||||
|
@ -14876,6 +15021,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"webidl-conversions@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "webidl-conversions@npm:7.0.0"
|
||||
checksum: 10/4c4f65472c010eddbe648c11b977d048dd96956a625f7f8b9d64e1b30c3c1f23ea1acfd654648426ce5c743c2108a5a757c0592f02902cf7367adb7d14e67721
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"webpack-bundle-analyzer@npm:4.6.1":
|
||||
version: 4.6.1
|
||||
resolution: "webpack-bundle-analyzer@npm:4.6.1"
|
||||
|
@ -15035,6 +15187,32 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-encoding@npm:^3.1.1":
|
||||
version: 3.1.1
|
||||
resolution: "whatwg-encoding@npm:3.1.1"
|
||||
dependencies:
|
||||
iconv-lite: "npm:0.6.3"
|
||||
checksum: 10/bbef815eb67f91487c7f2ef96329743f5fd8357d7d62b1119237d25d41c7e452dff8197235b2d3c031365a17f61d3bb73ca49d0ed1582475aa4a670815e79534
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-mimetype@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "whatwg-mimetype@npm:4.0.0"
|
||||
checksum: 10/894a618e2d90bf444b6f309f3ceb6e58cf21b2beaa00c8b333696958c4076f0c7b30b9d33413c9ffff7c5832a0a0c8569e5bb347ef44beded72aeefd0acd62e8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-url@npm:^14.0.0":
|
||||
version: 14.1.0
|
||||
resolution: "whatwg-url@npm:14.1.0"
|
||||
dependencies:
|
||||
tr46: "npm:^5.0.0"
|
||||
webidl-conversions: "npm:^7.0.0"
|
||||
checksum: 10/3afd325de6cf3a367820ce7c3566a1f78eb1409c4f27b1867c74c76dab096d26acedf49a8b9b71db53df7d806ec2e9ae9ed96990b2f7d1abe6ecf1fe753af6eb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-url@npm:^7.0.0":
|
||||
version: 7.1.0
|
||||
resolution: "whatwg-url@npm:7.1.0"
|
||||
|
@ -15494,7 +15672,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:^8.16.0":
|
||||
"ws@npm:^8.16.0, ws@npm:^8.18.0":
|
||||
version: 8.18.0
|
||||
resolution: "ws@npm:8.18.0"
|
||||
peerDependencies:
|
||||
|
@ -15536,6 +15714,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"xml-name-validator@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "xml-name-validator@npm:5.0.0"
|
||||
checksum: 10/43f30f3f6786e406dd665acf08cd742d5f8a46486bd72517edb04b27d1bcd1599664c2a4a99fc3f1e56a3194bff588b12f178b7972bc45c8047bdc4c3ac8d4a1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"xml-parse-from-string@npm:^1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "xml-parse-from-string@npm:1.0.1"
|
||||
|
@ -15560,6 +15745,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"xmlchars@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "xmlchars@npm:2.2.0"
|
||||
checksum: 10/4ad5924974efd004a47cce6acf5c0269aee0e62f9a805a426db3337af7bcbd331099df174b024ace4fb18971b8a56de386d2e73a1c4b020e3abd63a4a9b917f1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"xss@npm:1.0.15":
|
||||
version: 1.0.15
|
||||
resolution: "xss@npm:1.0.15"
|
||||
|
|
Loading…
Reference in New Issue