Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wd 15262 improve performance of metric page #4860

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ module.exports = {
moduleNameMapper: {
"\\.(scss|sass|css)$": "identity-obj-proxy",
},
globals: {
fetch: global.fetch,
},
};
28 changes: 28 additions & 0 deletions static/js/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,32 @@ declare interface Window {
MktoForms2: any;
Vimeo: any;
DNS_VERIFICATION_TOKEN: string;
SENTRY_DSN: string;
CSRF_TOKEN: string;
SNAP_PUBLICISE_DATA: {
hasScreenshot: boolean;
isReleased: boolean;
private: boolean;
trending: boolean;
};
SNAP_SETTINGS_DATA: {
blacklist_countries: string[];
blacklist_country_keys: string;
countries: Array<{ key: string; name: string }>;
country_keys_status: string | null;
private: boolean;
publisher_name: string;
snap_id: string;
snap_name: string;
snap_title: string;
status: string;
store: string;
territory_distribution_status: string;
unlisted: boolean;
update_metadata_on_release: boolean;
visibility: string;
visibility_locked: boolean;
whitelist_countries: string[];
whitelist_country_keys: string;
};
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useLocation } from "react-router-dom";
import {
SideNavigation,
SideNavigationText,
Expand All @@ -12,6 +13,7 @@ function PrimaryNav({
collapseNavigation: boolean;
setCollapseNavigation: Function;
}): JSX.Element {
const location = useLocation();
const { data: publisherData } = usePublisher();

return (
Expand Down Expand Up @@ -47,7 +49,7 @@ function PrimaryNav({
label: "My validation sets",
href: "/validation-sets",
icon: "topic",
"aria-current": "page",
"aria-current": location.pathname.includes("/validation-sets"),
},
],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";

import SaveAndPreview from "./SaveAndPreview";

const reset = jest.fn();

const renderComponent = (
isDirty: boolean,
isSaving: boolean,
isValid: boolean
) => {
return render(
<SaveAndPreview
snapName="test-snap-name"
isDirty={isDirty}
reset={reset}
isSaving={isSaving}
isValid={isValid}
/>
);
};

test("the 'Revert' button is disabled by default", () => {
renderComponent(false, false, true);
expect(screen.getByRole("button", { name: "Revert" })).toHaveAttribute("aria-disabled","true");
});

test("the 'Revert' button is enabled is data is dirty", () => {
renderComponent(true, false, true);
expect(screen.getByRole("button", { name: "Revert" })).not.toBeDisabled();
});

test("the 'Save' button is disabled by default", () => {
renderComponent(false, false, true);
expect(screen.getByRole("button", { name: "Save" })).toHaveAttribute("aria-disabled","true");
});

test("the 'Save' button is enabled is data is dirty", () => {
renderComponent(true, false, true);
expect(screen.getByRole("button", { name: "Save" })).not.toBeDisabled();
});

test("the 'Save' button shows loading state if saving", () => {
renderComponent(true, true, true);
expect(screen.getByRole("button", { name: "Saving" })).toBeInTheDocument();
});

test("the 'Save' button is disabled when saving", () => {
renderComponent(true, true, true);
expect(screen.getByRole("button", { name: "Saving" })).toHaveAttribute("aria-disabled","true");
});

test("the 'Save' button is disabled if the form is invalid", () => {
renderComponent(false, false, false);
expect(screen.getByRole("button", { name: "Save" })).toHaveAttribute("aria-disabled","true");
});

test("revert button resets the form", async () => {
const user = userEvent.setup();
renderComponent(true, false, true);
await user.click(screen.getByRole("button", { name: "Revert" }));
await waitFor(() => {
expect(reset).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useEffect, useRef } from "react";
import { Row, Col, Button } from "@canonical/react-components";

import debounce from "../../../libs/debounce";

type Props = {
snapName: string;
isDirty: boolean;
reset: Function;
isSaving: boolean;
isValid: boolean;
showPreview?: boolean;
};

function SaveAndPreview({
snapName,
isDirty,
reset,
isSaving,
showPreview,
}: Props) {
const stickyBar = useRef<HTMLDivElement>(null);
const handleScroll = () => {
stickyBar?.current?.classList.toggle(
"sticky-shadow",
stickyBar?.current?.getBoundingClientRect()?.top === 0
);
};

useEffect(() => {
document.addEventListener("scroll", debounce(handleScroll, 10, false));
}, []);

return (
<>
<div className="snapcraft-p-sticky js-sticky-bar" ref={stickyBar}>
<Row>
<Col size={7}>
<p className="u-no-margin--bottom">
Updates to this information will appear immediately on the{" "}
<a href={`/${snapName}`}>snap listing page</a>.
</p>
</Col>
<Col size={5}>
<div className="u-align--right">
{showPreview && (
<Button
type="submit"
className="p-button--base p-tooltip--btm-center"
aria-describedby="preview-tooltip"
form="preview-form"
>
Preview
<span
className="p-tooltip__message"
role="tooltip"
id="preview-tooltip"
>
Previews will only work in the same browser, locally
</span>
</Button>
)}
<Button
appearance="default"
disabled={!isDirty}
type="reset"
onClick={() => {
reset();
}}
>
Revert
</Button>
<Button
appearance="positive"
disabled={!isDirty || isSaving}
type="submit"
style={{ minWidth: "68px" }}
>
{isSaving ? (
<i className="p-icon--spinner is-light u-animation--spin">
Saving
</i>
) : (
"Save"
)}
</Button>
</div>
</Col>
</Row>
</div>
<div className="u-fixed-width">
<hr className="u-no-margin--bottom" />
</div>
</>
);
}

export default SaveAndPreview;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./SaveAndPreview";
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Notification } from "@canonical/react-components";

type Props = {
hasSaved: boolean;
setHasSaved: Function;
savedError: boolean | Array<{ message: string }>;
setSavedError: Function;
};

function SaveStateNotifications({
hasSaved,
setHasSaved,
savedError,
setSavedError,
}: Props) {
return (
<>
{hasSaved && (
<div className="u-fixed-width">
<Notification
severity="positive"
title="Changes applied successfully."
onDismiss={() => {
setHasSaved(false);
}}
/>
</div>
)}

{savedError && (
<div className="u-fixed-width">
<Notification
severity="negative"
title="Error"
onDismiss={() => {
setHasSaved(false);
setSavedError(false);
}}
>
Changes have not been saved.
<br />
{savedError === true
? "Something went wrong."
: savedError.map((error) => `${error.message}`).join("\n")}
</Notification>
</div>
)}
</>
);
}

export default SaveStateNotifications;
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { screen, render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";

import SaveStateNotifications from "../SaveStateNotifications";

type Options = {
hasSaved?: boolean;
setHasSaved?: Function;
savedError?: boolean | Array<{ message: string }>;
setSavedError?: Function;
};

const renderComponent = (options: Options) => {
return render(
<SaveStateNotifications
hasSaved={options.hasSaved || false}
setHasSaved={options.setHasSaved || jest.fn()}
savedError={options.savedError || false}
setSavedError={options.setSavedError || jest.fn()}
/>
);
};

describe("SaveStateNotifications", () => {
test("shows success notification if saved", () => {
renderComponent({ hasSaved: true });
expect(
screen.getByRole("heading", { name: "Changes applied successfully." })
).toBeInTheDocument();
});

test("doesn't show success notification if not saved", () => {
renderComponent({ hasSaved: false });
expect(
screen.queryByRole("heading", { name: "Changes applied successfully." })
).not.toBeInTheDocument();
});

test("success notifcation can be closed", async () => {
const user = userEvent.setup();
const setHasSaved = jest.fn();
renderComponent({ hasSaved: true, setHasSaved });
await user.click(
screen.getByRole("button", { name: "Close notification" })
);
expect(setHasSaved).toHaveBeenCalled();
});

test("shows error notification if saved", () => {
renderComponent({ savedError: true });
expect(
screen.getByText(/Changes have not been saved./)
).toBeInTheDocument();
});

test("doesn't show error notification if not saved", () => {
renderComponent({ savedError: false });
expect(
screen.queryByText(/Changes have not been saved./)
).not.toBeInTheDocument();
});

test("shows generic error if message is boolean", () => {
renderComponent({ savedError: true });
expect(screen.getByText(/Something went wrong./)).toBeInTheDocument();
});

test("shows custom error if message is an array", () => {
renderComponent({
savedError: [
{ message: "Saving error" },
{ message: "Field is required" },
],
});
expect(screen.getByText(/Saving error/)).toBeInTheDocument();
expect(screen.getByText(/Field is required/)).toBeInTheDocument();
});

test("error notifcation can be closed", async () => {
const user = userEvent.setup();
const setHasSaved = jest.fn();
renderComponent({ savedError: true, setHasSaved });
await user.click(
screen.getByRole("button", { name: "Close notification" })
);
expect(setHasSaved).toHaveBeenCalled();
});

test("error notifcation can be cleared", async () => {
const user = userEvent.setup();
const setSavedError = jest.fn();
renderComponent({ savedError: true, setSavedError });
await user.click(
screen.getByRole("button", { name: "Close notification" })
);
expect(setSavedError).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./SaveStateNotifications";
Loading