mirror of
https://github.com/coder/coder.git
synced 2025-07-18 14:17:22 +00:00
chore: remove useLocalStorage
hook (#11712)
This commit is contained in:
committed by
GitHub
parent
fa99f6a200
commit
80eac73ed1
@ -327,7 +327,7 @@ func (c *DeviceAuth) AuthorizeDevice(ctx context.Context) (*codersdk.ExternalAut
|
|||||||
case http.StatusTooManyRequests:
|
case http.StatusTooManyRequests:
|
||||||
return nil, xerrors.New("rate limit hit, unable to authorize device. please try again later")
|
return nil, xerrors.New("rate limit hit, unable to authorize device. please try again later")
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("status_code=%d: %w", resp.StatusCode, err)
|
return nil, xerrors.Errorf("status_code=%d: %w", resp.StatusCode, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.ErrorDescription != "" {
|
if r.ErrorDescription != "" {
|
||||||
|
@ -1626,7 +1626,7 @@ func TestWorkspaceAgentExternalAuthListen(t *testing.T) {
|
|||||||
cancel()
|
cancel()
|
||||||
// We expect only 1
|
// We expect only 1
|
||||||
// In a failed test, you will likely see 9, as the last one
|
// In a failed test, you will likely see 9, as the last one
|
||||||
// gets cancelled.
|
// gets canceled.
|
||||||
require.Equal(t, 1, validateCalls, "validate calls duplicated on same token")
|
require.Equal(t, 1, validateCalls, "validate calls duplicated on same token")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -276,9 +276,9 @@ func (p *ProxyHealth) runOnce(ctx context.Context, now time.Time) (map[uuid.UUID
|
|||||||
case err == nil && resp.StatusCode == http.StatusOK:
|
case err == nil && resp.StatusCode == http.StatusOK:
|
||||||
err := json.NewDecoder(resp.Body).Decode(&status.Report)
|
err := json.NewDecoder(resp.Body).Decode(&status.Report)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
isCoderErr := fmt.Errorf("proxy url %q is not a coder proxy instance, verify the url is correct", reqURL)
|
isCoderErr := xerrors.Errorf("proxy url %q is not a coder proxy instance, verify the url is correct", reqURL)
|
||||||
if resp.Header.Get(codersdk.BuildVersionHeader) != "" {
|
if resp.Header.Get(codersdk.BuildVersionHeader) != "" {
|
||||||
isCoderErr = fmt.Errorf("proxy url %q is a coder instance, but unable to decode the response payload. Could this be a primary coderd and not a proxy?", reqURL)
|
isCoderErr = xerrors.Errorf("proxy url %q is a coder instance, but unable to decode the response payload. Could this be a primary coderd and not a proxy?", reqURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the response is not json, then the user likely input a bad url that returns status code 200.
|
// If the response is not json, then the user likely input a bad url that returns status code 200.
|
||||||
@ -286,7 +286,7 @@ func (p *ProxyHealth) runOnce(ctx context.Context, now time.Time) (map[uuid.UUID
|
|||||||
if notJSONErr := codersdk.ExpectJSONMime(resp); notJSONErr != nil {
|
if notJSONErr := codersdk.ExpectJSONMime(resp); notJSONErr != nil {
|
||||||
err = errors.Join(
|
err = errors.Join(
|
||||||
isCoderErr,
|
isCoderErr,
|
||||||
fmt.Errorf("attempted to query health at %q but got back the incorrect content type: %w", reqURL, notJSONErr),
|
xerrors.Errorf("attempted to query health at %q but got back the incorrect content type: %w", reqURL, notJSONErr),
|
||||||
)
|
)
|
||||||
|
|
||||||
status.Report.Errors = []string{
|
status.Report.Errors = []string{
|
||||||
@ -300,7 +300,7 @@ func (p *ProxyHealth) runOnce(ctx context.Context, now time.Time) (map[uuid.UUID
|
|||||||
status.Report.Errors = []string{
|
status.Report.Errors = []string{
|
||||||
errors.Join(
|
errors.Join(
|
||||||
isCoderErr,
|
isCoderErr,
|
||||||
fmt.Errorf("received a status code 200, but failed to decode health report body: %w", err),
|
xerrors.Errorf("received a status code 200, but failed to decode health report body: %w", err),
|
||||||
).Error(),
|
).Error(),
|
||||||
}
|
}
|
||||||
status.Status = Unhealthy
|
status.Status = Unhealthy
|
||||||
|
@ -159,7 +159,7 @@ func New(ctx context.Context, opts *Options) (*Server, error) {
|
|||||||
info, err := client.SDKClient.BuildInfo(ctx)
|
info, err := client.SDKClient.BuildInfo(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("buildinfo: %w", errors.Join(
|
return nil, fmt.Errorf("buildinfo: %w", errors.Join(
|
||||||
fmt.Errorf("unable to fetch build info from primary coderd. Are you sure %q is a coderd instance?", opts.DashboardURL),
|
xerrors.Errorf("unable to fetch build info from primary coderd. Are you sure %q is a coderd instance?", opts.DashboardURL),
|
||||||
err,
|
err,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -1,12 +1,6 @@
|
|||||||
import { type PropsWithChildren } from "react";
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { Abbr } from "./Abbr";
|
import { Abbr } from "./Abbr";
|
||||||
|
|
||||||
// Just here to make the abbreviated part more obvious in the component library
|
|
||||||
const Underline = ({ children }: PropsWithChildren) => (
|
|
||||||
<span css={{ textDecoration: "underline dotted" }}>{children}</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
const meta: Meta<typeof Abbr> = {
|
const meta: Meta<typeof Abbr> = {
|
||||||
title: "components/Abbr",
|
title: "components/Abbr",
|
||||||
component: Abbr,
|
component: Abbr,
|
||||||
@ -34,9 +28,9 @@ export const InlinedShorthand: Story = {
|
|||||||
<p css={{ maxWidth: "40em" }}>
|
<p css={{ maxWidth: "40em" }}>
|
||||||
The physical pain of getting bonked on the head with a cartoon mallet
|
The physical pain of getting bonked on the head with a cartoon mallet
|
||||||
lasts precisely 593{" "}
|
lasts precisely 593{" "}
|
||||||
<Underline>
|
<span css={styles.underlined}>
|
||||||
<Story />
|
<Story />
|
||||||
</Underline>
|
</span>
|
||||||
. The emotional turmoil and complete embarrassment lasts forever.
|
. The emotional turmoil and complete embarrassment lasts forever.
|
||||||
</p>
|
</p>
|
||||||
),
|
),
|
||||||
@ -51,9 +45,9 @@ export const Acronym: Story = {
|
|||||||
},
|
},
|
||||||
decorators: [
|
decorators: [
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<Underline>
|
<span css={styles.underlined}>
|
||||||
<Story />
|
<Story />
|
||||||
</Underline>
|
</span>
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@ -66,9 +60,16 @@ export const Initialism: Story = {
|
|||||||
},
|
},
|
||||||
decorators: [
|
decorators: [
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<Underline>
|
<span css={styles.underlined}>
|
||||||
<Story />
|
<Story />
|
||||||
</Underline>
|
</span>
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
// Just here to make the abbreviated part more obvious in the component library
|
||||||
|
underlined: {
|
||||||
|
textDecoration: "underline dotted",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
@ -14,7 +14,7 @@ const createWrapper = (): FC<PropsWithChildren> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
window.localStorage.clear();
|
localStorage.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("is dismissed when does not have permission to see it", () => {
|
it("is dismissed when does not have permission to see it", () => {
|
||||||
@ -57,7 +57,7 @@ it("is dismissed when it was dismissed previously", async () => {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
window.localStorage.setItem("dismissedVersion", MockUpdateCheck.version);
|
localStorage.setItem("dismissedVersion", MockUpdateCheck.version);
|
||||||
const { result } = renderHook(() => useUpdateCheck(true), {
|
const { result } = renderHook(() => useUpdateCheck(true), {
|
||||||
wrapper: createWrapper(),
|
wrapper: createWrapper(),
|
||||||
});
|
});
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { FC, PropsWithChildren, useState, useRef } from "react";
|
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||||
|
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||||
|
import Menu from "@mui/material/Menu";
|
||||||
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
|
import { type FC, useState, useRef } from "react";
|
||||||
import { getApiKey } from "api/api";
|
import { getApiKey } from "api/api";
|
||||||
|
import { DisplayApp } from "api/typesGenerated";
|
||||||
import { VSCodeIcon } from "components/Icons/VSCodeIcon";
|
import { VSCodeIcon } from "components/Icons/VSCodeIcon";
|
||||||
import { VSCodeInsidersIcon } from "components/Icons/VSCodeInsidersIcon";
|
import { VSCodeInsidersIcon } from "components/Icons/VSCodeInsidersIcon";
|
||||||
import { AgentButton } from "components/Resources/AgentButton";
|
import { AgentButton } from "components/Resources/AgentButton";
|
||||||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
|
||||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
|
||||||
import { useLocalStorage } from "hooks";
|
|
||||||
import Menu from "@mui/material/Menu";
|
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
|
||||||
import { DisplayApp } from "api/typesGenerated";
|
|
||||||
import { DisplayAppNameMap } from "../AppLink/AppLink";
|
import { DisplayAppNameMap } from "../AppLink/AppLink";
|
||||||
|
|
||||||
export interface VSCodeDesktopButtonProps {
|
export interface VSCodeDesktopButtonProps {
|
||||||
@ -23,12 +22,9 @@ type VSCodeVariant = "vscode" | "vscode-insiders";
|
|||||||
|
|
||||||
const VARIANT_KEY = "vscode-variant";
|
const VARIANT_KEY = "vscode-variant";
|
||||||
|
|
||||||
export const VSCodeDesktopButton: FC<
|
export const VSCodeDesktopButton: FC<VSCodeDesktopButtonProps> = (props) => {
|
||||||
PropsWithChildren<VSCodeDesktopButtonProps>
|
|
||||||
> = (props) => {
|
|
||||||
const [isVariantMenuOpen, setIsVariantMenuOpen] = useState(false);
|
const [isVariantMenuOpen, setIsVariantMenuOpen] = useState(false);
|
||||||
const localStorage = useLocalStorage();
|
const previousVariant = localStorage.getItem(VARIANT_KEY);
|
||||||
const previousVariant = localStorage.getLocal(VARIANT_KEY);
|
|
||||||
const [variant, setVariant] = useState<VSCodeVariant>(() => {
|
const [variant, setVariant] = useState<VSCodeVariant>(() => {
|
||||||
if (!previousVariant) {
|
if (!previousVariant) {
|
||||||
return "vscode";
|
return "vscode";
|
||||||
@ -38,7 +34,7 @@ export const VSCodeDesktopButton: FC<
|
|||||||
const menuAnchorRef = useRef<HTMLDivElement>(null);
|
const menuAnchorRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const selectVariant = (variant: VSCodeVariant) => {
|
const selectVariant = (variant: VSCodeVariant) => {
|
||||||
localStorage.saveLocal(VARIANT_KEY, variant);
|
localStorage.setItem(VARIANT_KEY, variant);
|
||||||
setVariant(variant);
|
setVariant(variant);
|
||||||
setIsVariantMenuOpen(false);
|
setIsVariantMenuOpen(false);
|
||||||
};
|
};
|
||||||
@ -109,12 +105,12 @@ export const VSCodeDesktopButton: FC<
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const VSCodeButton = ({
|
const VSCodeButton: FC<VSCodeDesktopButtonProps> = ({
|
||||||
userName,
|
userName,
|
||||||
workspaceName,
|
workspaceName,
|
||||||
agentName,
|
agentName,
|
||||||
folderPath,
|
folderPath,
|
||||||
}: VSCodeDesktopButtonProps) => {
|
}) => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -153,12 +149,12 @@ const VSCodeButton = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const VSCodeInsidersButton = ({
|
const VSCodeInsidersButton: FC<VSCodeDesktopButtonProps> = ({
|
||||||
userName,
|
userName,
|
||||||
workspaceName,
|
workspaceName,
|
||||||
agentName,
|
agentName,
|
||||||
folderPath,
|
folderPath,
|
||||||
}: VSCodeDesktopButtonProps) => {
|
}) => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -19,7 +19,7 @@ import { screen } from "@testing-library/react";
|
|||||||
import { server } from "testHelpers/server";
|
import { server } from "testHelpers/server";
|
||||||
import { rest } from "msw";
|
import { rest } from "msw";
|
||||||
import { Region } from "api/typesGenerated";
|
import { Region } from "api/typesGenerated";
|
||||||
import "testHelpers/localstorage";
|
import "testHelpers/localStorage";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
|
|
||||||
// Mock useProxyLatency to use a hard-coded latency. 'jest.mock' must be called
|
// Mock useProxyLatency to use a hard-coded latency. 'jest.mock' must be called
|
||||||
@ -187,7 +187,7 @@ interface ProxyContextSelectionTest {
|
|||||||
|
|
||||||
describe("ProxyContextSelection", () => {
|
describe("ProxyContextSelection", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
window.localStorage.clear();
|
localStorage.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
// A way to simulate a user clearing the proxy selection.
|
// A way to simulate a user clearing the proxy selection.
|
||||||
|
@ -310,11 +310,11 @@ const computeUsableURLS = (proxy?: Region): PreferredProxy => {
|
|||||||
// Local storage functions
|
// Local storage functions
|
||||||
|
|
||||||
export const clearUserSelectedProxy = (): void => {
|
export const clearUserSelectedProxy = (): void => {
|
||||||
window.localStorage.removeItem("user-selected-proxy");
|
localStorage.removeItem("user-selected-proxy");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const saveUserSelectedProxy = (saved: Region): void => {
|
export const saveUserSelectedProxy = (saved: Region): void => {
|
||||||
window.localStorage.setItem("user-selected-proxy", JSON.stringify(saved));
|
localStorage.setItem("user-selected-proxy", JSON.stringify(saved));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadUserSelectedProxy = (): Region | undefined => {
|
export const loadUserSelectedProxy = (): Region | undefined => {
|
||||||
|
@ -2,7 +2,6 @@ export * from "./useClickable";
|
|||||||
export * from "./useClickableTableRow";
|
export * from "./useClickableTableRow";
|
||||||
export * from "./useClipboard";
|
export * from "./useClipboard";
|
||||||
export * from "./useFeatureVisibility";
|
export * from "./useFeatureVisibility";
|
||||||
export * from "./useLocalStorage";
|
|
||||||
export * from "./useMe";
|
export * from "./useMe";
|
||||||
export * from "./useOrganizationId";
|
export * from "./useOrganizationId";
|
||||||
export * from "./usePagination";
|
export * from "./usePagination";
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
export const useLocalStorage = () => {
|
|
||||||
return {
|
|
||||||
saveLocal,
|
|
||||||
getLocal,
|
|
||||||
clearLocal,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveLocal = (itemKey: string, itemValue: string): void => {
|
|
||||||
window.localStorage.setItem(itemKey, itemValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getLocal = (itemKey: string): string | undefined => {
|
|
||||||
return localStorage.getItem(itemKey) ?? undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearLocal = (itemKey: string): void => {
|
|
||||||
localStorage.removeItem(itemKey);
|
|
||||||
};
|
|
@ -3,13 +3,13 @@ import { updateTemplateMeta } from "api/api";
|
|||||||
import { UpdateTemplateMeta } from "api/typesGenerated";
|
import { UpdateTemplateMeta } from "api/typesGenerated";
|
||||||
import { useDashboard } from "components/Dashboard/DashboardProvider";
|
import { useDashboard } from "components/Dashboard/DashboardProvider";
|
||||||
import { displaySuccess } from "components/GlobalSnackbar/utils";
|
import { displaySuccess } from "components/GlobalSnackbar/utils";
|
||||||
import { FC } from "react";
|
import { type FC } from "react";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { pageTitle } from "utils/page";
|
import { pageTitle } from "utils/page";
|
||||||
import { useTemplateSettings } from "../TemplateSettingsLayout";
|
import { useTemplateSettings } from "../TemplateSettingsLayout";
|
||||||
import { TemplateSchedulePageView } from "./TemplateSchedulePageView";
|
import { TemplateSchedulePageView } from "./TemplateSchedulePageView";
|
||||||
import { useLocalStorage, useOrganizationId } from "hooks";
|
import { useOrganizationId } from "hooks";
|
||||||
import { templateByNameKey } from "api/queries/templates";
|
import { templateByNameKey } from "api/queries/templates";
|
||||||
|
|
||||||
const TemplateSchedulePage: FC = () => {
|
const TemplateSchedulePage: FC = () => {
|
||||||
@ -21,7 +21,6 @@ const TemplateSchedulePage: FC = () => {
|
|||||||
const { entitlements } = useDashboard();
|
const { entitlements } = useDashboard();
|
||||||
const allowAdvancedScheduling =
|
const allowAdvancedScheduling =
|
||||||
entitlements.features["advanced_template_scheduling"].enabled;
|
entitlements.features["advanced_template_scheduling"].enabled;
|
||||||
const { clearLocal } = useLocalStorage();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mutate: updateTemplate,
|
mutate: updateTemplate,
|
||||||
@ -36,8 +35,8 @@ const TemplateSchedulePage: FC = () => {
|
|||||||
);
|
);
|
||||||
displaySuccess("Template updated successfully");
|
displaySuccess("Template updated successfully");
|
||||||
// clear browser storage of workspaces impending deletion
|
// clear browser storage of workspaces impending deletion
|
||||||
clearLocal("dismissedWorkspaceList"); // workspaces page
|
localStorage.removeItem("dismissedWorkspaceList"); // workspaces page
|
||||||
clearLocal("dismissedWorkspace"); // workspace page
|
localStorage.removeItem("dismissedWorkspace"); // workspace page
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -64,13 +64,13 @@ export interface TemplateVersionEditorProps {
|
|||||||
defaultFileTree: FileTree;
|
defaultFileTree: FileTree;
|
||||||
buildLogs?: ProvisionerJobLog[];
|
buildLogs?: ProvisionerJobLog[];
|
||||||
resources?: WorkspaceResource[];
|
resources?: WorkspaceResource[];
|
||||||
disablePreview: boolean;
|
disablePreview?: boolean;
|
||||||
disableUpdate: boolean;
|
disableUpdate?: boolean;
|
||||||
onPreview: (files: FileTree) => void;
|
onPreview: (files: FileTree) => void;
|
||||||
onPublish: () => void;
|
onPublish: () => void;
|
||||||
onConfirmPublish: (data: PublishVersionData) => void;
|
onConfirmPublish: (data: PublishVersionData) => void;
|
||||||
onCancelPublish: () => void;
|
onCancelPublish: () => void;
|
||||||
publishingError: unknown;
|
publishingError?: unknown;
|
||||||
publishedVersion?: TemplateVersion;
|
publishedVersion?: TemplateVersion;
|
||||||
onCreateWorkspace: () => void;
|
onCreateWorkspace: () => void;
|
||||||
isAskingPublishParameters: boolean;
|
isAskingPublishParameters: boolean;
|
||||||
|
@ -317,7 +317,7 @@ describe("WorkspacePage", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("restart the workspace with one time parameters when having the confirmation dialog", async () => {
|
it("restart the workspace with one time parameters when having the confirmation dialog", async () => {
|
||||||
window.localStorage.removeItem(`${MockUser.id}_ignoredWarnings`);
|
localStorage.removeItem(`${MockUser.id}_ignoredWarnings`);
|
||||||
jest.spyOn(api, "getWorkspaceParameters").mockResolvedValue({
|
jest.spyOn(api, "getWorkspaceParameters").mockResolvedValue({
|
||||||
templateVersionRichParameters: [
|
templateVersionRichParameters: [
|
||||||
{
|
{
|
||||||
|
37
site/src/testHelpers/localStorage.ts
Normal file
37
site/src/testHelpers/localStorage.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
export const localStorageMock = (): Storage => {
|
||||||
|
const store = new Map<string, string>();
|
||||||
|
|
||||||
|
return {
|
||||||
|
getItem: (key) => {
|
||||||
|
return store.get(key) ?? null;
|
||||||
|
},
|
||||||
|
setItem: (key: string, value: string) => {
|
||||||
|
store.set(key, value);
|
||||||
|
},
|
||||||
|
clear: () => {
|
||||||
|
store.clear();
|
||||||
|
},
|
||||||
|
removeItem: (key: string) => {
|
||||||
|
store.delete(key);
|
||||||
|
},
|
||||||
|
|
||||||
|
get length() {
|
||||||
|
return store.size;
|
||||||
|
},
|
||||||
|
|
||||||
|
key: (index) => {
|
||||||
|
const values = store.values();
|
||||||
|
let value: IteratorResult<string, undefined> = values.next();
|
||||||
|
for (let i = 1; i < index && !value.done; i++) {
|
||||||
|
value = values.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.value ?? null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.defineProperty(globalThis, "localStorage", {
|
||||||
|
value: localStorageMock(),
|
||||||
|
writable: false,
|
||||||
|
});
|
@ -1,22 +0,0 @@
|
|||||||
export const localStorageMock = () => {
|
|
||||||
const store = {} as Record<string, string>;
|
|
||||||
|
|
||||||
return {
|
|
||||||
getItem: (key: string): string => {
|
|
||||||
return store[key];
|
|
||||||
},
|
|
||||||
setItem: (key: string, value: string) => {
|
|
||||||
store[key] = value;
|
|
||||||
},
|
|
||||||
clear: () => {
|
|
||||||
Object.keys(store).forEach((key) => {
|
|
||||||
delete store[key];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
removeItem: (key: string) => {
|
|
||||||
delete store[key];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.defineProperty(window, "localStorage", { value: localStorageMock() });
|
|
Reference in New Issue
Block a user