diff --git a/.eslintrc.js b/.eslintrc.js index b4da6dbcb9..d24fd74c0a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,6 @@ module.exports = { parser: "@babel/eslint-parser", - plugins: ["jest", "react"], + plugins: ["jest", "react", "prettier"], globals: {}, env: { browser: true, @@ -18,15 +18,27 @@ module.exports = { ecmaFeatures: { jsx: true, }, + requireConfigFile: false, }, rules: { "linebreak-style": ["error", "unix"], semi: ["error", "always"], "object-curly-spacing": ["error", "always"], + "prettier/prettier": "error", + "react/react-in-jsx-scope": "off", + "react/no-unescaped-entities": "off", }, settings: { react: { - version: "detect" - } - } + version: "detect", + }, + }, + overrides: [ + { + files: ["*.ts", "*.tsx"], + parser: "@typescript-eslint/parser", + plugins: ["@typescript-eslint"], + extends: ["plugin:@typescript-eslint/recommended"], + }, + ], }; diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index bfae45ced9..227d5d8b26 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -112,12 +112,39 @@ jobs: if: ${{ steps.filter.outputs.js == 'true' || steps.filter.outputs.ts == 'true' || steps.filter.outputs.jsx == 'true' || steps.filter.outputs.tsx == 'true' }} run: yarn lint-js + lint-a11y: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + js: + - '**/*.js' + ts: + - '**/*.ts' + jsx: + - '**/*.jsx' + tsx: + - '**/*.tsx' + + - name: Install JS dependencies + if: ${{ steps.filter.outputs.js == 'true' || steps.filter.outputs.ts == 'true' || steps.filter.outputs.jsx == 'true' || steps.filter.outputs.tsx == 'true' }} + run: yarn install --immutable + + - name: Lint JS for Accessibility + if: ${{ steps.filter.outputs.js == 'true' || steps.filter.outputs.ts == 'true' || steps.filter.outputs.jsx == 'true' || steps.filter.outputs.tsx == 'true' }} + run: yarn lint-a11y + test-python: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - + - uses: dorny/paths-filter@v3 id: filter with: diff --git a/eslint-a11y.config.js b/eslint-a11y.config.js new file mode 100644 index 0000000000..330ca6919e --- /dev/null +++ b/eslint-a11y.config.js @@ -0,0 +1,30 @@ +module.exports = { + parser: "@babel/eslint-parser", + plugins: ["jsx-a11y"], + env: { + browser: true, + es6: true, + node: true, + }, + extends: [ + "plugin:jsx-a11y/recommended", + ], + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + requireConfigFile: false, + }, + overrides: [ + { + files: ["*.ts", "*.tsx"], + parser: "@typescript-eslint/parser", + plugins: ["jsx-a11y"], + extends: [ + "plugin:jsx-a11y/recommended", + ], + }, + ], +}; diff --git a/package.json b/package.json index d08767b3cf..65f6728cf5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "scripts": { - "lint-js": "eslint static/js", + "lint-js": "eslint static/js --ext .js,.jsx,.ts,.tsx", + "lint-a11y": "eslint -c eslint-a11y.config.js static/js --ext .js,.jsx,.ts,.tsx", "lint-scss": "stylelint static/**/*.scss", "lint-python": "flake8 webapp tests && black --diff --check --line-length 79 webapp tests", "test": "yarn run test-python && yarn run test-js-all && yarn run lint-scss", @@ -111,11 +112,14 @@ "@types/d3": "7.4.3", "@types/markdown-it": "14.1.1", "@types/uuid": "10.0.0", + "@typescript-eslint/eslint-plugin": "^7.17.0", + "@typescript-eslint/parser": "^7.17.0", "babel-jest": "29.7.0", "concurrently": "8.2.2", "eslint": "8.56.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-jest": "27.6.1", + "eslint-plugin-jsx-a11y": "^6.9.0", "eslint-plugin-prettier": "5.1.3", "eslint-plugin-react": "7.33.2", "identity-obj-proxy": "3.0.0", diff --git a/static/js/base/contactForm.ts b/static/js/base/contactForm.ts index 5ff7a51619..11c5dae091 100644 --- a/static/js/base/contactForm.ts +++ b/static/js/base/contactForm.ts @@ -1,5 +1,5 @@ const contactFormTriggers = document.querySelectorAll( - "[data-js='contact-form-trigger']" + "[data-js='contact-form-trigger']", ) as NodeListOf; const modal = document.getElementById("contact-form-modal") as HTMLElement; @@ -19,7 +19,7 @@ function handleClick(event: Event): void { const args = target.dataset; const formEl = document.getElementById( - "contactFormTemplate" + "contactFormTemplate", ) as HTMLFormElement; let formTemplate = formEl.innerText; diff --git a/static/js/base/dropdown-menu-toggle.ts b/static/js/base/dropdown-menu-toggle.ts index 4d27ecf02e..e249fc36a6 100644 --- a/static/js/base/dropdown-menu-toggle.ts +++ b/static/js/base/dropdown-menu-toggle.ts @@ -1,9 +1,9 @@ (function () { function toggleDropdown(toggle: HTMLElement, open: boolean) { - var parentElement = toggle.parentNode as HTMLElement; - var dropdownElId = toggle.getAttribute("aria-controls") as string; + const parentElement = toggle.parentNode as HTMLElement; + const dropdownElId = toggle.getAttribute("aria-controls") as string; - var dropdown = document.getElementById(dropdownElId) as HTMLElement; + const dropdown = document.getElementById(dropdownElId) as HTMLElement; const openMenu = !open; @@ -26,10 +26,10 @@ function handleClickOutside( toggles: Array, - containerClass: string + containerClass: string, ) { document.addEventListener("click", function (event) { - var target = event.target as HTMLElement; + const target = event.target as HTMLElement; if (target.closest) { if (!target.closest(containerClass)) { @@ -40,8 +40,8 @@ } function initNavDropdowns(containerClass: string) { - var toggles = [].slice.call( - document.querySelectorAll(containerClass + " [aria-controls]") + const toggles = [].slice.call( + document.querySelectorAll(containerClass + " [aria-controls]"), ); handleClickOutside(toggles, containerClass); @@ -50,7 +50,7 @@ toggle.addEventListener("click", function (e) { e.preventDefault(); - var parentElement = toggle.parentNode as HTMLElement; + const parentElement = toggle.parentNode as HTMLElement; if (parentElement.classList.contains("is-active")) { toggleDropdown(toggle, false); } else { diff --git a/static/js/base/ga.ts b/static/js/base/ga.ts index a68a6816e3..3ad96022c0 100644 --- a/static/js/base/ga.ts +++ b/static/js/base/ga.ts @@ -25,7 +25,7 @@ function triggerEvent( category: string, from: string, to: string, - label: string + label: string, ): void { if (window.dataLayer) { window.dataLayer.push({ @@ -70,7 +70,7 @@ if (typeof window.dataLayer !== "undefined") { return; } - for (let key in events) { + for (const key in events) { if (target.matches(key)) { // This prevents subsequent matches triggering // So the order the events are added is important! diff --git a/static/js/base/navigation.ts b/static/js/base/navigation.ts index db2735e43e..432064113a 100644 --- a/static/js/base/navigation.ts +++ b/static/js/base/navigation.ts @@ -1,22 +1,22 @@ // Login -var navAccountContainer = document.querySelector( - ".js-nav-account" +const navAccountContainer = document.querySelector( + ".js-nav-account", ) as HTMLElement; if (navAccountContainer) { - var notAuthenticatedMenu = navAccountContainer.querySelector( - ".js-nav-account--notauthenticated" + const notAuthenticatedMenu = navAccountContainer.querySelector( + ".js-nav-account--notauthenticated", ) as HTMLElement; - var authenticatedMenu = navAccountContainer.querySelector( - ".js-nav-account--authenticated" + const authenticatedMenu = navAccountContainer.querySelector( + ".js-nav-account--authenticated", ) as HTMLElement; fetch("/account.json") .then((response) => response.json()) .then((data: { publisher: { fullname: string; has_stores: boolean } }) => { if (data.publisher) { - var displayName = navAccountContainer.querySelector( - ".js-account--name" + const displayName = navAccountContainer.querySelector( + ".js-account--name", ) as HTMLElement; notAuthenticatedMenu.classList.add("u-hide"); @@ -25,13 +25,13 @@ if (navAccountContainer) { if (window.sessionStorage) { window.sessionStorage.setItem( "displayName", - data.publisher["fullname"] + data.publisher["fullname"], ); } if (data.publisher.has_stores) { const storesMenu = authenticatedMenu.querySelector( - ".js-nav-account--stores" + ".js-nav-account--stores", ) as HTMLElement; storesMenu.classList.remove("u-hide"); } diff --git a/static/js/brand-store/brand-store.tsx b/static/js/brand-store/brand-store.tsx index c2d307b9ad..a35ddb7d96 100644 --- a/static/js/brand-store/brand-store.tsx +++ b/static/js/brand-store/brand-store.tsx @@ -18,5 +18,5 @@ root.render( - + , ); diff --git a/static/js/brand-store/components/AccountDetails/AccountDetails.tsx b/static/js/brand-store/components/AccountDetails/AccountDetails.tsx index 734f1de464..6e1b7b7db8 100644 --- a/static/js/brand-store/components/AccountDetails/AccountDetails.tsx +++ b/static/js/brand-store/components/AccountDetails/AccountDetails.tsx @@ -24,7 +24,7 @@ function AccountDetails(): ReactNode { useEffect(() => { setSubscribeToNewsletter( - publisher?.subscriptions ? publisher?.subscriptions?.newsletter : false + publisher?.subscriptions ? publisher?.subscriptions?.newsletter : false, ); }, [publisher]); @@ -171,11 +171,11 @@ function AccountDetails(): ReactNode { onChange={( e: SyntheticEvent & { target: HTMLInputElement; - } + }, ) => { setSubscribeToNewsletter(e.target.checked); setSubscriptionPreferencesChanged( - hasChanged(e.target.checked) + hasChanged(e.target.checked), ); }} /> @@ -213,7 +213,7 @@ function AccountDetails(): ReactNode { data.set("email", publisher?.email || ""); data.set( "newsletter", - subscribeToNewsletter ? "on" : "" + subscribeToNewsletter ? "on" : "", ); const response: Response = await fetch( @@ -221,14 +221,14 @@ function AccountDetails(): ReactNode { { method: "POST", body: data, - } + }, ); if (!response.ok) { setShowErrorMessage(true); setIsSaving(false); throw new Error( - "There has been a problem saving newsletter preferences" + "There has been a problem saving newsletter preferences", ); } diff --git a/static/js/brand-store/components/AccountDetails/__tests__/AccountDetails.test.tsx b/static/js/brand-store/components/AccountDetails/__tests__/AccountDetails.test.tsx index e1590fba67..ce9edfc113 100644 --- a/static/js/brand-store/components/AccountDetails/__tests__/AccountDetails.test.tsx +++ b/static/js/brand-store/components/AccountDetails/__tests__/AccountDetails.test.tsx @@ -2,7 +2,7 @@ import { screen, render, waitFor } from "@testing-library/react"; import "@testing-library/jest-dom"; import { Provider } from "react-redux"; import { BrowserRouter } from "react-router-dom"; -import { MutableSnapshot, RecoilRoot } from "recoil"; +import { MutableSnapshot, RecoilRoot, RecoilValue } from "recoil"; import { useEffect } from "react"; import { QueryClient, QueryClientProvider } from "react-query"; import { useRecoilValue } from "recoil"; @@ -12,7 +12,28 @@ import { publisherState } from "../../../atoms"; const queryClient = new QueryClient(); -const RecoilObserver = ({ node, event }: { node: any; event: Function }) => { +type PublisherState = { + email: string; + fullname: string; + has_stores?: boolean; + identity_url: string; + image: string | null; + is_canonical: boolean; + nickname: string; + subscriptions: { + newsletter: boolean; + } | null; +}; + +type EventFunction = (value: T) => void; + +const RecoilObserver = ({ + node, + event, +}: { + node: RecoilValue; + event: EventFunction; +}) => { const value = useRecoilValue(node); useEffect(() => { @@ -22,7 +43,13 @@ const RecoilObserver = ({ node, event }: { node: any; event: Function }) => { return null; }; -const renderComponent = ({ event, state }: { event: Function; state: any }) => { +const renderComponent = ({ + event, + state, +}: { + event: EventFunction; + state: RecoilValue; +}) => { render( { @@ -48,7 +75,7 @@ const renderComponent = ({ event, state }: { event: Function; state: any }) => { - + , ); }; diff --git a/static/js/brand-store/components/App/App.tsx b/static/js/brand-store/components/App/App.tsx index 22df88ac0f..ffe55f98f0 100644 --- a/static/js/brand-store/components/App/App.tsx +++ b/static/js/brand-store/components/App/App.tsx @@ -27,7 +27,7 @@ import type { StoresList, StoresSlice } from "../../types/shared"; function App(): ReactNode { const isLoading = useSelector( - (state: StoresSlice) => state.brandStores.loading + (state: StoresSlice) => state.brandStores.loading, ); const brandStoresList: StoresList = useSelector(brandStoresListSelector); const dispatch = useDispatch(); @@ -44,6 +44,7 @@ function App(): ReactNode { const setRecoilBrandStores = useSetRecoilState(brandStoresState); useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any dispatch(fetchStores() as any); }, []); diff --git a/static/js/brand-store/components/AppPagination/AppPagination.tsx b/static/js/brand-store/components/AppPagination/AppPagination.tsx index 3b6d4541a4..f8042949bc 100644 --- a/static/js/brand-store/components/AppPagination/AppPagination.tsx +++ b/static/js/brand-store/components/AppPagination/AppPagination.tsx @@ -1,15 +1,25 @@ import { useState, useEffect, ReactNode } from "react"; import { Button, Icon, Input, Select } from "@canonical/react-components"; -import type { Model, Policy, SigningKey } from "../../types/shared"; +import type { + Model as ModelType, + Policy, + SigningKey, +} from "../../types/shared"; -type Props = { +export type ItemType = SigningKey | Policy | ModelType; + +type Props = { keyword: string; - items: Model[] | Policy[] | SigningKey[]; - setItemsToShow: Function; + items: T[]; + setItemsToShow: (items: T[]) => void; }; -function AppPagination({ keyword, items, setItemsToShow }: Props): ReactNode { +function AppPagination({ + keyword, + items, + setItemsToShow, +}: Props): ReactNode { const paginationOptions = [ { label: "25/page", @@ -33,7 +43,7 @@ function AppPagination({ keyword, items, setItemsToShow }: Props): ReactNode { const [currentPage, setCurrentPage] = useState(1); const [visibleItemsCount, setVisibleItemsCount] = useState(0); const [totalPages, setTotalPages] = useState( - Math.ceil(items.length / pageSize) + Math.ceil(items.length / pageSize), ); useEffect(() => { @@ -49,7 +59,7 @@ function AppPagination({ keyword, items, setItemsToShow }: Props): ReactNode { const multiplier = currentPage - 1; const itemsToShow = items.slice( pageSize * multiplier, - pageSize * multiplier + pageSize + pageSize * multiplier + pageSize, ); setItemsToShow(itemsToShow); @@ -86,7 +96,7 @@ function AppPagination({ keyword, items, setItemsToShow }: Props): ReactNode { labelClassName="u-off-screen u-off-screen--top" onChange={(e) => { setCurrentPage( - Math.min(totalPages, Math.max(1, parseInt(e.target.value))) + Math.min(totalPages, Math.max(1, parseInt(e.target.value))), ); }} />{" "} diff --git a/static/js/brand-store/components/Filter/__tests__/Filter.test.tsx b/static/js/brand-store/components/Filter/__tests__/Filter.test.tsx index f57ce861cc..09f91f1588 100644 --- a/static/js/brand-store/components/Filter/__tests__/Filter.test.tsx +++ b/static/js/brand-store/components/Filter/__tests__/Filter.test.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { BrowserRouter } from "react-router-dom"; -import { RecoilRoot, useRecoilValue, atom } from "recoil"; +import { RecoilRoot, RecoilValue, useRecoilValue, atom } from "recoil"; import { QueryClient, QueryClientProvider } from "react-query"; import { render, screen, fireEvent } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; @@ -35,8 +35,8 @@ export const RecoilObserver = ({ node, onChange, }: { - node: any; - onChange: Function; + node: RecoilValue; + onChange: (value: string) => void; }) => { const value = useRecoilValue(node); useEffect(() => onChange(value), [onChange, value]); @@ -60,7 +60,7 @@ const renderComponent = (filterQuery?: string) => { /> - + , ); }; diff --git a/static/js/brand-store/components/Members/InviteModal.tsx b/static/js/brand-store/components/Members/InviteModal.tsx index e10e34ad90..919e599005 100644 --- a/static/js/brand-store/components/Members/InviteModal.tsx +++ b/static/js/brand-store/components/Members/InviteModal.tsx @@ -1,16 +1,12 @@ import { Dispatch, ReactNode, SetStateAction } from "react"; import { Modal, Button } from "@canonical/react-components"; - -type InviteActionData = { - action: "resend" | "revoke" | "open"; - email: string; -}; +import type { InviteActionData } from "../../types/shared"; type Props = { - inviteActionData: InviteActionData; + inviteActionData: InviteActionData | null; inviteModalOpen: boolean; setInviteModalOpen: Dispatch>; - updateInvite: Function; + updateInvite: (data: InviteActionData) => void; inviteModalIsSaving: boolean; }; @@ -21,6 +17,10 @@ function InviteModal({ updateInvite, inviteModalIsSaving, }: Props): ReactNode { + if (!inviteModalOpen || !inviteActionData) { + return null; + } + const ACTIONS = { resend: "Resend", revoke: "Revoke", diff --git a/static/js/brand-store/components/Members/InvitesTable.tsx b/static/js/brand-store/components/Members/InvitesTable.tsx index 0cfd0e3756..a985dba1d5 100644 --- a/static/js/brand-store/components/Members/InvitesTable.tsx +++ b/static/js/brand-store/components/Members/InvitesTable.tsx @@ -6,6 +6,7 @@ import { SetStateAction, } from "react"; import { useDispatch } from "react-redux"; +import { AppDispatch } from "../../store/index"; import { useParams } from "react-router-dom"; import { format } from "date-fns"; import { MainTable, Button } from "@canonical/react-components"; @@ -15,10 +16,15 @@ import InviteModal from "./InviteModal"; import { fetchInvites } from "../../slices/invitesSlice"; import ROLES from "./memberRoles"; -import type { Invite, InvitesList, Status } from "../../types/shared"; +import type { + Invite, + InvitesList, + Status, + InviteActionData, +} from "../../types/shared"; type Props = { - invites: any; + invites: InvitesList; setShowSuccessNotification: Dispatch>; setNotificationText: Dispatch>; setShowErrorNotification: Dispatch>; @@ -30,28 +36,29 @@ function InvitesTable({ setNotificationText, setShowErrorNotification, }: Props): ReactNode { - const [pendingInvites, setPendingInvites] = useState([]); - const [expiredInvites, setExpiredInvites] = useState([]); - const [revokedInvites, setRevokedInvites] = useState([]); + const [pendingInvites, setPendingInvites] = useState([]); + const [expiredInvites, setExpiredInvites] = useState([]); + const [revokedInvites, setRevokedInvites] = useState([]); const [inviteModalOpen, setInviteModalOpen] = useState(false); - const [inviteActionData, setInviteActionData]: any = useState({}); + const [inviteActionData, setInviteActionData] = + useState(null); const [inviteModalIsSaving, setInviteModalIsSaving] = useState(false); const { id } = useParams(); - const dispatch = useDispatch(); + const dispatch = useDispatch(); useEffect(() => { setPendingInvites( - invites.filter((invite: Invite) => invite.status === "Pending") + invites.filter((invite: Invite) => invite.status === "Pending"), ); setExpiredInvites( - invites.filter((invite: Invite) => invite.status === "Expired") + invites.filter((invite: Invite) => invite.status === "Expired"), ); setRevokedInvites( - invites.filter((invite: Invite) => invite.status === "Revoked") + invites.filter((invite: Invite) => invite.status === "Revoked"), ); }, [invites]); - const updateInvite = (inviteData: Invite) => { + const updateInvite = (inviteData: InviteActionData) => { const data = new FormData(); data.set("csrf_token", window.CSRF_TOKEN); @@ -71,11 +78,11 @@ function InvitesTable({ } }) .then(() => { - dispatch(fetchInvites(id as string) as any); + dispatch(fetchInvites(id as string)); setTimeout(() => { setInviteModalOpen(false); setInviteModalIsSaving(false); - setInviteActionData({}); + setInviteActionData(null); setNotificationText("The invite status has been updated"); setShowSuccessNotification(true); }, 1500); @@ -83,7 +90,7 @@ function InvitesTable({ .catch(() => { setInviteModalOpen(false); setInviteModalIsSaving(false); - setInviteActionData({}); + setInviteActionData(null); setShowErrorNotification(true); }); }; @@ -228,7 +235,7 @@ function InvitesTable({ ]} /> state.members.loading); const membersNotFound = useSelector( - (state: Members) => state.members.notFound + (state: Members) => state.members.notFound, ); const invitesNotFound = useSelector( - (state: InvitesSlice) => state.invites.notFound + (state: InvitesSlice) => state.invites.notFound, ); - const dispatch = useDispatch(); + const dispatch = useDispatch(); const { id } = useParams(); const [filteredMembers, setFilteredMembers] = useState([]); const [sidePanelOpen, setSidePanelOpen] = useState(false); @@ -72,9 +73,9 @@ function Members(): ReactNode { const [showInviteForm, setShowInviteForm] = useState(false); const [storeName, setStoreName] = useState(""); const [memberButtonDisabled, setMemberButtonDisabled] = useState(false); - const [changedMembers, setChangedMembers] = useState([]); + const [changedMembers, setChangedMembers] = useState([]); const [notificationText, setNotificationText] = useState( - "Changes have been saved" + "Changes have been saved", ); const [currentMember, setCurrentMember] = useState(); @@ -116,8 +117,8 @@ function Members(): ReactNode { setSidePanelOpen(false); setNewMemberEmail(""); setNewMemberRoles([]); - dispatch(fetchMembers(id as string) as any); - dispatch(fetchInvites(id as string) as any); + dispatch(fetchMembers(id as string)); + dispatch(fetchInvites(id as string)); setShowSuccessNotification(true); setNotificationText("Member has been added to the store"); setShowInviteForm(false); @@ -155,8 +156,8 @@ function Members(): ReactNode { currentMember?.roles.length === 1 && currentMember?.roles.includes("view"); useEffect(() => { - dispatch(fetchMembers(id as string) as any); - dispatch(fetchInvites(id as string) as any); + dispatch(fetchMembers(id as string)); + dispatch(fetchInvites(id as string)); setStoreName((): string | undefined => { const store = brandStoresList.find((item) => item.id === id); @@ -230,8 +231,8 @@ function Members(): ReactNode { members.filter( (member) => member.displayname.includes(query) || - member.email.includes(query) - ) + member.email.includes(query), + ), ); } else { setFilteredMembers(members); diff --git a/static/js/brand-store/components/Members/MembersTable.tsx b/static/js/brand-store/components/Members/MembersTable.tsx index ec1a06012a..1634c4629d 100644 --- a/static/js/brand-store/components/Members/MembersTable.tsx +++ b/static/js/brand-store/components/Members/MembersTable.tsx @@ -7,7 +7,7 @@ import type { Member } from "../../types/shared"; type Props = { filteredMembers: Array; changedMembers: Array; - setChangedMembers: Function; + setChangedMembers: (members: Array) => void; }; function MembersTable({ @@ -51,17 +51,17 @@ function MembersTable({ } const changedMember = changedMembers.find( - (m: Member) => m.id === currentMember.id + (m: Member) => m.id === currentMember.id, ); const originalMember = filteredMembers.find( - (m: Member) => m.id === currentMember.id + (m: Member) => m.id === currentMember.id, ); if (changedMember && originalMember) { if (checkArrayEqual(originalMember.roles, updatedItem.roles)) { setChangedMembers( - changedMembers.filter((m) => m.id !== currentMember.id) + changedMembers.filter((m) => m.id !== currentMember.id), ); } else { setChangedMembers([ diff --git a/static/js/brand-store/components/Members/__tests__/InviteModal.test.tsx b/static/js/brand-store/components/Members/__tests__/InviteModal.test.tsx index d30aa0bc67..060b2758b0 100644 --- a/static/js/brand-store/components/Members/__tests__/InviteModal.test.tsx +++ b/static/js/brand-store/components/Members/__tests__/InviteModal.test.tsx @@ -16,7 +16,7 @@ const renderComponent = (props: { setInviteModalOpen={jest.fn()} updateInvite={updateInvite} inviteModalIsSaving={props.inviteModalIsSaving} - /> + />, ); }; diff --git a/static/js/brand-store/components/Model/CreatePolicyForm.test.tsx b/static/js/brand-store/components/Model/CreatePolicyForm.test.tsx index c944f05a3f..03bcc549d7 100644 --- a/static/js/brand-store/components/Model/CreatePolicyForm.test.tsx +++ b/static/js/brand-store/components/Model/CreatePolicyForm.test.tsx @@ -34,20 +34,20 @@ const renderComponent = () => { /> - + , ); }; describe("CreatePolicyForm", () => { it("shows a message if there are no available signing keys", () => { - // @ts-ignore + // @ts-expect-error - Mocking useQuery without providing types for the mock data useQuery.mockReturnValue({ data: [] }); renderComponent(); expect(screen.getByText(/No signing keys available/)).toBeInTheDocument(); }); it("disables the 'Add policy' button if there is no selected signing key", () => { - // @ts-ignore + // @ts-expect-error - Mocking useQuery for a valid signing key but skipping type validation useQuery.mockReturnValue({ data: [ { @@ -59,11 +59,14 @@ describe("CreatePolicyForm", () => { ], }); renderComponent(); - expect(screen.getByRole("button", { name: "Add policy" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Add policy" })).toHaveAttribute( + "aria-disabled", + "true", + ); }); it("enables the 'Add policy' button if a signing key is selected", async () => { - // @ts-ignore + // @ts-expect-error - Mocking useQuery to test form behavior with a selected signing key useQuery.mockReturnValue({ data: [ { @@ -80,7 +83,7 @@ describe("CreatePolicyForm", () => { "signing-key-1", ]); expect( - screen.getByRole("button", { name: "Add policy" }) + screen.getByRole("button", { name: "Add policy" }), ).not.toBeDisabled(); }); }); diff --git a/static/js/brand-store/components/Model/CreatePolicyForm.tsx b/static/js/brand-store/components/Model/CreatePolicyForm.tsx index 33aa42b6c3..074b0d976e 100644 --- a/static/js/brand-store/components/Model/CreatePolicyForm.tsx +++ b/static/js/brand-store/components/Model/CreatePolicyForm.tsx @@ -23,7 +23,7 @@ import { brandStoreState } from "../../selectors"; type Props = { setShowNotification: Dispatch>; setShowErrorNotification: Dispatch>; - refetchPolicies: Function; + refetchPolicies: () => void; }; function CreatePolicyForm({ @@ -35,7 +35,7 @@ function CreatePolicyForm({ const brandId = useRecoilValue(brandIdState); const navigate = useNavigate(); const location = useLocation(); - const { isLoading, isError, error, data }: any = useSigningKeys(brandId); + const { isLoading, isError, error, data } = useSigningKeys(brandId); const [signingKeys, setSigningKeys] = useRecoilState(signingKeysListState); const [newSigningKey, setNewSigningKey] = useRecoilState(newSigningKeyState); const brandStore = useRecoilValue(brandStoreState(id)); @@ -98,7 +98,7 @@ function CreatePolicyForm({ } useEffect(() => { - if (!isLoading && !error) { + if (!isLoading && !error && data) { setSigningKeys(data); } }, [isLoading, error, data]); @@ -118,8 +118,7 @@ function CreatePolicyForm({
{isLoading &&

Fetching signing keys...

} - {isError && error &&

Error: {error.message}

} - + {isError && error instanceof Error &&

Error: {error.message}

} {isSaving && (

diff --git a/static/js/brand-store/components/Model/Model.test.tsx b/static/js/brand-store/components/Model/Model.test.tsx index 819237aa1d..cb74ffa224 100644 --- a/static/js/brand-store/components/Model/Model.test.tsx +++ b/static/js/brand-store/components/Model/Model.test.tsx @@ -40,7 +40,7 @@ const renderComponent = () => { - + , ); }; @@ -75,7 +75,10 @@ jest.mock("react-query", () => ({ describe("Model", () => { it("disables the 'Save' button if the API key hasn't been modified", async () => { renderComponent(); - expect(screen.getByRole("button", { name: "Save" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Save" })).toHaveAttribute( + "aria-disabled", + "true", + ); }); it("enables the 'Save' button when the API key has been modified", async () => { @@ -92,13 +95,16 @@ describe("Model", () => { const apiKeyField: HTMLInputElement = screen.getByLabelText("API key"); const apiKeyFieldValue = apiKeyField.value; expect(apiKeyFieldValue).not.toEqual( - "K2NjWGA4iKhLmGDDQUJhJyhzS35CBLJClyNu8dAS0TWrTF3aSD" + "K2NjWGA4iKhLmGDDQUJhJyhzS35CBLJClyNu8dAS0TWrTF3aSD", ); }); it("disables the 'Revert' button if the API key hasn't been modified", async () => { renderComponent(); - expect(screen.getByRole("button", { name: "Revert" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Revert" })).toHaveAttribute( + "aria-disabled", + "true", + ); }); it("enables the 'Revert' button when the API key has been modified", async () => { @@ -113,11 +119,11 @@ describe("Model", () => { const user = userEvent.setup(); await user.click(screen.getByRole("button", { name: "Generate key" })); expect(screen.getByLabelText("API key")).not.toHaveValue( - "K2NjWGA4iKhLmGDDQUJhJyhzS35CBLJClyNu8dAS0TWrTF3aSD" + "K2NjWGA4iKhLmGDDQUJhJyhzS35CBLJClyNu8dAS0TWrTF3aSD", ); await user.click(screen.getByRole("button", { name: "Revert" })); expect(screen.getByLabelText("API key")).toHaveValue( - "K2NjWGA4iKhLmGDDQUJhJyhzS35CBLJClyNu8dAS0TWrTF3aSD" + "K2NjWGA4iKhLmGDDQUJhJyhzS35CBLJClyNu8dAS0TWrTF3aSD", ); }); }); diff --git a/static/js/brand-store/components/Model/Model.tsx b/static/js/brand-store/components/Model/Model.tsx index 0c9c91380e..153c94ea9b 100644 --- a/static/js/brand-store/components/Model/Model.tsx +++ b/static/js/brand-store/components/Model/Model.tsx @@ -21,6 +21,7 @@ import Navigation from "../Navigation"; import { useModels } from "../../hooks"; import { setPageTitle } from "../../utils"; +import type { Model as ModelType } from "../../types/shared"; function Model() { const { id, model_id } = useParams(); @@ -29,7 +30,7 @@ function Model() { const [newApiKey, setNewApiKey] = useState(""); const [showSuccessNotification, setShowSuccessNotificaton] = useState(false); const [showErrorNotification, setShowErrorNotificaton] = useState(false); - const setModelsList = useSetRecoilState(modelsListState); + const setModelsList = useSetRecoilState(modelsListState); const brandStore = useRecoilValue(brandStoreState(id)); const mutation = useMutation({ @@ -66,7 +67,11 @@ function Model() { data: models, isLoading: modelsIsLoading, error: modelsError, - }: any = useModels(brandId); + }: { + data: ModelType[] | undefined; + isLoading: boolean; + error: unknown; + } = useModels(brandId); const handleError = () => { setShowErrorNotificaton(true); @@ -80,9 +85,15 @@ function Model() { : setPageTitle("Model"); useEffect(() => { - if (!currentModel && !modelsIsLoading && !modelsError && models) { + if (!currentModel && !modelsIsLoading && models) { setModelsList(models); } + + if (modelsError) { + if (modelsError instanceof Error) { + console.error(modelsError.message); + } + } }, [currentModel, modelsIsLoading, modelsError, models]); return ( @@ -198,7 +209,7 @@ function Model() { setNewApiKey( randomstring.generate({ length: 50, - }) + }), ); }} > @@ -217,7 +228,7 @@ function Model() {

{format( new Date(currentModel["created-at"]), - "dd/MM/yyyy" + "dd/MM/yyyy", )}

@@ -250,7 +261,7 @@ function Model() {

{format( new Date(currentModel["modified-at"]), - "dd/MM/yyyy" + "dd/MM/yyyy", )}

diff --git a/static/js/brand-store/components/Model/ModelBreadcrumb.tsx b/static/js/brand-store/components/Model/ModelBreadcrumb.tsx index 67aa1981dc..ac4e06e8d0 100644 --- a/static/js/brand-store/components/Model/ModelBreadcrumb.tsx +++ b/static/js/brand-store/components/Model/ModelBreadcrumb.tsx @@ -9,7 +9,8 @@ function ModelBreadcrumb(): JSX.Element { return (

- ‹ Models / {model_id ?? ''} + ‹ Models /{" "} + {model_id ?? ""}

); } diff --git a/static/js/brand-store/components/Model/ModelNav.test.tsx b/static/js/brand-store/components/Model/ModelNav.test.tsx index d4c67cbbca..6c15f12201 100644 --- a/static/js/brand-store/components/Model/ModelNav.test.tsx +++ b/static/js/brand-store/components/Model/ModelNav.test.tsx @@ -8,7 +8,7 @@ const renderComponent = () => { return render( - + , ); }; diff --git a/static/js/brand-store/components/Model/Policies.test.tsx b/static/js/brand-store/components/Model/Policies.test.tsx index c03b6e1726..a4297ab8f0 100644 --- a/static/js/brand-store/components/Model/Policies.test.tsx +++ b/static/js/brand-store/components/Model/Policies.test.tsx @@ -10,7 +10,7 @@ import "@testing-library/jest-dom"; import Policies from "./Policies"; import { store } from "../../store"; -let mockFilterQuery = "1.7"; +const mockFilterQuery = "1.7"; jest.mock("react-router-dom", () => { return { @@ -50,7 +50,7 @@ function renderComponent() { - + , ); } @@ -58,7 +58,7 @@ describe("Policies", () => { it("displays a link to create a new policy", () => { renderComponent(); expect( - screen.getByRole("link", { name: "Create policy" }) + screen.getByRole("link", { name: "Create policy" }), ).toBeInTheDocument(); }); @@ -67,10 +67,10 @@ describe("Policies", () => { renderComponent(); await user.click(screen.getByRole("link", { name: "Create policy" })); expect( - screen.getByRole("combobox", { name: "Signing key" }) + screen.getByRole("combobox", { name: "Signing key" }), ).toBeInTheDocument(); expect( - screen.getByRole("button", { name: "Add policy" }) + screen.getByRole("button", { name: "Add policy" }), ).toBeInTheDocument(); }); @@ -82,7 +82,7 @@ describe("Policies", () => { it("populates filter with the filter query parameter", () => { renderComponent(); expect(screen.getByLabelText("Search policies")).toHaveValue( - mockFilterQuery + mockFilterQuery, ); }); }); diff --git a/static/js/brand-store/components/Model/Policies.tsx b/static/js/brand-store/components/Model/Policies.tsx index 86afe35f23..6411b2b1f2 100644 --- a/static/js/brand-store/components/Model/Policies.tsx +++ b/static/js/brand-store/components/Model/Policies.tsx @@ -16,7 +16,12 @@ import PoliciesTable from "./PoliciesTable"; import CreatePolicyForm from "./CreatePolicyForm"; import Navigation from "../Navigation"; -import { usePolicies, useSigningKeys } from "../../hooks"; +import { + ApiError, + UsePoliciesResponse, + usePolicies, + useSigningKeys, +} from "../../hooks"; import { policiesListFilterState, policiesListState, @@ -35,10 +40,8 @@ function Policies(): ReactNode { const brandId = useRecoilValue(brandIdState); const location = useLocation(); const navigate = useNavigate(); - const { isLoading, isError, error, refetch, data }: any = usePolicies( - brandId, - model_id - ); + const { isLoading, isError, error, refetch, data }: UsePoliciesResponse = + usePolicies(brandId, model_id); const signingKeys = useSigningKeys(brandId); const setPoliciesList = useSetRecoilState>(policiesListState); const setFilter = useSetRecoilState(policiesListFilterState); @@ -59,13 +62,19 @@ function Policies(): ReactNode { useEffect(() => { if (!signingKeys.isLoading && !signingKeys.isError) { - setSigningKeysList(signingKeys.data); + if (signingKeys.data) { + setSigningKeysList(signingKeys.data); + } else { + setSigningKeysList([]); + } } }, [signingKeys]); useEffect(() => { if (!isLoading && !isError) { - setPoliciesList(data); + if (data) { + setPoliciesList(data); + } setFilter(searchParams.get("filter") || ""); } else { setPoliciesList([]); @@ -158,7 +167,7 @@ function Policies(): ReactNode { <> {isError && error && ( - Error: {error.message} + Error: {(error as ApiError).message} )} {isLoading ? ( diff --git a/static/js/brand-store/components/Model/PoliciesTable.test.tsx b/static/js/brand-store/components/Model/PoliciesTable.test.tsx index d0a93d0b96..4ae3f2b6b6 100644 --- a/static/js/brand-store/components/Model/PoliciesTable.test.tsx +++ b/static/js/brand-store/components/Model/PoliciesTable.test.tsx @@ -27,7 +27,7 @@ const renderComponent = () => { /> - + , ); }; @@ -41,19 +41,19 @@ describe("PoliciesTable", () => { renderComponent(); expect( - screen.getByRole("columnheader", { name: "Revision" }) + screen.getByRole("columnheader", { name: "Revision" }), ).toBeInTheDocument(); expect( - screen.getByRole("columnheader", { name: "Signing key" }) + screen.getByRole("columnheader", { name: "Signing key" }), ).toBeInTheDocument(); expect( - screen.getByRole("columnheader", { name: "Creation date" }) + screen.getByRole("columnheader", { name: "Creation date" }), ).toBeInTheDocument(); expect( - screen.getByRole("columnheader", { name: "Last updated" }) + screen.getByRole("columnheader", { name: "Last updated" }), ).toBeInTheDocument(); }); diff --git a/static/js/brand-store/components/Model/PoliciesTable.tsx b/static/js/brand-store/components/Model/PoliciesTable.tsx index 8aaad1a9ed..b103642143 100644 --- a/static/js/brand-store/components/Model/PoliciesTable.tsx +++ b/static/js/brand-store/components/Model/PoliciesTable.tsx @@ -6,7 +6,7 @@ import { MainTable, Button, Modal, Icon } from "@canonical/react-components"; import AppPagination from "../AppPagination"; -import { usePolicies } from "../../hooks"; +import { usePolicies, UsePoliciesResponse } from "../../hooks"; import { brandIdState } from "../../atoms"; import { filteredPoliciesListState } from "../../selectors"; @@ -24,13 +24,13 @@ function ModelsTable({ const { model_id } = useParams(); const brandId = useRecoilValue(brandIdState); const [policiesList, setPoliciesList] = useRecoilState( - filteredPoliciesListState + filteredPoliciesListState, ); const [itemsToShow, setItemsToShow] = useState>(policiesList); const [showModal, setShowModal] = useState(false); const [selectedPolicy, setSelectedPolicy] = useState(); const [isLoading, setIsLoading] = useState(false); - const { refetch }: any = usePolicies(brandId, model_id); + const { refetch } = usePolicies(brandId, model_id) as UsePoliciesResponse; const deletePolicy = async (policyRevision: number | undefined) => { if (policyRevision === undefined) { @@ -40,7 +40,7 @@ function ModelsTable({ setIsLoading(true); setPoliciesList( - policiesList.filter((policy) => policy.revision !== policyRevision) + policiesList.filter((policy) => policy.revision !== policyRevision), ); const formData = new FormData(); @@ -51,7 +51,7 @@ function ModelsTable({ { method: "DELETE", body: formData, - } + }, ); const data = await response.json(); diff --git a/static/js/brand-store/components/Models/CreateModelForm.test.tsx b/static/js/brand-store/components/Models/CreateModelForm.test.tsx index 8709ff795c..eda9606ce0 100644 --- a/static/js/brand-store/components/Models/CreateModelForm.test.tsx +++ b/static/js/brand-store/components/Models/CreateModelForm.test.tsx @@ -28,14 +28,17 @@ const renderComponent = () => { /> - + , ); }; describe("CreateModelForm", () => { it("disables 'Add model' button if no new model name", async () => { renderComponent(); - expect(screen.getByRole("button", { name: "Add model" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Add model" })).toHaveAttribute( + "aria-disabled", + "true", + ); }); it("enables 'Add model' button if there is a new model name", async () => { @@ -43,10 +46,10 @@ describe("CreateModelForm", () => { renderComponent(); await user.type( screen.getByRole("textbox", { name: "Name" }), - "test-model-name" + "test-model-name", ); expect( - screen.getByRole("button", { name: "Add model" }) + screen.getByRole("button", { name: "Add model" }), ).not.toBeDisabled(); }); diff --git a/static/js/brand-store/components/Models/Models.test.tsx b/static/js/brand-store/components/Models/Models.test.tsx index 0ba13ed85a..8729749d0f 100644 --- a/static/js/brand-store/components/Models/Models.test.tsx +++ b/static/js/brand-store/components/Models/Models.test.tsx @@ -10,7 +10,7 @@ import "@testing-library/jest-dom"; import Models from "./Models"; import { store } from "../../store"; -let mockFilterQuery = "model-1"; +const mockFilterQuery = "model-1"; jest.mock("react-router-dom", () => { return { @@ -38,7 +38,7 @@ function renderComponent() { - + , ); } @@ -46,7 +46,7 @@ describe("Models", () => { it("displays a link to create a new model", () => { renderComponent(); expect( - screen.getByRole("link", { name: "Create new model" }) + screen.getByRole("link", { name: "Create new model" }), ).toBeInTheDocument(); }); @@ -56,13 +56,13 @@ describe("Models", () => { await user.click(screen.getByRole("link", { name: "Create new model" })); expect(screen.getByRole("textbox", { name: "Name" })).toBeInTheDocument(); expect( - screen.getByRole("textbox", { name: "API key" }) + screen.getByRole("textbox", { name: "API key" }), ).toBeInTheDocument(); expect( - screen.getByRole("button", { name: "Generate key" }) + screen.getByRole("button", { name: "Generate key" }), ).toBeInTheDocument(); expect( - screen.getByRole("button", { name: "Add model" }) + screen.getByRole("button", { name: "Add model" }), ).toBeInTheDocument(); }); diff --git a/static/js/brand-store/components/Models/Models.tsx b/static/js/brand-store/components/Models/Models.tsx index 64882ceb09..8b9851496f 100644 --- a/static/js/brand-store/components/Models/Models.tsx +++ b/static/js/brand-store/components/Models/Models.tsx @@ -8,6 +8,7 @@ import { useSearchParams, } from "react-router-dom"; import { Row, Col, Notification, Icon } from "@canonical/react-components"; +import { UseQueryResult } from "react-query"; import { modelsListFilterState, @@ -26,7 +27,7 @@ import Navigation from "../Navigation"; import { useModels } from "../../hooks"; import { isClosedPanel, setPageTitle, getPolicies } from "../../utils"; -import type { Model, Policy } from "../../types/shared"; +import type { Model as ModelType, Policy } from "../../types/shared"; function Models(): ReactNode { const { id } = useParams(); @@ -37,11 +38,11 @@ function Models(): ReactNode { isLoading: modelsIsLoading, error: modelsError, isError: modelsIsError, - }: any = useModels(brandId); + }: UseQueryResult = useModels(brandId); const location = useLocation(); const navigate = useNavigate(); - const setModelsList = useSetRecoilState>(modelsListState); + const setModelsList = useSetRecoilState>(modelsListState); const setPolicies = useSetRecoilState>(policiesListState); const setNewModel = useSetRecoilState(newModelState); const setFilter = useSetRecoilState(modelsListFilterState); @@ -122,7 +123,7 @@ function Models(): ReactNode {
- {modelsIsError && modelsError && ( + {modelsIsError && modelsError instanceof Error && ( Error: {modelsError.message} diff --git a/static/js/brand-store/components/Models/ModelsTable.test.tsx b/static/js/brand-store/components/Models/ModelsTable.test.tsx index 3df8a90a1a..6f10de032f 100644 --- a/static/js/brand-store/components/Models/ModelsTable.test.tsx +++ b/static/js/brand-store/components/Models/ModelsTable.test.tsx @@ -12,7 +12,7 @@ const renderComponent = () => { - + , ); }; @@ -26,23 +26,23 @@ describe("ModelsTable", () => { renderComponent(); expect( - screen.getByRole("columnheader", { name: /Name/ }) + screen.getByRole("columnheader", { name: /Name/ }), ).toBeInTheDocument(); expect( - screen.getByRole("columnheader", { name: "API key" }) + screen.getByRole("columnheader", { name: "API key" }), ).toBeInTheDocument(); expect( - screen.getByRole("columnheader", { name: "Policy revision" }) + screen.getByRole("columnheader", { name: "Policy revision" }), ).toBeInTheDocument(); expect( - screen.getByRole("columnheader", { name: "Last updated" }) + screen.getByRole("columnheader", { name: "Last updated" }), ).toBeInTheDocument(); expect( - screen.getByRole("columnheader", { name: "Created date" }) + screen.getByRole("columnheader", { name: "Created date" }), ).toBeInTheDocument(); }); diff --git a/static/js/brand-store/components/Models/ModelsTable.tsx b/static/js/brand-store/components/Models/ModelsTable.tsx index f615bc8ebb..149ab21212 100644 --- a/static/js/brand-store/components/Models/ModelsTable.tsx +++ b/static/js/brand-store/components/Models/ModelsTable.tsx @@ -10,12 +10,12 @@ import { maskString } from "../../utils"; import { filteredModelsListState } from "../../selectors"; -import type { Model } from "../../types/shared"; +import type { Model as ModelType } from "../../types/shared"; function ModelsTable(): ReactNode { const { id } = useParams(); - const modelsList = useRecoilValue>(filteredModelsListState); - const [itemsToShow, setItemsToShow] = useState>(modelsList); + const modelsList = useRecoilValue>(filteredModelsListState); + const [itemsToShow, setItemsToShow] = useState>(modelsList); return ( <> @@ -57,7 +57,7 @@ function ModelsTable(): ReactNode { }, }, ]} - rows={itemsToShow.map((model: Model) => { + rows={itemsToShow.map((model: ModelType) => { return { columns: [ { diff --git a/static/js/brand-store/components/Navigation/Navigation.tsx b/static/js/brand-store/components/Navigation/Navigation.tsx index e9517c1696..3195fefc31 100644 --- a/static/js/brand-store/components/Navigation/Navigation.tsx +++ b/static/js/brand-store/components/Navigation/Navigation.tsx @@ -186,13 +186,13 @@ function Navigation({ const storeName = store.name.toLowerCase(); return storeName.includes( - value.toLowerCase() + value.toLowerCase(), ); - }) + }), ); } else { setFilteredBrandstores( - brandStoresList + brandStoresList, ); } }} diff --git a/static/js/brand-store/components/Navigation/__tests__/Navigation.test.tsx b/static/js/brand-store/components/Navigation/__tests__/Navigation.test.tsx index a71d6ef8fb..864a2a7b35 100644 --- a/static/js/brand-store/components/Navigation/__tests__/Navigation.test.tsx +++ b/static/js/brand-store/components/Navigation/__tests__/Navigation.test.tsx @@ -57,7 +57,7 @@ const renderComponent = (sectionName: string) => { - + , ); }; @@ -65,7 +65,7 @@ describe("Navigation", () => { test("displays logo", () => { renderComponent("snaps"); expect(screen.getAllByRole("img", { name: "Snapcraft logo" })).toHaveLength( - 2 + 2, ); }); @@ -79,7 +79,7 @@ describe("Navigation", () => { mockRouterReturnValue.id = "non-admin-store"; renderComponent("snaps"); expect( - screen.queryByRole("link", { name: /Members/ }) + screen.queryByRole("link", { name: /Members/ }), ).not.toBeInTheDocument(); }); @@ -93,7 +93,7 @@ describe("Navigation", () => { mockRouterReturnValue.id = "non-admin-store"; renderComponent("snaps"); expect( - screen.queryByRole("link", { name: /Settings/ }) + screen.queryByRole("link", { name: /Settings/ }), ).not.toBeInTheDocument(); }); }); diff --git a/static/js/brand-store/components/Publisher/__tests__/Publisher.test.tsx b/static/js/brand-store/components/Publisher/__tests__/Publisher.test.tsx index bdb9a42636..65d2f8755d 100644 --- a/static/js/brand-store/components/Publisher/__tests__/Publisher.test.tsx +++ b/static/js/brand-store/components/Publisher/__tests__/Publisher.test.tsx @@ -11,7 +11,7 @@ describe("Publisher", () => { test("shows the correct page", () => { renderComponent(); expect( - screen.getByRole("heading", { level: 1, name: "Publisher" }) + screen.getByRole("heading", { level: 1, name: "Publisher" }), ).toBeInTheDocument(); }); @@ -21,12 +21,12 @@ describe("Publisher", () => { expect( screen.getByRole("link", { name: "register a snap name on the Snap store", - }) + }), ).toHaveAttribute("href", "/snaps"); expect( screen.getByRole("link", { name: "manage your snaps on the dashboard", - }) + }), ).toHaveAttribute("href", "https://snapcraft.io/stores/snaps/"); }); }); diff --git a/static/js/brand-store/components/Reviewer/__tests__/Reviewer.test.tsx b/static/js/brand-store/components/Reviewer/__tests__/Reviewer.test.tsx index 0aa5397fee..efe26810cd 100644 --- a/static/js/brand-store/components/Reviewer/__tests__/Reviewer.test.tsx +++ b/static/js/brand-store/components/Reviewer/__tests__/Reviewer.test.tsx @@ -15,7 +15,7 @@ const renderComponent = () => { render( - + , ); }; @@ -23,7 +23,7 @@ describe("Reviewer", () => { test("shows the correct page", () => { renderComponent(); expect( - screen.getByRole("heading", { level: 1, name: "Reviewer" }) + screen.getByRole("heading", { level: 1, name: "Reviewer" }), ).toBeInTheDocument(); }); @@ -33,7 +33,7 @@ describe("Reviewer", () => { expect( screen.getByRole("link", { name: "review the snaps in this store on the dashboard", - }) + }), ).toHaveAttribute("href", "https://snapcraft.io/stores/storeid/reviews/"); }); }); diff --git a/static/js/brand-store/components/ReviewerAndPublisher/__tests__/ReviewerAndPubliser.test.tsx b/static/js/brand-store/components/ReviewerAndPublisher/__tests__/ReviewerAndPubliser.test.tsx index bae6e8da4b..0d5f61c733 100644 --- a/static/js/brand-store/components/ReviewerAndPublisher/__tests__/ReviewerAndPubliser.test.tsx +++ b/static/js/brand-store/components/ReviewerAndPublisher/__tests__/ReviewerAndPubliser.test.tsx @@ -15,7 +15,7 @@ const renderComponent = () => { render( - + , ); }; @@ -23,7 +23,7 @@ describe("ReviewerAndPublisher", () => { test("shows the correct page", () => { renderComponent(); expect( - screen.getByRole("heading", { level: 1, name: "Reviewer and publisher" }) + screen.getByRole("heading", { level: 1, name: "Reviewer and publisher" }), ).toBeInTheDocument(); }); @@ -33,17 +33,17 @@ describe("ReviewerAndPublisher", () => { expect( screen.getByRole("link", { name: "register a snap name on the Snap store", - }) + }), ).toHaveAttribute("href", "/snaps"); expect( screen.getByRole("link", { name: "manage your snaps on the dashboard", - }) + }), ).toHaveAttribute("href", "https://snapcraft.io/stores/snaps/"); expect( screen.getByRole("link", { name: "review the snaps in this store on the dashboard", - }) + }), ).toHaveAttribute("href", "https://snapcraft.io/stores/storeid/reviews/"); }); }); diff --git a/static/js/brand-store/components/Settings/Settings.test.tsx b/static/js/brand-store/components/Settings/Settings.test.tsx index 8bd3d9e544..ede7d62585 100644 --- a/static/js/brand-store/components/Settings/Settings.test.tsx +++ b/static/js/brand-store/components/Settings/Settings.test.tsx @@ -38,7 +38,7 @@ function renderComponent() { - + , ); } @@ -60,6 +60,9 @@ function getInitialState(): RootState { id: "testid", current_user: true, roles: ["admin"], + displayname: "", + email: "", + username: "", }, ], loading: false, @@ -77,7 +80,7 @@ let initialState: RootState = getInitialState(); const mockSelector = jest.spyOn(reactRedux, "useSelector"); const setupMockSelector = (state: RootState) => { - mockSelector.mockImplementation((callback: any) => { + mockSelector.mockImplementation((callback: (state: RootState) => unknown) => { return callback(state); }); }; @@ -90,7 +93,7 @@ test("the 'is public' checkbox should not be checked when the current store is s setupMockSelector(initialState); renderComponent(); expect( - screen.getByLabelText("Include this store in public lists") + screen.getByLabelText("Include this store in public lists"), ).not.toBeChecked(); }); @@ -99,7 +102,7 @@ test("the 'is public' checkbox should be checked when the current store is not s setupMockSelector(initialState); renderComponent(); expect( - screen.getByLabelText("Include this store in public lists") + screen.getByLabelText("Include this store in public lists"), ).toBeChecked(); }); @@ -117,14 +120,17 @@ test("the correct radio button is checked by default for manual review policy", checked: true, }) as HTMLInputElement; expect(checkedRadioButton.value).toEqual( - initialState.currentStore.currentStore["manual-review-policy"] + initialState.currentStore.currentStore["manual-review-policy"], ); }); test("the save button is disabled by default", () => { setupMockSelector(initialState); renderComponent(); - expect(screen.getByText("Save changes")).toBeDisabled(); + expect(screen.getByText("Save changes")).toHaveAttribute( + "aria-disabled", + "true", + ); }); test("the save button is enabled when the data changes", () => { diff --git a/static/js/brand-store/components/Settings/Settings.tsx b/static/js/brand-store/components/Settings/Settings.tsx index b783a8863c..d1bcb34fb4 100644 --- a/static/js/brand-store/components/Settings/Settings.tsx +++ b/static/js/brand-store/components/Settings/Settings.tsx @@ -26,7 +26,7 @@ import Navigation from "../Navigation"; import { setPageTitle } from "../../utils"; -import type { RouteParams, Member } from "../../types/shared"; +import type { RouteParams, Member, Store } from "../../types/shared"; export type RootState = { currentStore: { @@ -40,12 +40,12 @@ export type RootState = { notFound: boolean; }; members: { - members: Array<{}>; + members: Array; loading: boolean; notFound: boolean; }; brandStores: { - brandStoresList: Array<{}>; + brandStoresList: Array; loading: boolean; notFound: boolean; }; @@ -55,16 +55,16 @@ function Settings(): ReactNode { const currentStore = useSelector(currentStoreSelector); const members = useSelector(membersSelector); const storeLoading = useSelector( - (state: RootState) => state.currentStore.loading + (state: RootState) => state.currentStore.loading, ); const membersLoading = useSelector( - (state: RootState) => state.members.loading + (state: RootState) => state.members.loading, ); const storeNotFound = useSelector( - (state: RootState) => state.currentStore.notFound + (state: RootState) => state.currentStore.notFound, ); const membersNotFound = useSelector( - (state: RootState) => state.members.notFound + (state: RootState) => state.members.notFound, ); const dispatch = useAppDispatch(); const { id } = useParams(); diff --git a/static/js/brand-store/components/SigningKeys/CreateSigningKeyForm.tsx b/static/js/brand-store/components/SigningKeys/CreateSigningKeyForm.tsx index 40aa833a21..8d084b398f 100644 --- a/static/js/brand-store/components/SigningKeys/CreateSigningKeyForm.tsx +++ b/static/js/brand-store/components/SigningKeys/CreateSigningKeyForm.tsx @@ -18,7 +18,7 @@ import type { SigningKey } from "../../types/shared"; type Props = { setShowNotification: Dispatch>; setErrorMessage: Dispatch>; - refetch: Function; + refetch: () => void; }; function CreateSigningKeyForm({ @@ -41,7 +41,7 @@ function CreateSigningKeyForm({ const handleError = () => { setSigningKeysList((oldSigningKeysList: Array) => { return oldSigningKeysList.filter( - (signingKey) => signingKey.name !== newSigningKey.name + (signingKey) => signingKey.name !== newSigningKey.name, ); }); navigate(`/admin/${id}/signing-keys`); diff --git a/static/js/brand-store/components/SigningKeys/DeactivateSigningKeyModal.tsx b/static/js/brand-store/components/SigningKeys/DeactivateSigningKeyModal.tsx index 1e04994da2..e289758237 100644 --- a/static/js/brand-store/components/SigningKeys/DeactivateSigningKeyModal.tsx +++ b/static/js/brand-store/components/SigningKeys/DeactivateSigningKeyModal.tsx @@ -6,7 +6,7 @@ import { Dispatch, ReactNode, SetStateAction } from "react"; type Props = { setModalOpen: Dispatch>; - handleDisable: Function; + handleDisable: (signingKey: SigningKey) => void; isDeleting: boolean; signingKey: SigningKey; }; diff --git a/static/js/brand-store/components/SigningKeys/SigningKeys.tsx b/static/js/brand-store/components/SigningKeys/SigningKeys.tsx index aabc7512c5..322ea2c4ce 100644 --- a/static/js/brand-store/components/SigningKeys/SigningKeys.tsx +++ b/static/js/brand-store/components/SigningKeys/SigningKeys.tsx @@ -8,6 +8,7 @@ import { useLocation, } from "react-router-dom"; import { Row, Col, Notification, Icon } from "@canonical/react-components"; +import { UseQueryResult } from "react-query"; import { useSigningKeys, useModels } from "../../hooks"; import { @@ -31,15 +32,20 @@ import { getPolicies, } from "../../utils"; -import type { SigningKey, Policy } from "../../types/shared"; +import type { SigningKey, Policy, Model } from "../../types/shared"; function SigningKeys(): ReactNode { const { id } = useParams(); const brandId = useRecoilValue(brandIdState); const location = useLocation(); const navigate = useNavigate(); - const { isLoading, isError, error, data, refetch }: any = - useSigningKeys(brandId); + const { + isLoading, + isError, + error, + data, + refetch, + }: UseQueryResult = useSigningKeys(brandId); const setSigningKeysList = useSetRecoilState>(signingKeysListState); const setPolicies = useSetRecoilState>(policiesListState); @@ -61,10 +67,10 @@ function SigningKeys(): ReactNode { data: models, isLoading: modelsIsLoading, isError: modelsIsError, - }: any = useModels(brandId); + }: UseQueryResult = useModels(brandId); useEffect(() => { - if (!isLoading && !error) { + if (!isLoading && !error && data) { setSigningKeysList([...data.sort(sortByDateDescending)]); setFilter(searchParams.get("filter") || ""); } diff --git a/static/js/brand-store/components/SigningKeys/SigningKeysTable.tsx b/static/js/brand-store/components/SigningKeys/SigningKeysTable.tsx index 9f93feee13..bc7f67aa0d 100644 --- a/static/js/brand-store/components/SigningKeys/SigningKeysTable.tsx +++ b/static/js/brand-store/components/SigningKeys/SigningKeysTable.tsx @@ -13,6 +13,7 @@ import { filteredSigningKeysListState } from "../../selectors"; import { signingKeysListState, brandIdState } from "../../atoms"; import type { SigningKey } from "../../types/shared"; +import type { ItemType } from "../AppPagination/AppPagination"; type Props = { setShowDisableSuccessNotification: Dispatch>; @@ -23,10 +24,10 @@ function SigningKeysTable({ setShowDisableSuccessNotification, enableTableActions, }: Props): ReactNode { - const { id } = useParams(); + useParams(); const brandId = useRecoilValue(brandIdState); const signingKeysList = useRecoilValue>( - filteredSigningKeysListState + filteredSigningKeysListState, ); const [itemsToShow, setItemsToShow] = useState>(signingKeysList); @@ -55,7 +56,7 @@ function SigningKeysTable({ { method: "DELETE", body: formData, - } + }, ); if (!response.ok) { @@ -217,7 +218,13 @@ function SigningKeysTable({ { + if (items.length > 0 && "name" in items[0]) { + setItemsToShow(items as SigningKey[]); + } else { + console.error("Invalid item types."); + } + }} /> ); diff --git a/static/js/brand-store/components/Snaps/IncludedSnapsTable.tsx b/static/js/brand-store/components/Snaps/IncludedSnapsTable.tsx index 4ea88eeece..5071aab7ff 100644 --- a/static/js/brand-store/components/Snaps/IncludedSnapsTable.tsx +++ b/static/js/brand-store/components/Snaps/IncludedSnapsTable.tsx @@ -5,13 +5,17 @@ import { Input, MainTable } from "@canonical/react-components"; import type { SnapsList, Store, Snap } from "../../types/shared"; +type GetStoreNameFunc = (storeId: string) => string | undefined; +type IsOnlyViewerFunc = () => boolean; +type SetSnapsToRemoveFunc = (snaps: SnapsList) => void; + type IncludedSnapsTableRowsProps = { snaps: Array; - getStoreName?: Function; + getStoreName?: GetStoreNameFunc; isGlobal?: boolean; - isOnlyViewer: Function; + isOnlyViewer: IsOnlyViewerFunc; snapsToRemove: SnapsList; - setSnapsToRemove: Function; + setSnapsToRemove: SetSnapsToRemoveFunc; }; function SnapTableRows({ @@ -60,7 +64,7 @@ function SnapTableRows({ setSnapsToRemove([...snapsToRemove, snap]); } else { setSnapsToRemove( - snapsToRemove.filter((item) => item.id !== snap.id) + snapsToRemove.filter((item) => item.id !== snap.id), ); } }} @@ -108,12 +112,12 @@ function SnapTableRows({ } type IncludedSnapsTableProps = { - isOnlyViewer: Function; + isOnlyViewer: IsOnlyViewerFunc; snapsToRemove: SnapsList; - setSnapsToRemove: Function; - globalStore: Store; - nonEssentialSnapIds: SnapsList; - getStoreName: Function; + setSnapsToRemove: SetSnapsToRemoveFunc; + globalStore: Store | null; + nonEssentialSnapIds: string[]; + getStoreName: GetStoreNameFunc; otherStores: Array<{ id: string; name: string; @@ -134,7 +138,7 @@ function IncludedSnapsTable({ const [isIndeterminate, setIsIndeterminate] = useState(false); const otherStoresSnaps = otherStores.map((item) => item?.snaps); - const allSnaps = otherStoresSnaps.flat().concat(globalStore?.snaps); + const allSnaps = otherStoresSnaps.flat().concat(globalStore?.snaps ?? []); const deDupedSnaps = (snaps: Array, sourceStoreId: string) => { return snaps.filter((snap) => snap.store === sourceStoreId); @@ -169,7 +173,7 @@ function IncludedSnapsTable({ onChange={(e) => { if (e.target.checked) { setSnapsToRemove( - allSnaps.filter((item) => !item.essential) + allSnaps.filter((item) => !item.essential), ); setIsChecked(true); } else { @@ -180,7 +184,7 @@ function IncludedSnapsTable({ disabled={!nonEssentialSnapIds.length} label="Name" checked={isChecked} - // @ts-ignore + // @ts-expect-error - The Input component does not support the 'indeterminate' prop. indeterminate={isIndeterminate} />
@@ -217,17 +221,17 @@ function IncludedSnapsTable({ isOnlyViewer, snapsToRemove, setSnapsToRemove, - }) + }), ) .flat() .concat( ...SnapTableRows({ - snaps: deDupedSnaps(globalStore.snaps, "ubuntu"), + snaps: globalStore ? deDupedSnaps(globalStore.snaps, "ubuntu") : [], isGlobal: true, isOnlyViewer, snapsToRemove, setSnapsToRemove, - }) + }), )} /> ); diff --git a/static/js/brand-store/components/Snaps/Snaps.tsx b/static/js/brand-store/components/Snaps/Snaps.tsx index 34d4f30b51..6f382cb490 100644 --- a/static/js/brand-store/components/Snaps/Snaps.tsx +++ b/static/js/brand-store/components/Snaps/Snaps.tsx @@ -1,4 +1,5 @@ -import { useState, useEffect, ReactNode } from "react"; +import { useState, useEffect } from "react"; +import type { ReactNode } from "react"; import { useParams, Link } from "react-router-dom"; import { useSelector } from "react-redux"; import { AsyncThunkAction } from "@reduxjs/toolkit"; @@ -36,6 +37,8 @@ import { setPageTitle } from "../../utils"; import type { StoresSlice, Snap, + SnapsList, + Store, Member, SnapsSlice, MembersSlice, @@ -60,33 +63,35 @@ function Snaps(): ReactNode { ); const dispatch = useAppDispatch(); const { id } = useParams(); - const [snapsInStore, setSnapsInStore]: any = useState([]); - const [otherStoreIds, setOtherStoreIds]: any = useState([]); - const [otherStores, setOtherStores] = useState([]); - const [selectedSnaps, setSelectedSnaps] = useState([]); + const [snapsInStore, setSnapsInStore] = useState([]); + const [otherStoreIds, setOtherStoreIds] = useState([]); + const [otherStores, setOtherStores] = useState([]); + const [selectedSnaps, setSelectedSnaps] = useState([]); const [sidePanelOpen, setSidePanelOpen] = useState(false); const [isSaving, setIsSaving] = useState(false); - const [snapsToRemove, setSnapsToRemove]: any = useState([]); + const [snapsToRemove, setSnapsToRemove] = useState([]); const [showAddSuccessNotification, setShowAddSuccessNotification] = useState(false); const [showErrorNotification, setShowErrorNotification] = useState(false); const [showRemoveSuccessNotification, setShowRemoveSuccessNotification] = useState(false); const [removeSnapSaving, setRemoveSnapSaving] = useState(false); - const [nonEssentialSnapIds, setNonEssentialSnapIds]: any = useState([]); + const [nonEssentialSnapIds, setNonEssentialSnapIds] = useState([]); const [isReloading, setIsReloading] = useState(false); - const [currentMember, setCurrentMember]: any = useState(null); - const [currentStore, setCurrentStore]: any = useState(null); + const [currentMember, setCurrentMember] = useState(null); + const [currentStore, setCurrentStore] = useState(null); const [isPublisherOnly, setIsPublisherOnly] = useState(false); const [isReviewerOnly, setIsReviewerOnly] = useState(false); const [isReviewerAndPublisherOnly, setIsReviewerAndPublisherOnly] = useState(false); const [showRemoveSnapsConfirmation, setShowRemoveSnapsConfirmation] = useState(false); - const [globalStore, setGlobalStore]: any = useState(null); + const [globalStore, setGlobalStore] = useState(null); + const [fetchSnapsByStoreIdPromise, setFetchSnapsByStoreIdPromise] = useState< ReturnType> | undefined >(); + const [fetchMembersByStoreIdPromise, setFetchMembersByStoreIdPromise] = useState> | undefined>(); @@ -127,7 +132,7 @@ function Snaps(): ReactNode { } }) .then((data) => { - dispatch(fetchSnaps(id as string) as any); + dispatch(fetchSnaps(id as string)); // Add timeout so that the user has time to notice the save action // in the event of it happening very fast @@ -188,7 +193,7 @@ function Snaps(): ReactNode { } }) .then(() => { - dispatch(fetchSnaps(id as string) as any); + dispatch(fetchSnaps(id as string)); // Add timeout so that the user has time to notice the save action // in the event of it happening very fast @@ -235,8 +240,15 @@ function Snaps(): ReactNode { }; const includedStores = snaps - .filter((snap) => snap["included-stores"]) - .map((snap) => snap["included-stores"][0]); + .filter( + (snap) => snap["included-stores"] && snap["included-stores"].length > 0 + ) + .map((snap) => ({ + id: snap.id, + name: snap.name, + userHasAccess: snap.userHasAccess, + includedStore: snap["included-stores"][0], + })); useEffect(() => { setSnapsInStore([]); @@ -266,7 +278,7 @@ function Snaps(): ReactNode { setSnapsInStore(snaps.filter((snap) => snap.store === id)); setOtherStoreIds(getOtherStoreIds()); - const nonEssentialSnaps = snaps.filter((item) => { + const nonEssentialSnaps = snaps.filter((item: Snap) => { return item.store !== id && !item.essential; }); @@ -313,27 +325,37 @@ function Snaps(): ReactNode { }, [otherStoreIds]); useEffect(() => { - setCurrentMember(members.find((member) => member.current_user)); + const currentMember = members.find((member) => member.current_user) ?? null; + setCurrentMember(currentMember); }, [snaps, members, snapsLoading, membersLoading]); useEffect(() => { - setCurrentStore(brandStoresList.find((store) => store.id === id)); + const store = brandStoresList.find((store) => store.id === id); + setCurrentStore(store || null); }, [brandStoresList, id]); useEffect(() => { - setIsPublisherOnly( - currentStore?.roles.length === 1 && currentStore?.roles.includes("access") - ); - - setIsReviewerOnly( - currentStore?.roles.length === 1 && currentStore?.roles.includes("review") - ); - - setIsReviewerAndPublisherOnly( - currentStore?.roles.length === 2 && - currentStore?.roles.includes("access") && - currentStore?.roles.includes("review") - ); + if (currentStore) { + const roles = currentStore.roles; + + if (roles) { + setIsPublisherOnly(roles.length === 1 && roles.includes("access")); + setIsReviewerOnly(roles.length === 1 && roles.includes("review")); + setIsReviewerAndPublisherOnly( + roles.length === 2 && + roles.includes("access") && + roles.includes("review") + ); + } else { + setIsPublisherOnly(false); + setIsReviewerOnly(false); + setIsReviewerAndPublisherOnly(false); + } + } else { + setIsPublisherOnly(false); + setIsReviewerOnly(false); + setIsReviewerAndPublisherOnly(false); + } }, [currentStore, id]); const getSectionName = () => { @@ -486,7 +508,7 @@ function Snaps(): ReactNode { included in {getStoreName(id || "")}.

    - {includedStores.map((store: any) => ( + {includedStores.map((store) => (
  • {store.userHasAccess ? ( void; +type SetOtherStoresFunc = ( + stores: Array<{ id: string; name: string; snaps: SnapsList }>, +) => void; +type GetStoreNameFunc = (storeId: string) => string; + type Props = { - setSnapsInStore: Function; + setSnapsInStore: SetSnapsInStoreFunc; snapsInStore: SnapsList; - setOtherStores: Function; + setOtherStores: SetOtherStoresFunc; otherStoreIds: Array; - getStoreName: Function; + getStoreName: GetStoreNameFunc; snaps: SnapsList; id: string; }; @@ -33,13 +39,13 @@ function SnapsFilter({ onKeyUp={( e: KeyboardEvent & { target: HTMLInputElement; - } + }, ) => { if (e.target.value) { setSnapsInStore( snapsInStore.filter((snap) => - snap?.name?.includes(e.target.value) - ) + snap?.name?.includes(e.target.value), + ), ); setOtherStores( otherStoreIds.map((storeId) => { @@ -49,10 +55,10 @@ function SnapsFilter({ snaps: snaps.filter( (snap) => snap.store === storeId && - snap.name.includes(e.target.value) + snap.name.includes(e.target.value), ), }; - }) + }), ); } else { setSnapsInStore(snaps.filter((snap) => snap.store === id)); @@ -63,7 +69,7 @@ function SnapsFilter({ name: getStoreName(storeId), snaps: snaps.filter((snap) => snap.store === storeId), }; - }) + }), ); } }} diff --git a/static/js/brand-store/components/Snaps/SnapsSearch.tsx b/static/js/brand-store/components/Snaps/SnapsSearch.tsx index 73835c332a..3e19d7fd8b 100644 --- a/static/js/brand-store/components/Snaps/SnapsSearch.tsx +++ b/static/js/brand-store/components/Snaps/SnapsSearch.tsx @@ -6,10 +6,12 @@ import debounce from "../../../libs/debounce"; import type { SnapsList, Snap } from "../../types/shared"; +type SetSelectedSnapsFunc = (snaps: SnapsList) => void; + type Props = { storeId: string; selectedSnaps: SnapsList; - setSelectedSnaps: Function; + setSelectedSnaps: SetSelectedSnapsFunc; nonEssentialSnapIds: Array; }; @@ -57,7 +59,7 @@ function SnapsSearch({ ( e: KeyboardEvent & { target: HTMLInputElement; - } + }, ) => { if (e.target.value.length < 2) { return; @@ -66,7 +68,7 @@ function SnapsSearch({ setIsSearching(true); fetch( - `/admin/${storeId}/snaps/search?q=${e.target.value}&allowed_for_inclusion=${storeId}` + `/admin/${storeId}/snaps/search?q=${e.target.value}&allowed_for_inclusion=${storeId}`, ) .then((response) => { if (response.status !== 200) { @@ -77,7 +79,7 @@ function SnapsSearch({ }) .then((data) => { const selectionIds = selectedSnaps.map( - (item) => item.id + (item) => item.id, ); setSuggestions( @@ -86,7 +88,7 @@ function SnapsSearch({ !selectionIds.includes(item.id) && !nonEssentialSnapIds.includes(item.id) ); - }) + }), ); setIsSearching(false); @@ -97,7 +99,7 @@ function SnapsSearch({ }); }, 200, - false + false, ), })} /> @@ -124,8 +126,8 @@ function SnapsSearch({ {suggestions.map((item: Snap, index: number) => (
  • { setSelectedSnaps([ ...selectedSnaps.filter( - (suggestion) => suggestion.id !== item.id + (suggestion) => suggestion.id !== item.id, ), ]); }} diff --git a/static/js/brand-store/hooks/index.ts b/static/js/brand-store/hooks/index.ts index 912b7c3910..3cee0aaa61 100644 --- a/static/js/brand-store/hooks/index.ts +++ b/static/js/brand-store/hooks/index.ts @@ -1,19 +1,32 @@ -import { useQuery } from "react-query"; +import { useQuery, UseQueryResult } from "react-query"; import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; import type { AppDispatch, RootState } from "../store"; +import type { Model as ModelType, SigningKey, Policy } from "../types/shared"; export const useAppDispatch: () => AppDispatch = useDispatch; export const useAppSelector: TypedUseSelectorHook = useSelector; +export interface ApiError { + message: string; +} + +export interface UsePoliciesResponse { + isLoading: boolean; + isError: boolean; + error: unknown; + refetch: () => void; + data: Policy[] | undefined; +} + export function usePolicies( brandId: string | undefined, - modelId: string | undefined -) { - return useQuery({ + modelId: string | undefined, +): UsePoliciesResponse { + return useQuery({ queryKey: ["policies", brandId], queryFn: async () => { const response = await fetch( - `/admin/store/${brandId}/models/${modelId}/policies` + `/admin/store/${brandId}/models/${modelId}/policies`, ); if (!response.ok) { @@ -31,8 +44,10 @@ export function usePolicies( }); } -export function useSigningKeys(brandId: string | undefined) { - return useQuery({ +export const useSigningKeys = ( + brandId: string | undefined, +): UseQueryResult => { + return useQuery({ queryKey: ["signingKeys", brandId], queryFn: async () => { const response = await fetch(`/admin/store/${brandId}/signing-keys`); @@ -50,7 +65,7 @@ export function useSigningKeys(brandId: string | undefined) { return signingKeysData.data; }, }); -} +}; export function useBrand(id: string | undefined) { return useQuery({ @@ -73,8 +88,10 @@ export function useBrand(id: string | undefined) { }); } -export function useModels(brandId: string | undefined) { - return useQuery({ +export const useModels = ( + brandId: string | undefined, +): UseQueryResult => { + return useQuery({ queryKey: ["models", brandId], queryFn: async () => { const response = await fetch(`/admin/store/${brandId}/models`); @@ -93,7 +110,7 @@ export function useModels(brandId: string | undefined) { }, enabled: !!brandId, }); -} +}; export function usePublisher() { return useQuery("publisher", async () => { diff --git a/static/js/brand-store/selectors/index.ts b/static/js/brand-store/selectors/index.ts index cceed57a5e..6d8e229aa8 100644 --- a/static/js/brand-store/selectors/index.ts +++ b/static/js/brand-store/selectors/index.ts @@ -45,7 +45,7 @@ const filteredModelsListState = selector>({ const policies = get(policiesListState); const modelsWithPolicies = models.map((model) => { const policy = policies.find( - (policy) => policy["model-name"] === model.name + (policy) => policy["model-name"] === model.name, ); return { @@ -60,10 +60,12 @@ const filteredModelsListState = selector>({ const currentModelState = selectorFamily({ key: "currentModel", - get: (modelId) => ({ get }) => { - const models = get(modelsListState); - return models.find((model) => model.name === modelId); - }, + get: + (modelId) => + ({ get }) => { + const models = get(modelsListState); + return models.find((model) => model.name === modelId); + }, }); const filteredPoliciesListState = selector>({ @@ -74,7 +76,7 @@ const filteredPoliciesListState = selector>({ const signingKeys = get(signingKeysListState); const policiesWithKeys = policies.map((policy) => { const signingKey = signingKeys.find( - (key) => key["sha3-384"] === policy["signing-key-sha3-384"] + (key) => key["sha3-384"] === policy["signing-key-sha3-384"], ); return { @@ -92,10 +94,12 @@ const filteredPoliciesListState = selector>({ const brandStoreState = selectorFamily({ key: "brandStore", - get: (storeId) => ({ get }) => { - const brandStores = get(brandStoresState); - return brandStores.find((store) => store.id === storeId); - }, + get: + (storeId) => + ({ get }) => { + const brandStores = get(brandStoresState); + return brandStores.find((store) => store.id === storeId); + }, }); const filteredSigningKeysListState = selector>({ diff --git a/static/js/brand-store/slices/membersSlice.ts b/static/js/brand-store/slices/membersSlice.ts index 01cc60ac38..ffc9fc77dd 100644 --- a/static/js/brand-store/slices/membersSlice.ts +++ b/static/js/brand-store/slices/membersSlice.ts @@ -9,7 +9,7 @@ export const fetchMembers = createAsyncThunk( const data = await response.json(); return data; - } + }, ); export const slice = createSlice({ diff --git a/static/js/brand-store/slices/snapsSlice.ts b/static/js/brand-store/slices/snapsSlice.ts index 7300f8d0f5..1ba74bc221 100644 --- a/static/js/brand-store/slices/snapsSlice.ts +++ b/static/js/brand-store/slices/snapsSlice.ts @@ -9,7 +9,7 @@ export const fetchSnaps = createAsyncThunk( const data = await response.json(); return data; - } + }, ); export const slice = createSlice({ diff --git a/static/js/brand-store/types/shared.ts b/static/js/brand-store/types/shared.ts index 6cb4e160cd..abb27236b9 100644 --- a/static/js/brand-store/types/shared.ts +++ b/static/js/brand-store/types/shared.ts @@ -62,6 +62,11 @@ export type Invite = { "expiration-date": string; }; +export type InviteActionData = { + action: "resend" | "revoke" | "open"; + email: string; +}; + export type Member = { displayname: string; email: string; @@ -86,14 +91,15 @@ export type Snap = { private: boolean; store: string; users: Array; + userHasAccess?: boolean; }; export type Store = { id: string; name: string; - roles: Array<"admin" | "review" | "view" | "access">; + roles?: Array<"admin" | "review" | "view" | "access">; snaps: SnapsList; - userHasAccess: boolean; + userHasAccess?: boolean; }; export type Model = { diff --git a/static/js/brand-store/utils/checkModelNameExists.ts b/static/js/brand-store/utils/checkModelNameExists.ts index a8adcfd9eb..e21194a910 100644 --- a/static/js/brand-store/utils/checkModelNameExists.ts +++ b/static/js/brand-store/utils/checkModelNameExists.ts @@ -1,6 +1,6 @@ -import type { Model } from "../types/shared"; +import type { Model as ModelType } from "../types/shared"; -function checkModelNameExists(name: string, models: Array): boolean { +function checkModelNameExists(name: string, models: Array): boolean { return models.filter((model) => model.name === name).length > 0; } diff --git a/static/js/brand-store/utils/checkSigningKeyExists.ts b/static/js/brand-store/utils/checkSigningKeyExists.ts index 3c894a83cd..edf314d4c6 100644 --- a/static/js/brand-store/utils/checkSigningKeyExists.ts +++ b/static/js/brand-store/utils/checkSigningKeyExists.ts @@ -1,6 +1,9 @@ import type { SigningKey } from "../types/shared"; -function checkSigningKeyExists(name: string, signingKeys: Array): boolean { +function checkSigningKeyExists( + name: string, + signingKeys: Array, +): boolean { return ( signingKeys.filter((signingKey) => signingKey.name === name).length > 0 ); diff --git a/static/js/brand-store/utils/getFilteredPolicies.test.ts b/static/js/brand-store/utils/getFilteredPolicies.test.ts index db0677cbdb..3366de4f77 100644 --- a/static/js/brand-store/utils/getFilteredPolicies.test.ts +++ b/static/js/brand-store/utils/getFilteredPolicies.test.ts @@ -48,16 +48,16 @@ const mockPolicies = [ describe("getFilteredPolicies", () => { it("returns unfiltered policies if no filter query", () => { expect(getFilteredPolicies(mockPolicies).length).toEqual( - mockPolicies.length + mockPolicies.length, ); expect(getFilteredPolicies(mockPolicies)[0]["revision"]).toEqual( - mockPolicies[0]["revision"] + mockPolicies[0]["revision"], ); expect(getFilteredPolicies(mockPolicies)[1]["revision"]).toEqual( - mockPolicies[1]["revision"] + mockPolicies[1]["revision"], ); expect(getFilteredPolicies(mockPolicies)[2]["revision"]).toEqual( - mockPolicies[2]["revision"] + mockPolicies[2]["revision"], ); }); diff --git a/static/js/brand-store/utils/getFilteredPolicies.ts b/static/js/brand-store/utils/getFilteredPolicies.ts index 816cbdd5cd..8eed69eb42 100644 --- a/static/js/brand-store/utils/getFilteredPolicies.ts +++ b/static/js/brand-store/utils/getFilteredPolicies.ts @@ -2,7 +2,7 @@ import type { Policy } from "../types/shared"; function getFilteredPolicies( policies: Array, - filterQuery?: string | null + filterQuery?: string | null, ) { if (!filterQuery) { return policies; diff --git a/static/js/brand-store/utils/getFilteredSigningKeys.test.ts b/static/js/brand-store/utils/getFilteredSigningKeys.test.ts index 0e47797803..00ee80bdc9 100644 --- a/static/js/brand-store/utils/getFilteredSigningKeys.test.ts +++ b/static/js/brand-store/utils/getFilteredSigningKeys.test.ts @@ -75,16 +75,16 @@ const mockSigningKeys = [ describe("getFilteredSigningKeys", () => { it("returns unfiltered signing keys if no filter query", () => { expect(getFilteredSigningKeys(mockSigningKeys).length).toEqual( - mockSigningKeys.length + mockSigningKeys.length, ); expect(getFilteredSigningKeys(mockSigningKeys)[0].name).toEqual( - mockSigningKeys[0].name + mockSigningKeys[0].name, ); expect(getFilteredSigningKeys(mockSigningKeys)[1].name).toEqual( - mockSigningKeys[1].name + mockSigningKeys[1].name, ); expect(getFilteredSigningKeys(mockSigningKeys)[2].name).toEqual( - mockSigningKeys[2].name + mockSigningKeys[2].name, ); }); @@ -94,10 +94,10 @@ describe("getFilteredSigningKeys", () => { it("returns filtered signing keys if query matches", () => { expect( - getFilteredSigningKeys(mockSigningKeys, "signing-key-1").length + getFilteredSigningKeys(mockSigningKeys, "signing-key-1").length, ).toBe(1); expect( - getFilteredSigningKeys(mockSigningKeys, "signing-key-2")[0].name + getFilteredSigningKeys(mockSigningKeys, "signing-key-2")[0].name, ).toEqual("signing-key-2"); }); }); diff --git a/static/js/brand-store/utils/getFilteredSigningKeys.ts b/static/js/brand-store/utils/getFilteredSigningKeys.ts index 90cf493a7b..49bca5bea1 100644 --- a/static/js/brand-store/utils/getFilteredSigningKeys.ts +++ b/static/js/brand-store/utils/getFilteredSigningKeys.ts @@ -2,7 +2,7 @@ import type { SigningKey } from "../types/shared"; function getFilteredSigningKeys( signingKeys: Array, - filterQuery?: string | null + filterQuery?: string | null, ) { if (!filterQuery) { return signingKeys; diff --git a/static/js/brand-store/utils/getPolicies.ts b/static/js/brand-store/utils/getPolicies.ts index 35e3b30356..ff6682d50e 100644 --- a/static/js/brand-store/utils/getPolicies.ts +++ b/static/js/brand-store/utils/getPolicies.ts @@ -1,9 +1,9 @@ import { Dispatch, SetStateAction } from "react"; import { SetterOrUpdater } from "recoil"; -import type { Model, Policy } from "../types/shared"; +import type { Model as ModelType, Policy } from "../types/shared"; type Options = { - models: Model[]; + models: ModelType[]; id: string | undefined; setPolicies: SetterOrUpdater; signal?: AbortSignal; @@ -22,7 +22,7 @@ const getPolicies = async ({ return fetch(`/admin/store/${id}/models/${model.name}/policies`, { signal, }); - }) + }), ); const allPolicies = await Promise.all( @@ -38,7 +38,7 @@ const getPolicies = async ({ } return policies.data; - }) + }), ); setPolicies(allPolicies.flat()); diff --git a/static/js/brand-store/utils/sortByDateDescending.ts b/static/js/brand-store/utils/sortByDateDescending.ts index c23b63a4be..b03b7743cf 100644 --- a/static/js/brand-store/utils/sortByDateDescending.ts +++ b/static/js/brand-store/utils/sortByDateDescending.ts @@ -1,6 +1,6 @@ function sortByDateAscending( a: { "created-at": string }, - b: { "created-at": string } + b: { "created-at": string }, ) { if (a["created-at"] > b["created-at"]) { return -1; diff --git a/static/js/global.d.ts b/static/js/global.d.ts index 064031f2a1..0a494042c2 100644 --- a/static/js/global.d.ts +++ b/static/js/global.d.ts @@ -8,9 +8,12 @@ type DataLayerEvent = { declare interface Window { dataLayer: Array; + // eslint-disable-next-line @typescript-eslint/no-explicit-any chrome: any; __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose; + // eslint-disable-next-line @typescript-eslint/no-explicit-any MktoForms2: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any Vimeo: any; DNS_VERIFICATION_TOKEN: string; } diff --git a/static/js/libs/__tests__/arrays.test.ts b/static/js/libs/__tests__/arrays.test.ts index d5fefd4bab..4bb029d004 100644 --- a/static/js/libs/__tests__/arrays.test.ts +++ b/static/js/libs/__tests__/arrays.test.ts @@ -47,14 +47,14 @@ describe("arraysEqual", () => { }); it("should fail if an array is modified", () => { - let arr1 = ["test1", 1]; - let arr1Copy = [...arr1]; - let arr2 = ["test2", 2]; - let arr2Copy = [...arr2]; + const arr1 = ["test1", 1]; + const arr1Copy = [...arr1]; + const arr2 = ["test2", 2]; + const arr2Copy = [...arr2]; arraysEqual(arr1, arr2); expect( JSON.stringify(arr1) === JSON.stringify(arr1Copy) && - JSON.stringify(arr2) === JSON.stringify(arr2Copy) + JSON.stringify(arr2) === JSON.stringify(arr2Copy), ).toEqual(true); }); }); diff --git a/static/js/libs/__tests__/debounce.test.ts b/static/js/libs/__tests__/debounce.test.ts index e500d1f270..7b5909574e 100644 --- a/static/js/libs/__tests__/debounce.test.ts +++ b/static/js/libs/__tests__/debounce.test.ts @@ -1,11 +1,11 @@ import debounce from "../debounce"; jest.useFakeTimers(); -const fn: Function = jest.fn(); +const fn: jest.Mock = jest.fn(); describe("debounce", () => { test("function is only called once", () => { - const debouncedFn: Function = debounce(fn, 1000); + const debouncedFn: () => void = debounce(fn, 1000); for (let i = 0; i < 1000; i++) { debouncedFn(); @@ -17,7 +17,7 @@ describe("debounce", () => { }); test("function is called with immediate flag", () => { - const debouncedFn: Function = debounce(fn, 0, false); + const debouncedFn: () => void = debounce(fn, 0, false); for (let i = 0; i < 1000; i++) { debouncedFn(); diff --git a/static/js/libs/__tests__/iframeSize.test.ts b/static/js/libs/__tests__/iframeSize.test.ts index 5ec9785f80..e2ee085619 100644 --- a/static/js/libs/__tests__/iframeSize.test.ts +++ b/static/js/libs/__tests__/iframeSize.test.ts @@ -14,16 +14,19 @@ describe("iframeSize", () => { document.body.appendChild(frameWrapper); - frameWrapper.getBoundingClientRect = jest.fn(() => ({ - x: 0, - y: 0, - top: 0, - left: 0, - bottom: 0, - right: 0, - width: 500, - height: 500, - })) as any; + frameWrapper.getBoundingClientRect = jest.fn( + (): DOMRect => ({ + x: 0, + y: 0, + top: 0, + left: 0, + bottom: 0, + right: 0, + width: 500, + height: 500, + toJSON: () => ({}), + }), + ) as jest.Mock; iframeSize(".frame-wrapper"); diff --git a/static/js/libs/__tests__/shallowDiff.test.ts b/static/js/libs/__tests__/shallowDiff.test.ts index 102ba383b6..aedf0b0e17 100644 --- a/static/js/libs/__tests__/shallowDiff.test.ts +++ b/static/js/libs/__tests__/shallowDiff.test.ts @@ -6,10 +6,10 @@ describe("shallowDiff", () => { }); it("should return true if different", () => { - let initialState = { test: true }; - let addedState = { ...initialState, added: true }; - let removedState = {}; - let changeState = { test: false }; + const initialState = { test: true }; + const addedState = { ...initialState, added: true }; + const removedState = {}; + const changeState = { test: false }; expect(shallowDiff(initialState, addedState)).toEqual(true); expect(shallowDiff(initialState, removedState)).toEqual(true); diff --git a/static/js/libs/__tests__/throttle.test.ts b/static/js/libs/__tests__/throttle.test.ts index 2c761e6f64..b1d948a676 100644 --- a/static/js/libs/__tests__/throttle.test.ts +++ b/static/js/libs/__tests__/throttle.test.ts @@ -3,14 +3,14 @@ import throttle from "../throttle"; describe("throttle", () => { test("calls function", () => { const fn = jest.fn(); - const throttledFunction: Function = throttle(fn, -1); + const throttledFunction: () => void = throttle(fn, -1); throttledFunction(); expect(fn).toHaveBeenCalled(); }); test("doesn't call function", () => { const fn = jest.fn(); - const throttledFunction: Function = throttle(fn, 1); + const throttledFunction: () => void = throttle(fn, 1); throttledFunction(); expect(fn).not.toHaveBeenCalled(); }); diff --git a/static/js/libs/arrays.ts b/static/js/libs/arrays.ts index 32e90c61fe..1e2a438dad 100644 --- a/static/js/libs/arrays.ts +++ b/static/js/libs/arrays.ts @@ -25,8 +25,8 @@ function arraysEqual(oldArray: unknown[], newArray: unknown[]): boolean { return true; } -function arrayChunk(arr: unknown[], chunkSize: number): any[] { - const chunks = []; +function arrayChunk(arr: T[], chunkSize: number): T[][] { + const chunks: T[][] = []; const arrCopy = arr.slice(0); for (let i = 0, ii = arrCopy.length; i < ii; i += chunkSize) { diff --git a/static/js/libs/debounce.ts b/static/js/libs/debounce.ts index 450660648f..799a015d54 100644 --- a/static/js/libs/debounce.ts +++ b/static/js/libs/debounce.ts @@ -5,14 +5,16 @@ interface CallableFunction { export default function debounce( func: CallableFunction, wait: number, - immediate?: boolean + immediate?: boolean, ): { (this: HTMLElement): void; clear(): void } { let timeout: ReturnType | null; const debounced = function (this: HTMLElement) { + // eslint-disable-next-line @typescript-eslint/no-this-alias const context = this; + // eslint-disable-next-line prefer-rest-params const args = arguments; - let later = function () { + const later = function () { timeout = null; if (!immediate) func.apply(context, args); }; diff --git a/static/js/libs/events.ts b/static/js/libs/events.ts index 0b62871194..e71abc7dfa 100644 --- a/static/js/libs/events.ts +++ b/static/js/libs/events.ts @@ -1,54 +1,86 @@ +interface EventHandler { + (event: Event, target: HTMLElement): void; +} + +interface SelectChangeHandler { + (event: Event, target: HTMLSelectElement): void; +} + +type ValidEventTypes = "change" | "click" | "keyup" | "resize"; + +type EventHandlerMap = { + change: SelectChangeHandler; + click: EventHandler; + keyup: EventHandler; + resize: EventHandler; +}; + +interface EventRegistration { + selector: string | HTMLElement; + func: EventHandlerMap[T]; +} + class Events { - events: any; - availableHandles: any; - defaultBindTarget: any; + events: Partial<{ + [T in ValidEventTypes]: EventRegistration[]; + }> = {}; + availableHandles: ValidEventTypes[]; + defaultBindTarget: ParentNode; + constructor(defaultBindTarget: ParentNode | null | undefined) { this.defaultBindTarget = defaultBindTarget || document.body; this.events = {}; this.availableHandles = []; - return this; } - _addListener(type: string, selector: string | HTMLElement | Window) { + _addListener( + type: ValidEventTypes, + selector: string | HTMLElement | HTMLSelectElement | Window, + ) { const bindTarget = typeof selector === "string" ? this.defaultBindTarget : selector; - bindTarget.addEventListener(type, this._handleEvent.bind(this, type)); + bindTarget.addEventListener(type, (e) => this._handleEvent(type, e)); } - _handleEvent(type: string, event: Event) { - const eventTarget = event.target as HTMLElement; - - this.events[type].forEach( - (ev: { - selector: string; - func: (ar1: unknown, arg2: unknown) => void; - }) => { - const target = - typeof ev.selector === "string" - ? eventTarget.closest(ev.selector) - : ev.selector; - - if (target) { - ev.func(event, target); + _handleEvent(type: ValidEventTypes, event: Event) { + const eventTarget = event.target as HTMLElement | HTMLSelectElement; + const eventRegistrations = this.events[type]; + + if (!eventRegistrations) return; + + eventRegistrations.forEach((ev) => { + const target = + typeof ev.selector === "string" + ? eventTarget.closest(ev.selector) + : ev.selector; + + if (target) { + if (type === "change" && target instanceof HTMLSelectElement) { + (ev.func as SelectChangeHandler)(event, target); + } else if (target instanceof HTMLElement) { + (ev.func as EventHandler)(event, target); } } - ); + }); } - addEvent( - type: string, - selector: string | HTMLElement | Window, - func: unknown - ) { + addEvent( + type: T, + selector: string | HTMLElement | HTMLSelectElement, + func: EventHandlerMap[T], + ): void { if (!this.events[type]) { this.events[type] = []; } - this.events[type].push({ - selector: selector, - func: func, - }); + const eventArray = this.events[type]; + if (eventArray) { + eventArray.push({ + selector: selector as string | HTMLElement, + func, + }); + } if (!this.availableHandles.includes(type)) { this._addListener(type, selector); @@ -56,11 +88,28 @@ class Events { } } - addEvents(eventTypes: { [key: string]: { [key: string]: unknown } }) { - Object.keys(eventTypes).forEach((type) => { - Object.keys(eventTypes[type]).forEach((selector) => { - this.addEvent(type, selector, eventTypes[type][selector]); - }); + addWindowEvent(type: ValidEventTypes, func: EventHandler) { + window.addEventListener(type, (event) => { + func(event, window as unknown as HTMLElement); + }); + } + + addEvents( + eventTypes: Partial<{ + [T in ValidEventTypes]: { + [selector: string]: EventHandlerMap[T]; + }; + }>, + ) { + (Object.keys(eventTypes) as ValidEventTypes[]).forEach((type) => { + const handlers = eventTypes[type]; + + if (handlers) { + Object.keys(handlers).forEach((selector) => { + const handler = handlers[selector]; + this.addEvent(type, selector, handler); + }); + } }); } } diff --git a/static/js/libs/fileValidation.ts b/static/js/libs/fileValidation.ts index eac735aedd..5d8752089b 100644 --- a/static/js/libs/fileValidation.ts +++ b/static/js/libs/fileValidation.ts @@ -25,7 +25,7 @@ type Restrictions = { function baseRestrictions( file: File, - restrictions: Restrictions + restrictions: Restrictions, ): Promise { const MB = 1000000; const KB = 1000; @@ -77,7 +77,7 @@ function imageWhitelistHandler( fileName: string; accept: string; dimensions: number[]; - }> + }>, ): boolean { const errors = whitelist.filter((whitelistItem) => { if (whitelistItem.fileName) { @@ -108,7 +108,7 @@ function imageWhitelistHandler( function imageRestrictions( file: File, - restrictions: Restrictions + restrictions: Restrictions, ): Promise { return new Promise((resolve, reject) => { if (!restrictions.accept || restrictions.accept[0].indexOf("image") < 0) { @@ -192,7 +192,7 @@ function imageRestrictions( // If the min and max are the same we only accept 1 aspect ratio if (min === max) { message.push( - `it needs to be ${aspectRatioMin[0]}:${aspectRatioMin[1]}` + `it needs to be ${aspectRatioMin[0]}:${aspectRatioMin[1]}`, ); const suggestedSize = [height / max]; @@ -207,14 +207,14 @@ function imageRestrictions( message.push( `(e.g., ${Math.round(suggestedSize[0])} x ${Math.round( - suggestedSize[1] - )} pixels)` + suggestedSize[1], + )} pixels)`, ); // Otherwise it's a range } else { message.push( - `it needs to be between ${aspectRatioMin[0]}:${aspectRatioMin[1]} and ${aspectRatioMax[0]}:${aspectRatioMax[1]}` + `it needs to be between ${aspectRatioMin[0]}:${aspectRatioMin[1]} and ${aspectRatioMax[0]}:${aspectRatioMax[1]}`, ); } @@ -231,14 +231,14 @@ function imageRestrictions( function validateRestrictions( file: File, - restrictions: Restrictions + restrictions: Restrictions, ): Promise { return new Promise((resolve) => { baseRestrictions(file, restrictions) .then((file) => imageRestrictions(file, restrictions)) .then(resolve) .catch((errors) => { - // @ts-expect-error + // @ts-expect-error - Ignoring TypeScript error because we are attaching an 'errors' property to the 'file' object for validation purposes file.errors = errors; resolve(file); }); diff --git a/static/js/libs/mobile.ts b/static/js/libs/mobile.ts index b545900ded..3a8c80696c 100644 --- a/static/js/libs/mobile.ts +++ b/static/js/libs/mobile.ts @@ -1,7 +1,7 @@ function isMobile(): boolean { // If the mobile menu button is visible, we're on mobile const mobileMenuButton = document.querySelector( - ".p-navigation__toggle--open" + ".p-navigation__toggle--open", ) as HTMLElement; // Use offsetWidth and offsetHeight to figure out if an element is visibile diff --git a/static/js/libs/mouse.ts b/static/js/libs/mouse.ts index c1f912c359..334f012e5f 100644 --- a/static/js/libs/mouse.ts +++ b/static/js/libs/mouse.ts @@ -6,10 +6,10 @@ class Mouse { window.addEventListener("mousemove", this.updatePosition.bind(this)); } - updatePosition(e: { x: any; y: any }) { + updatePosition(e: MouseEvent) { this.position = { - x: e.x, - y: e.y, + x: e.clientX, + y: e.clientY, }; } } diff --git a/static/js/libs/shallowDiff.ts b/static/js/libs/shallowDiff.ts index ef49340cc9..db43242172 100644 --- a/static/js/libs/shallowDiff.ts +++ b/static/js/libs/shallowDiff.ts @@ -1,6 +1,6 @@ const shallowDiff = ( firstState: { [key: string]: unknown }, - secondState: { [key: string]: unknown } + secondState: { [key: string]: unknown }, ) => { let diff = Object.keys(firstState).some((key) => { const value = firstState[key]; diff --git a/static/js/libs/throttle.ts b/static/js/libs/throttle.ts index 7f0791917b..828cacf8ca 100644 --- a/static/js/libs/throttle.ts +++ b/static/js/libs/throttle.ts @@ -1,6 +1,6 @@ export default function throttle( func: () => unknown, - wait: number + wait: number, ): () => void { let time = Date.now(); return function () { diff --git a/static/js/public/about/listing.ts b/static/js/public/about/listing.ts index 881ac568ed..6e68021f1b 100644 --- a/static/js/public/about/listing.ts +++ b/static/js/public/about/listing.ts @@ -2,17 +2,17 @@ export {}; const listingTabLinks = document.querySelectorAll( - "[data-js='listing-tabs-link']" + "[data-js='listing-tabs-link']", ); const listingTabs = document.querySelectorAll("[data-js='listing-tab']"); const previousTabButton = document.querySelector( - "[data-js='previous-tab-button']" + "[data-js='previous-tab-button']", ) as HTMLButtonElement; const nextTabButton = document.querySelector( - "[data-js='next-tab-button']" + "[data-js='next-tab-button']", ) as HTMLButtonElement; const listingTabsSelect = document.querySelector( - "[data-js='listing-tabs-select']" + "[data-js='listing-tabs-select']", ) as HTMLSelectElement; let currentTabIndex = 0; @@ -38,7 +38,7 @@ listingTabsSelect.addEventListener("change", (e) => { const nextTabId = target.value; const listingTab = document.querySelector(nextTabId) as HTMLElement; const options = Array.prototype.slice.call( - listingTabsSelect.options + listingTabsSelect.options, ) as Array; const optionIndex = options.findIndex((option) => { return option.value === nextTabId; @@ -55,7 +55,7 @@ listingTabsSelect.addEventListener("change", (e) => { const selectCurrentTab = (tab: HTMLElement) => { const nextTabId = tab.id; const currentListingTabLink = document.querySelector( - `[data-js='listing-tabs-link'][href='#${nextTabId}']` + `[data-js='listing-tabs-link'][href='#${nextTabId}']`, ) as HTMLLinkElement; currentListingTabLink.setAttribute("aria-current", "page"); listingTabsSelect.value = `#${nextTabId}`; @@ -64,13 +64,13 @@ const selectCurrentTab = (tab: HTMLElement) => { const deselectPreviousTab = () => { const previousListingTabLink = document.querySelector( - "[data-js='listing-tabs-link'][aria-current='page']" + "[data-js='listing-tabs-link'][aria-current='page']", ) as HTMLLinkElement; const previousListingTabId = previousListingTabLink.getAttribute( - "href" + "href", ) as string; const previousListingTab = document.querySelector( - previousListingTabId + previousListingTabId, ) as HTMLElement; previousListingTab.classList.add("u-hide"); previousListingTabLink.removeAttribute("aria-current"); diff --git a/static/js/public/accordion.ts b/static/js/public/accordion.ts index 9081d48828..a837828389 100644 --- a/static/js/public/accordion.ts +++ b/static/js/public/accordion.ts @@ -9,26 +9,26 @@ export const toggleAccordion = (element: HTMLElement, show: boolean): void => { }; export default function initAccordion( - accordionContainerSelector: string + accordionContainerSelector: string, ): void { // Set up an event listener on the container so that panels can be added // and removed and events do not need to be managed separately. const accordionContainer = document.querySelector( - accordionContainerSelector + accordionContainerSelector, ) as HTMLElement; accordionContainer.addEventListener("click", (e) => { const targetEl = e.target as HTMLElement; const target = targetEl.closest( - "[class*='p-accordion__tab']" + "[class*='p-accordion__tab']", ) as HTMLButtonElement; if (target && !target.disabled) { // Find any open panels within the container and close them. const currentTarget = e.currentTarget as HTMLElement; const expandedElements = currentTarget.querySelectorAll( - "[aria-expanded=true]" + "[aria-expanded=true]", ) as NodeListOf; Array.from(expandedElements).forEach((element: HTMLElement) => - toggleAccordion(element, false) + toggleAccordion(element, false), ); // Open the target. toggleAccordion(target, true); @@ -37,7 +37,7 @@ export default function initAccordion( // Add event listeners to buttons that expand the next section of the accordion const nextButtons = [].slice.call( - document.querySelectorAll("[data-js='js-accordion-next-button']") + document.querySelectorAll("[data-js='js-accordion-next-button']"), ); if (nextButtons) { nextButtons.forEach((button: HTMLButtonElement) => { @@ -45,14 +45,14 @@ export default function initAccordion( e.preventDefault(); const currentPanel = button.closest( - ".p-accordion__group" + ".p-accordion__group", ) as HTMLElement; const currentToggle = currentPanel.querySelector( - "[class*='p-accordion__tab']" + "[class*='p-accordion__tab']", ) as HTMLElement; const nextPanel = currentPanel.nextElementSibling as HTMLElement; const nextToggle = nextPanel.querySelector( - "[class*='p-accordion__tab']" + "[class*='p-accordion__tab']", ) as HTMLElement; if (currentPanel && nextPanel) { @@ -72,15 +72,15 @@ export function initAccordionButtons(continueButton: HTMLButtonElement): void { event.preventDefault(); const currentPanel = continueButton.closest( - ".p-accordion__group" + ".p-accordion__group", ) as HTMLElement; const currentToggle = currentPanel.querySelector( - "[class*='p-accordion__tab']" + "[class*='p-accordion__tab']", ) as HTMLElement; const currentSuccess = currentPanel.querySelector(".p-icon--success"); const nextPanel = currentPanel.nextElementSibling as HTMLElement; const nextToggle = nextPanel.querySelector( - "[class*='p-accordion__tab']" + "[class*='p-accordion__tab']", ) as HTMLElement; toggleAccordion(currentToggle, false); diff --git a/static/js/public/expandable-area.ts b/static/js/public/expandable-area.ts index 8991f82c22..5d0243c2b0 100644 --- a/static/js/public/expandable-area.ts +++ b/static/js/public/expandable-area.ts @@ -1,9 +1,9 @@ export default function initExpandableArea( overflowSelector: string, - heightMatchSelector: string + heightMatchSelector: string, ): void { const showMoreContainer = [].slice.call( - document.querySelectorAll("[data-js='js-show-more']") + document.querySelectorAll("[data-js='js-show-more']"), ) as Array; if (showMoreContainer && showMoreContainer.length > 0) { @@ -14,7 +14,7 @@ export default function initExpandableArea( if (overflowSelector && heightMatchSelector) { const overflowEl = el.querySelector(overflowSelector) as HTMLElement; const heightMatchEl = el.querySelector( - heightMatchSelector + heightMatchSelector, ) as HTMLElement; if (overflowEl.scrollHeight <= heightMatchEl.scrollHeight) { diff --git a/static/js/public/featured-snaps.ts b/static/js/public/featured-snaps.ts index d60c78f11b..1feac76afc 100644 --- a/static/js/public/featured-snaps.ts +++ b/static/js/public/featured-snaps.ts @@ -20,12 +20,12 @@ type PackageData = { async function buildCards(category: string): Promise { const featuredSnapCards = document.querySelectorAll( - "[data-js='featured-snap-card']" + "[data-js='featured-snap-card']", ) as NodeListOf; if (window.sessionStorage.getItem(category)) { const localData = JSON.parse( - window.sessionStorage.getItem(category) as string + window.sessionStorage.getItem(category) as string, ); featuredSnapCards.forEach((featuredSnapCard, index) => { @@ -45,40 +45,40 @@ async function buildCards(category: string): Promise { function buildCard(featuredSnapCard: Element, data: PackageData) { const placeholder = featuredSnapCard.querySelector( - "[data-js='featured-snap-card-placeholder']" + "[data-js='featured-snap-card-placeholder']", ) as HTMLElement; const content = featuredSnapCard.querySelector( - "[data-js='featured-snap-card-content']" + "[data-js='featured-snap-card-content']", ) as HTMLElement; content.innerHTML = ""; if ("content" in document.createElement("template")) { const template = document.querySelector( - "#featured-snap-card" + "#featured-snap-card", ) as HTMLTemplateElement; const clone = template.content.cloneNode(true) as HTMLElement; const snapIcon = clone.querySelector( - "[data-js='snap-icon']" + "[data-js='snap-icon']", ) as HTMLImageElement; const snapIconLink = clone.querySelector( - "[data-js='snap-icon-link']" + "[data-js='snap-icon-link']", ) as HTMLLinkElement; const snapTitleLink = clone.querySelector( - "[data-js='snap-title-link']" + "[data-js='snap-title-link']", ) as HTMLLinkElement; const snapPublisher = clone.querySelector( - "[data-js='snap-publisher']" + "[data-js='snap-publisher']", ) as HTMLElement; const snapDescription = clone.querySelector( - "[data-js='snap-description']" + "[data-js='snap-description']", ) as HTMLElement; snapIcon.src = data.icon_url; @@ -123,11 +123,11 @@ function buildCard(featuredSnapCard: Element, data: PackageData) { async function init(featuredCategories: Array): Promise { const featuredCategorySwitches = document.querySelectorAll( - "[data-js='featured-category-switch']" + "[data-js='featured-category-switch']", ); const viewCategoryLink = document.querySelector( - "[data-js='view-category-link']" + "[data-js='view-category-link']", ) as HTMLLinkElement; featuredCategorySwitches.forEach((featuredCategorySwitch) => { @@ -137,10 +137,10 @@ async function init(featuredCategories: Array): Promise { const target = e.target as HTMLLinkElement; const category = target.dataset.category?.toLowerCase(); const previousTargetLink = document.querySelector( - "[data-js='featured-category-switch'][aria-current='page']" + "[data-js='featured-category-switch'][aria-current='page']", ); const previousTargetTab = document.querySelector( - "[data-js='featured-category-switch'][aria-selected='true']" + "[data-js='featured-category-switch'][aria-selected='true']", ); if (previousTargetLink) { diff --git a/static/js/public/first-snap-flow.ts b/static/js/public/first-snap-flow.ts index 19fcb56532..f6cbf5da75 100644 --- a/static/js/public/first-snap-flow.ts +++ b/static/js/public/first-snap-flow.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ /* globals ClipboardJS, ga */ import "whatwg-fetch"; @@ -6,10 +7,10 @@ import { toggleAccordion } from "./accordion"; function install(language: unknown, fsfFlow: string): void { const osPickers = Array.from( - document.querySelectorAll(".js-os-select") + document.querySelectorAll(".js-os-select"), ) as Array; const osWrappers = Array.from( - document.querySelectorAll(".js-os-wrapper") + document.querySelectorAll(".js-os-wrapper"), ) as Array; function select(selectedOs: string) { @@ -25,7 +26,7 @@ function install(language: unknown, fsfFlow: string): void { if (!document.querySelector(".js-linux-manual")) { const paginationBtn = document.querySelector( - `#js-pagination-next` + `#js-pagination-next`, ) as HTMLLinkElement; if (paginationBtn) { paginationBtn.classList.remove("is-disabled"); @@ -74,7 +75,7 @@ function install(language: unknown, fsfFlow: string): void { const os = type.split("-")[0]; const selected = document.querySelector(".js-" + type) as HTMLElement; const unselected = document.querySelector( - "[class*='js-" + os + "-']:not(.js-" + type + ")" + "[class*='js-" + os + "-']:not(.js-" + type + ")", ) as HTMLElement; if (!selected && !unselected) { @@ -96,7 +97,7 @@ function install(language: unknown, fsfFlow: string): void { unselected.classList.add("u-hide"); const paginationBtn = document.querySelector( - `#js-pagination-next` + `#js-pagination-next`, ) as HTMLLinkElement; if (paginationBtn) { const paginationBtnLink: string = `/${fsfFlow}/${language}/${type}/package`; @@ -116,8 +117,7 @@ function install(language: unknown, fsfFlow: string): void { function getSnapCount(cb: { (data: { count: number; snaps: string[] }): void; - (arg0: any): void; -}) { +}): void { fetch("/snaps/api/snap-count") .then((r) => r.json()) .then((data) => { @@ -126,11 +126,11 @@ function getSnapCount(cb: { .catch(); } -function getArrayDiff(arr1: [], arr2: []) { +function getArrayDiff(arr1: string[], arr2: string[]): string[] { let newArr: string[]; let oldArr: string[]; if (arr1.length === arr2.length) { - return false; + return []; } if (arr1.length > arr2.length) { @@ -141,7 +141,7 @@ function getArrayDiff(arr1: [], arr2: []) { oldArr = arr1; } - let newValues: Array = []; + const newValues: Array = []; newArr.forEach((item: string) => { if (!oldArr.includes(item)) { @@ -154,7 +154,7 @@ function getArrayDiff(arr1: [], arr2: []) { function push(): void { let initialCount: number | null = null; - let initialSnaps: [] = []; + let initialSnaps: string[] = []; let timer: string | number | NodeJS.Timeout | undefined; let ready = false; @@ -198,7 +198,7 @@ function push(): void { getCount((snapName) => { // Enable "Continue" button const continueBtn = document.querySelector( - ".js-continue" + ".js-continue", ) as HTMLLinkElement; if (continueBtn) { continueBtn.href = `/${snapName}/releases`; @@ -209,7 +209,7 @@ function push(): void { } // Update "Go to listing" button for a published snap const paginationBtn = document.querySelector( - "#js-pagination-next" + "#js-pagination-next", ) as HTMLLinkElement; if (paginationBtn) { paginationBtn.href = `/${snapName}/listing?from=first-snap`; @@ -221,33 +221,26 @@ function push(): void { function updateNotification( notificationEl: { className: string; - querySelector: (arg0: string) => { - (): HTMLElement; - new (): any; - innerHTML: string; - }; + querySelector: (arg0: string) => HTMLElement; }, className: string, - message: string | undefined + message: string | undefined, ) { notificationEl.className = className; - if (message) { - notificationEl.querySelector(".p-notification__message").innerHTML = - message; + const messageEl = notificationEl.querySelector(".p-notification__message"); + if (messageEl) { + messageEl.innerHTML = message; + } } } function successNotification( notificationEl: { className: string; - querySelector: (arg0: string) => { - (): HTMLElement; - new (): any; - innerHTML: string; - }; + querySelector: (arg0: string) => HTMLElement; }, - message: string | undefined + message: string | undefined, ) { updateNotification(notificationEl, "p-notification--positive", message); } @@ -255,13 +248,9 @@ function successNotification( function errorNotification( notificationEl: { className: string; - querySelector: (arg0: string) => { - (): HTMLElement; - new (): any; - innerHTML: string; - }; + querySelector: (arg0: string) => HTMLElement; }, - message: string + message: string, ) { updateNotification(notificationEl, "p-notification--negative", message); } @@ -272,26 +261,39 @@ function validateSnapName(name: string) { function initChooseName( formEl: { - querySelector: (arg0: string) => { - (): HTMLElement; - new (): any; - disabled: boolean; - }; + querySelector: (arg0: string) => HTMLElement; addEventListener: (arg0: string, arg1: (event: Event) => void) => void; }, - language: string + language: string, ): void { - const snapNameInput = formEl.querySelector("[name=snap-name]") as any; + const snapNameInput = formEl.querySelector( + "[name=snap-name]", + ) as HTMLInputElement; snapNameInput.addEventListener("keyup", () => { const isValid = validateSnapName(snapNameInput.value); - if (!isValid) { - snapNameInput.parentNode.classList.add("is-error"); - formEl.querySelector("button").disabled = true; - } else { - snapNameInput.parentNode.classList.remove("is-error"); - formEl.querySelector("button").disabled = false; + const parent = snapNameInput.parentNode as HTMLElement | null; + if (parent) { + if (!isValid) { + parent.classList.add("is-error"); + + const submitButton = formEl.querySelector( + "button", + ) as HTMLButtonElement; + if (submitButton) { + submitButton.disabled = true; + } + } else { + parent.classList.remove("is-error"); + + const submitButton = formEl.querySelector( + "button", + ) as HTMLButtonElement; + if (submitButton) { + submitButton.disabled = false; + } + } } }); @@ -307,10 +309,10 @@ function initChooseName( function initRegisterName( formEl: HTMLFormElement, notificationEl: HTMLElement, - successEl: { classList: { remove: (arg0: string) => void } } + successEl: { classList: { remove: (arg0: string) => void } }, ): void { const snapNameInput = formEl.querySelector( - "[name=snap-name]" + "[name=snap-name]", ) as HTMLInputElement; snapNameInput.addEventListener("keyup", () => { @@ -345,20 +347,20 @@ function initRegisterName( formEl.addEventListener("submit", (event) => { event.preventDefault(); - let formData = new FormData(formEl); + const formData = new FormData(formEl); const submitButton = formEl.querySelector( - "[data-js='js-snap-name-register']" + "[data-js='js-snap-name-register']", ) as HTMLButtonElement; const currentPanel = formEl.closest( - ".p-accordion__group" + ".p-accordion__group", ) as HTMLFormElement; const currentToggle = currentPanel.querySelector( - ".p-accordion__tab" + ".p-accordion__tab", ) as HTMLElement; const nextPanel = currentPanel.nextElementSibling as HTMLElement; const nextToggle = nextPanel.querySelector( - ".p-accordion__tab" + ".p-accordion__tab", ) as HTMLElement; const enableButton = () => { @@ -371,11 +373,11 @@ function initRegisterName( // Enable "Go to listing" button for an unpublished app const enableGoToListingButton = () => { const formField = formEl.querySelector( - "[name=snap-name]" + "[name=snap-name]", ) as HTMLInputElement; const snapName = formField.value; const paginationBtn = document.querySelector( - "#js-pagination-next" + "#js-pagination-next", ) as HTMLLinkElement; if (paginationBtn) { paginationBtn.href = `/${snapName}/listing?from=first-snap-unpublished`; @@ -425,7 +427,7 @@ function initRegisterName( } errorNotification( notificationEl, - "There was some problem registering name. Please try again." + "There was some problem registering name. Please try again.", ); }); }); diff --git a/static/js/public/fsf-language-select.ts b/static/js/public/fsf-language-select.ts index 2954b3dcc1..6cf92b010c 100644 --- a/static/js/public/fsf-language-select.ts +++ b/static/js/public/fsf-language-select.ts @@ -1,20 +1,20 @@ function initFSFLanguageSelect(): void { const flowLinksContainer = document.querySelector( - "[data-js='flow-links-container']" + "[data-js='flow-links-container']", ) as HTMLElement; const flowLinks = document.querySelectorAll( - "[data-js='flow-link']" + "[data-js='flow-link']", ) as NodeList; let activeFlowLink = flowLinksContainer.querySelector( - "[aria-current='page']" + "[aria-current='page']", ) as HTMLElement; let activeFlowLanguage = activeFlowLink.getAttribute("data-flow"); - let activeFlowContentSelector = `[data-flow-details='${activeFlowLanguage}']`; + const activeFlowContentSelector = `[data-flow-details='${activeFlowLanguage}']`; let activeFlowContent = document.querySelector( - activeFlowContentSelector + activeFlowContentSelector, ) as HTMLElement; function hideCurrentFlow() { @@ -25,7 +25,7 @@ function initFSFLanguageSelect(): void { function updateFlow( newFlowLink: HTMLLinkElement, newFlowLanguage: string, - newFlowContent: HTMLElement + newFlowContent: HTMLElement, ) { activeFlowLink = newFlowLink; activeFlowLanguage = newFlowLanguage; @@ -34,7 +34,7 @@ function initFSFLanguageSelect(): void { function showNewFlow( newFlowLink: HTMLLinkElement, - newFlowContent: HTMLElement + newFlowContent: HTMLElement, ) { newFlowLink.setAttribute("aria-current", "page"); newFlowContent.classList.remove("u-hide"); @@ -42,10 +42,10 @@ function initFSFLanguageSelect(): void { function handleFlowChange( newFlowLink: HTMLLinkElement, - newFlowLanguage: string + newFlowLanguage: string, ) { const newFlowContent = document.querySelector( - `[data-flow-details='${newFlowLanguage}']` + `[data-flow-details='${newFlowLanguage}']`, ) as HTMLElement; hideCurrentFlow(); @@ -67,14 +67,14 @@ function initFSFLanguageSelect(): void { }); const flowOptions = document.querySelector( - "[data-js='flow-options']" + "[data-js='flow-options']", ) as HTMLSelectElement; flowOptions.addEventListener("change", (e: Event) => { const target = e.target as HTMLSelectElement; const newFlowLanguage = target.value; const flowLink = flowLinksContainer.querySelector( - `[data-flow='${newFlowLanguage}']` + `[data-flow='${newFlowLanguage}']`, ) as HTMLLinkElement; handleFlowChange(flowLink, newFlowLanguage); diff --git a/static/js/public/ga-scroll-event.ts b/static/js/public/ga-scroll-event.ts index 076ae9db06..0c4e4c23b7 100644 --- a/static/js/public/ga-scroll-event.ts +++ b/static/js/public/ga-scroll-event.ts @@ -2,7 +2,7 @@ import { triggerEvent } from "../base/ga"; import debounce from "../libs/debounce"; const isInViewport = (el: { getBoundingClientRect: () => DOMRect }) => { - var bounding = el.getBoundingClientRect(); + const bounding = el.getBoundingClientRect(); return ( bounding.top >= 0 && bounding.left >= 0 && @@ -23,7 +23,7 @@ export default function triggerEventWhenVisible(selector: string): void { "element-visible", origin, selector, - `Element visible on screen: ${selector}` + `Element visible on screen: ${selector}`, ); } else { let triggered = false; @@ -35,11 +35,11 @@ export default function triggerEventWhenVisible(selector: string): void { "element-visible", origin, selector, - `Element visible on screen: ${selector}` + `Element visible on screen: ${selector}`, ); triggered = true; } - }, 500) + }, 500), ); } } else { diff --git a/static/js/public/nps.ts b/static/js/public/nps.ts index f7f23568ad..0f36d054f2 100644 --- a/static/js/public/nps.ts +++ b/static/js/public/nps.ts @@ -4,14 +4,14 @@ function nps(): void { const MktoForms2 = window.MktoForms2 || (null as unknown | null); const toggle = document.querySelector( - ".js-nps-comment-toggle" + ".js-nps-comment-toggle", ) as HTMLElement; if (!MktoForms2) { toggle.classList.add("u-hide"); return; } const commentHolder = document.querySelector( - ".js-nps-comment" + ".js-nps-comment", ) as HTMLElement; const form = commentHolder.querySelector("form") as HTMLFormElement; const button = form.querySelector("button") as HTMLButtonElement; @@ -57,7 +57,7 @@ function nps(): void { button.innerHTML = ``; mktoForm.submit(); }); - } + }, ); } diff --git a/static/js/public/snap-details/blog-posts.ts b/static/js/public/snap-details/blog-posts.ts index 65260423cb..8b81c2cfc3 100644 --- a/static/js/public/snap-details/blog-posts.ts +++ b/static/js/public/snap-details/blog-posts.ts @@ -1,17 +1,28 @@ import "whatwg-fetch"; +interface Post { + slug: string; + [key: string]: string | undefined; +} + +interface PostWithClassName extends Post { + className: string; +} + +type Modifier = (posts: Post[]) => Post[]; + class BlogPosts { url: string; path: string; - holder: any; - template: any; + holder: HTMLElement; + template: HTMLTemplateElement; limit: number; - modifiers?: ((posts: any) => any)[]; + modifiers?: Modifier[]; constructor( url: string | undefined, holderSelector: string, - templateSelector: string + templateSelector: string, ) { if (!url) { throw new Error("`url` must be defined"); @@ -25,8 +36,10 @@ class BlogPosts { this.url = url; this.path = ""; - this.holder = document.querySelector(holderSelector); - this.template = document.querySelector(templateSelector); + this.holder = document.querySelector(holderSelector) as HTMLElement; + this.template = document.querySelector( + templateSelector, + ) as HTMLTemplateElement; this.limit = 3; @@ -39,7 +52,7 @@ class BlogPosts { } } - setResultModifiers(modifiers: ((posts: any) => any)[]) { + setResultModifiers(modifiers: Modifier[]) { this.modifiers = modifiers; } @@ -60,29 +73,27 @@ class BlogPosts { const cols = 12 / this.limit; - posts.forEach( - (post: { [x: string]: any; slug?: any }, index: number) => { - if (index >= this.limit) { - return; - } - let postHTML = this.template.innerHTML; - Object.keys(post).forEach((key) => { - if (post[key]) { - postHTML = postHTML.split("${" + key + "}").join(post[key]); - } else { - postHTML = postHTML.split("${" + key + "}").join(""); - } - }); - const containerClasses = [`col-${cols}`]; - if (post.slug.indexOf("http") === 0) { - containerClasses.push(`p-blog-post--guest-post`); + posts.forEach((post: Post, index: number) => { + if (index >= this.limit) { + return; + } + let postHTML = this.template.innerHTML; + Object.keys(post).forEach((key) => { + if (post[key]) { + postHTML = postHTML.split("${" + key + "}").join(post[key]); + } else { + postHTML = postHTML.split("${" + key + "}").join(""); } - postHTML = postHTML - .split("${container_class}") - .join(containerClasses.join(" ")); - postsHTML.push(postHTML); + }); + const containerClasses = [`col-${cols}`]; + if (post.slug.indexOf("http") === 0) { + containerClasses.push(`p-blog-post--guest-post`); } - ); + postHTML = postHTML + .split("${container_class}") + .join(containerClasses.join(" ")); + postsHTML.push(postHTML); + }); if (postsHTML.length > 0) { this.holder.innerHTML = postsHTML.join(""); @@ -99,12 +110,12 @@ class BlogPosts { function snapDetailsPosts( holderSelector: string, templateSelector: string, - showOnSuccessSelector: string + showOnSuccessSelector: string, ): void { const blogPosts = new BlogPosts( "/blog/api/snap-posts/", holderSelector, - templateSelector + templateSelector, ); const snap = blogPosts.holder.dataset.snap; @@ -113,7 +124,7 @@ function snapDetailsPosts( } if (blogPosts.holder.dataset.limit) { - blogPosts.limit = blogPosts.holder.dataset.limit; + blogPosts.limit = parseInt(blogPosts.holder.dataset.limit, 10); } blogPosts.path = snap; @@ -132,10 +143,10 @@ function seriesPosts(holderSelector: string, templateSelector: string): void { const blogPosts = new BlogPosts( "/blog/api/series/", holderSelector, - templateSelector + templateSelector, ); - const series = blogPosts.holder.dataset.series; + const series = blogPosts.holder.dataset.series || ""; const currentSlug = blogPosts.holder.dataset.currentslug; blogPosts.path = series; @@ -145,13 +156,11 @@ function seriesPosts(holderSelector: string, templateSelector: string): void { return posts.reverse(); }, function filter(posts) { - return posts.map((post: { slug: any; className: string }) => { - if (post.slug === currentSlug) { - post.className = "is-current"; - } else { - post.className = ""; - } - return post; + return posts.map((post): PostWithClassName => { + return { + ...post, + className: post.slug === currentSlug ? "is-current" : "", + }; }); }, ]); diff --git a/static/js/public/snap-details/channelMap.ts b/static/js/public/snap-details/channelMap.ts index d3c9ab47d3..9520cd2587 100644 --- a/static/js/public/snap-details/channelMap.ts +++ b/static/js/public/snap-details/channelMap.ts @@ -1,6 +1,39 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import SnapEvents from "../../libs/events"; import { triggerEvent } from "../../base/ga"; +interface SnapElement extends HTMLElement { + dataset: { + snap: string; + }; +} + +function isSnapElement(target: HTMLElement): target is SnapElement { + return ( + (target as SnapElement).dataset && + (target as SnapElement).dataset.snap !== undefined + ); +} + +interface SlideInstallInstructionsElement extends HTMLElement { + dataset: { + channel: string; + confinement: string; + } & DOMStringMap; + closest: (selector: string) => Element | null; +} + +interface ChannelData { + track: string; + confinement: string; + "released-at": string; + risk: string; + version: string; + channel?: string; +} + +type ChannelMapData = Record; + class ChannelMap { RISK_ORDER: string[]; packageName: string; @@ -9,28 +42,18 @@ class ChannelMap { selectorString: string; channelMapEl: HTMLElement; channelOverlayEl: HTMLElement; - channelMapData: any; + channelMapData: ChannelMapData; events: SnapEvents; - INSTALL_TEMPLATE: any; + INSTALL_TEMPLATE: string = ""; CHANNEL_ROW_TEMPLATE: string | undefined; arch: string | undefined; - openButton: - | { - classList: any; - getBoundingClientRect(): unknown; - dataset: { - controls: string; - }; - innerText: string; - } - | undefined - | null; + openButton: HTMLElement | null | undefined; openScreenName: string | undefined; constructor( selectorString: string, packageName: string, - channelMapData: any, - defaultTrack: string + channelMapData: ChannelMapData, + defaultTrack: string, ) { this.RISK_ORDER = ["stable", "candidate", "beta", "edge"]; this.packageName = packageName; @@ -40,10 +63,10 @@ class ChannelMap { this.selectorString = selectorString; this.channelMapEl = document.querySelector( - this.selectorString + this.selectorString, ) as HTMLElement; this.channelOverlayEl = document.querySelector( - ".p-channel-map-overlay" + ".p-channel-map-overlay", ) as HTMLElement; this.channelMapData = channelMapData; @@ -59,11 +82,15 @@ class ChannelMap { this.bindEvents(); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any sortRows(rows: any[]) { // split tracks into strings and numbers - let numberTracks: Array = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const numberTracks: Array = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any let stringTracks: Array = []; - let latestTracks: any[] = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const latestTracks: any[] = []; rows.forEach((row) => { // numbers are defined by any string starting any of the following patterns: // just a number – 1,2,3,4, @@ -100,23 +127,23 @@ class ChannelMap { initOtherVersions() { let installTemplateEl = document.querySelector( - '[data-js="install-window"]' + '[data-js="install-window"]', ); if (!installTemplateEl) { installTemplateEl = document.getElementById("install-window-template"); } let channelRowTemplateEl = document.querySelector( - '[data-js="channel-map-row"]' + '[data-js="channel-map-row"]', ); if (!channelRowTemplateEl) { channelRowTemplateEl = document.getElementById( - "channel-map-row-template" + "channel-map-row-template", ); } if (!installTemplateEl || !channelRowTemplateEl) { const buttonsVersions = document.querySelector( - ".p-snap-install-buttons__versions" + ".p-snap-install-buttons__versions", ) as HTMLElement; buttonsVersions.style.display = "none"; return false; @@ -130,7 +157,7 @@ class ChannelMap { // initialize architecture select const archSelect = document.querySelector( - '[data-js="arch-select"]' + '[data-js="arch-select"]', ) as HTMLElement; archSelect.innerHTML = architectures @@ -142,17 +169,26 @@ class ChannelMap { return; } + private isTabClickElement( + target: HTMLElement, + ): target is HTMLElement & { dataset: { tab: string } } { + return "tab" in target.dataset; + } bindEvents() { this.events.addEvents({ click: { '[data-js="open-channel-map"]': ( event: { preventDefault: () => void }, - target: any + target: HTMLElement, ) => { event.preventDefault(); // If the button has already been clicked, close the channel map - if (target === this.openButton) { + if ( + this.openButton instanceof HTMLElement && + target instanceof HTMLElement && + target === this.openButton + ) { this.closeChannelMap(); this.openButton = null; } else { @@ -161,8 +197,8 @@ class ChannelMap { triggerEvent( this.openScreenName === "channel-map-install" ? "cta-0" : "cta-1", window.location.href, - target.dataset.controls, - target.innerText + target.dataset.controls ?? "", + target.innerText, ); } }, @@ -174,53 +210,75 @@ class ChannelMap { this.openButton = null; }, - '[data-js="slide-all-versions"]': (event: Event, target: any) => { + '[data-js="slide-all-versions"]': ( + event: Event, + target: HTMLElement, + ) => { event.preventDefault(); this.slideToVersions(target); }, '[data-js="switch-tab"]': ( event: { preventDefault: () => void }, - target: any + target: HTMLElement, ) => { event.preventDefault(); - this.switchTab(target); + if (this.isTabClickElement(target)) { + this.switchTab(target); + } }, - '[data-js="open-desktop"]': ( - event: Event, - target: { dataset: { snap: any }; innerText: string } - ) => { - event.preventDefault(); - this.openDesktop(target); - triggerEvent( - "cta-1", - window.location.href, - `snap://${target.dataset.snap}`, - target.innerText - ); + '[data-js="open-desktop"]': (event: Event, target: HTMLElement) => { + if (isSnapElement(target)) { + event.preventDefault(); + this.openDesktop(target); + triggerEvent( + "cta-1", + window.location.href, + `snap://${target.dataset.snap}`, + target.innerText, + ); + } else { + console.error("Target is not a SnapElement"); + } }, '[data-js="slide-install-instructions"]': ( - event: { preventDefault: () => void }, - target: any + event: Event & { preventDefault: () => void }, + target: HTMLElement, ) => { event.preventDefault(); - this.slideToInstructions(target); + + if ( + target instanceof HTMLElement && + target.dataset.channel && + target.dataset.confinement + ) { + const slideTarget = target as SlideInstallInstructionsElement; + this.slideToInstructions(slideTarget); + } else { + console.error("Target is not a SlideInstallInstructionsElement"); + } }, }, - change: { '[data-js="arch-select"]': ( - _event: any, - target: { value: string | number } + _event: Event, + target: HTMLSelectElement, ) => { - this.prepareTable(this.channelMapData[target.value]); + const selectedArch = target.value; + + if (selectedArch in this.channelMapData) { + this.prepareTable(this.channelMapData[selectedArch]); + } else { + console.error(`No data found for architecture: ${selectedArch}`); + } }, }, }); // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-explicit-any this.events.addEvent("keyup", window, (event: any) => { this._closeOnEscape.call(this, event); }); @@ -236,7 +294,7 @@ class ChannelMap { return; } const windowWidth = document.body.scrollWidth; - const buttonRect = this.openButton.getBoundingClientRect() as any; + const buttonRect = this.openButton.getBoundingClientRect(); const channelMapPosition = [ windowWidth - buttonRect.right, buttonRect.y + buttonRect.height + 16 + window.scrollY, @@ -246,13 +304,12 @@ class ChannelMap { this.channelMapEl.style.top = `${channelMapPosition[1]}px`; } - openChannelMap(openButton: any) { + openChannelMap(openButton: HTMLElement) { // Hide everything first, so we can click between this.closeChannelMap(); this.openButton = openButton; - // @ts-ignore this.openButton.classList.add("is-active"); this.positionChannelMap(); @@ -263,7 +320,7 @@ class ChannelMap { this.openButton.getAttribute("aria-controls") || "channel-map-install"; const openScreen = this.channelMapEl.querySelector( - `#${this.openScreenName}` + `#${this.openScreenName}`, ) as HTMLElement; // select default screen before opening @@ -303,21 +360,22 @@ class ChannelMap { } } - _closeOnClick(event: { target: { closest: (arg0: any) => any } }) { + _closeOnClick(event: MouseEvent) { // when channel map is not closed and clicking outside of it, close it + const target = event.target as HTMLElement; if ( !this.channelMapEl.classList.contains("is-closed") && - !event.target.closest(this.selectorString) + !target.closest(this.selectorString) ) { this.closeChannelMap(); } } - openDesktop(clickEl: { dataset: any; innerText?: string }) { - const name = clickEl.dataset.snap.trim(); + openDesktop(clickEl: HTMLElement) { + const name = clickEl.dataset.snap?.trim() || ""; let iframe = document.querySelector( - ".js-snap-open-frame" - ) as HTMLIFrameElement; + ".js-snap-open-frame", + ) as HTMLIFrameElement | null; if (iframe && iframe.parentNode) { iframe.parentNode.removeChild(iframe); @@ -349,28 +407,32 @@ class ChannelMap { } } - slideToVersions(clickEl: { closest: (arg0: string) => any }) { + slideToVersions(clickEl: HTMLElement) { const slides = clickEl.closest(".p-channel-map__slides"); - slides.classList.add("show-left"); - slides.classList.remove("show-right"); + if (slides instanceof HTMLElement) { + slides.classList.add("show-left"); + slides.classList.remove("show-right"); + } } - slideToInstructions(clickEl: { - dataset: { channel: any; confinement: any }; - closest: (arg0: string) => any; - }) { - // Add content to the right slide area - this.writeInstallInstructions( - clickEl.dataset.channel, - clickEl.dataset.confinement - ); + slideToInstructions(clickEl: HTMLElement) { + const channel = clickEl.dataset.channel; + const confinement = clickEl.dataset.confinement; + + if (channel && confinement) { + this.writeInstallInstructions(channel, confinement); + } else { + console.error("Missing channel or confinement data"); + } const slides = clickEl.closest(".p-channel-map__slides"); - slides.classList.add("show-right"); - slides.classList.remove("show-left"); + if (slides instanceof HTMLElement) { + slides.classList.add("show-right"); + slides.classList.remove("show-left"); + } } - writeInstallInstructions(channel: string, confinement: string) { + writeInstallInstructions(channel: string, confinement: string): void { let paramString = ""; // By default no params are required @@ -413,17 +475,17 @@ class ChannelMap { } const holder = document.querySelector( - '[data-js="channel-map-install-details"]' + '[data-js="channel-map-install-details"]', ) as HTMLElement; holder.innerHTML = newDiv.innerHTML; } - writeTable(el: { innerHTML: any }, data: any[]) { - let cache: any; + writeTable(el: HTMLElement, data: string[][]): void { + let cache: string | undefined; const tbody = data.map((row, i) => { const isSameTrack = cache && row[0] === cache; - let rowClass = []; + const rowClass: string[] = []; if (i === 0) { rowClass.push("is-highlighted"); @@ -437,12 +499,12 @@ class ChannelMap { if (this.CHANNEL_ROW_TEMPLATE) { _row = this.CHANNEL_ROW_TEMPLATE.split("${rowClass}").join( - rowClass.join(" ") + rowClass.join(" "), ); } - row.forEach((val: string | undefined, index: string) => { - _row = _row.split("${row[" + index + "]}").join(val); + row.forEach((val, index) => { + _row = _row.split("${row[" + index + "]}").join(val || ""); }); cache = row[0]; @@ -458,46 +520,44 @@ class ChannelMap { * @param {Object} archData * @param {Array.<{channel: string, confinement: string, 'released-at': string, risk: string, size: number, version: string}>} archData.track */ - prepareTable(archData: { [x: string]: any[] }) { + prepareTable(archData: ChannelData[]) { const tbodyEl = this.channelMapEl.querySelector( - '[data-js="channel-map-table"]' + '[data-js="channel-map-table"]', ) as HTMLElement; // If we're on the overview tab we only want to see latest/[all risks] // and [all tracks]/[highest risk], so filter out anything that isn't these const filtered = this.currentTab === "overview"; - let numberOfTracks = 0; + const numberOfTracks = new Set(archData.map((data) => data.channel)).size; let trimmedNumberOfTracks = 0; - // Get a total number of tracks - Object.keys(archData).forEach((arch) => { - numberOfTracks += archData[arch].length; - }); - - let rows: Array = []; + const rows: Array = []; - // If we're not filtering, pass through all the data.... - let trackList = filtered ? {} : archData; + const trackList: Record = archData.reduce( + (acc, data) => { + const track = data.channel || "unknown"; + if (!acc[track]) acc[track] = []; + acc[track].push(data); + return acc; + }, + {} as Record, + ); - // ...and don't do the expensive bit if (filtered) { - Object.keys(archData).forEach((track) => { - // Sort by risk - archData[track].sort((a, b) => { - return ( - this.RISK_ORDER.indexOf(a["risk"]) - - this.RISK_ORDER.indexOf(b["risk"]) - ); - }); - - // Only the default track has all risks - // Other tracks should show the highest risk + Object.entries(trackList).forEach(([track, channelDataArray]) => { + // Sort the ChannelData array for this track + channelDataArray.sort( + (a, b) => + this.RISK_ORDER.indexOf(a.risk) - this.RISK_ORDER.indexOf(b.risk), + ); + if (track === this.defaultTrack) { - trackList[track] = archData[track]; - trimmedNumberOfTracks += trackList[track].length; + // Keep all risks for the default track + trimmedNumberOfTracks += channelDataArray.length; } else { - trackList[track] = [archData[track][0]]; + // Only keep the highest risk for other tracks + trackList[track] = [channelDataArray[0]]; trimmedNumberOfTracks += 1; } }); @@ -528,7 +588,7 @@ class ChannelMap { hideTabs() { const tabs = document.querySelector( - '[data-js="channel-map-tabs"]' + '[data-js="channel-map-tabs"]', ) as HTMLElement; if (tabs) { @@ -536,33 +596,42 @@ class ChannelMap { } } - switchTab(clickEl: { - closest: (arg0: string) => { - (): any; - new (): any; - querySelector: { (arg0: string): any; new (): any }; - }; - dataset: { tab: string }; - setAttribute: (arg0: string, arg1: string) => void; - }) { - const selected = clickEl - .closest(".p-tabs") - .querySelector('[aria-selected="true"]'); - this.currentTab = clickEl.dataset.tab; + switchTab(clickEl: HTMLElement) { + const tabsContainer = clickEl.closest(".p-tabs"); + if (!(tabsContainer instanceof HTMLElement)) { + console.error("Tabs container not found"); + return; + } + + const selected = tabsContainer.querySelector('[aria-selected="true"]'); + if (!(selected instanceof HTMLElement)) { + console.error("No selected tab found"); + return; + } + + const tab = clickEl.dataset.tab; + if (!tab) { + console.error("No tab data found on clicked element"); + return; + } + + this.currentTab = tab; selected.removeAttribute("aria-selected"); clickEl.setAttribute("aria-selected", "true"); - if (this.arch) { + if (this.arch && this.arch in this.channelMapData) { this.prepareTable(this.channelMapData[this.arch]); + } else if (this.arch) { + console.error(`No data found for architecture: ${this.arch}`); } } } export default function channelMap( - el: any, - packageName: any, - channelMapData: any, - defaultTrack: any + el: string, + packageName: string, + channelMapData: ChannelMapData, + defaultTrack: string, ) { return new ChannelMap(el, packageName, channelMapData, defaultTrack); } diff --git a/static/js/public/snap-details/embeddedCard.ts b/static/js/public/snap-details/embeddedCard.ts index 53bf3f8b0f..fc2fccd119 100644 --- a/static/js/public/snap-details/embeddedCard.ts +++ b/static/js/public/snap-details/embeddedCard.ts @@ -8,7 +8,7 @@ const hideEl = (el: { classList: { add: (arg0: string) => void } }) => function toggleModal( modal: HTMLElement, show?: boolean | undefined, - initCallback?: { (): void; (): void } | undefined + initCallback?: { (): void; (): void } | undefined, ) { if (typeof show === "undefined") { show = modal.classList.contains("u-hide"); @@ -28,11 +28,11 @@ function toggleModal( export default function initEmbeddedCardModal(snapName: string): void { const toggle = document.querySelector( - ".js-embedded-card-toggle" + ".js-embedded-card-toggle", ) as HTMLElement; const modal = document.querySelector("#embedded-card-modal") as HTMLElement; const dialog = modal.querySelector( - "#embedded-card-modal-dialog" + "#embedded-card-modal-dialog", ) as HTMLElement; const previewFrame = modal.querySelector("#embedded-card-frame"); const codeElement = modal.querySelector("#snippet-card-html"); diff --git a/static/js/public/snap-details/map.ts b/static/js/public/snap-details/map.ts index 9405e4ac8a..50a620324b 100644 --- a/static/js/public/snap-details/map.ts +++ b/static/js/public/snap-details/map.ts @@ -1,9 +1,9 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import { select, pointer, Selection, BaseType } from "d3-selection"; import { json } from "d3-fetch"; import { geoNaturalEarth1, geoPath } from "d3-geo"; import { feature, mesh } from "topojson-client"; -import SnapEvents from "../../libs/events"; import { GeoJsonProperties } from "geojson"; import { Topology, Objects } from "topojson-specification"; @@ -17,7 +17,7 @@ export default function renderMap( number_of_users: number; percentage_of_users: number; }; - } + }, ) { const mapEl = select(el); @@ -39,7 +39,7 @@ export default function renderMap( percentage_of_users: number; }; }, - world: Topology> + world: Topology>, ) { const width = mapEl.property("clientWidth"); const height = width * 0.5; @@ -81,7 +81,7 @@ export default function renderMap( coordinates: Array>; }; id: number; - properties: {}; + properties: object; type: string; }, SVGGElement, @@ -137,7 +137,7 @@ export default function renderMap( .style("left", pos[0] + "px") .style("display", "block"); - let content = [ + const content = [ '', countrySnapData.name, ]; @@ -151,7 +151,7 @@ export default function renderMap( style="background-color: rgb(${countrySnapData.color_rgb[0]}, ${ countrySnapData.color_rgb[1] }, ${countrySnapData.color_rgb[2]})"> - ${content.join(" ")}` + ${content.join(" ")}`, ); } }) @@ -164,7 +164,7 @@ export default function renderMap( // @ts-expect-error mesh(world, world.objects.countries, function (a, b) { return a !== b; - }) + }), ) .attr("class", "snapcraft-territories__boundary") .attr("d", path); @@ -175,10 +175,7 @@ export default function renderMap( let resizeTimeout: string | number | NodeJS.Timeout | undefined; - // @ts-expect-error - const events = new SnapEvents(); - - events.addEvent("resize", window, () => { + window.addEventListener("resize", () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(function () { render(mapEl, snapData, world); diff --git a/static/js/public/snap-details/reportSnap.ts b/static/js/public/snap-details/reportSnap.ts index 45b0ce004a..8057b1c888 100644 --- a/static/js/public/snap-details/reportSnap.ts +++ b/static/js/public/snap-details/reportSnap.ts @@ -20,7 +20,7 @@ function toggleModal(modal: HTMLElement, show?: boolean): void { function initForm(modal: HTMLElement): void { buttonEnabled( modal.querySelector("button[type=submit]") as HTMLButtonElement, - "Submit report" + "Submit report", ); showEl(modal.querySelector(".js-report-snap-form") as HTMLElement); @@ -43,17 +43,17 @@ function showError(modal: HTMLElement): void { export default function initReportSnap( toggleSelector: string, modalSelector: string, - formURL: string + formURL: string, ): void { const toggle = document.querySelector(toggleSelector) as HTMLElement; const modal = document.querySelector(modalSelector) as HTMLElement; const reportForm = modal.querySelector("form") as HTMLFormElement; const honeypotField = reportForm.querySelector( - "#report-snap-confirm" + "#report-snap-confirm", ) as HTMLInputElement; const commentField = reportForm.querySelector( - "#report-snap-comment" + "#report-snap-comment", ) as HTMLInputElement; toggle.addEventListener("click", (event) => { @@ -73,7 +73,7 @@ export default function initReportSnap( e.preventDefault(); buttonLoading( reportForm.querySelector("button[type=submit]") as HTMLButtonElement, - "Submitting…" + "Submitting…", ); if ( diff --git a/static/js/public/snap-details/screenshots.ts b/static/js/public/snap-details/screenshots.ts index 1b0b5a38f8..e5d04c33dd 100644 --- a/static/js/public/snap-details/screenshots.ts +++ b/static/js/public/snap-details/screenshots.ts @@ -21,7 +21,7 @@ function clickCallback(event: Event): void { function filterImages(): (string | undefined)[] { const screenshotsEls = screenshotsEl.querySelectorAll( - "img, video, .js-video-slide" + "img, video, .js-video-slide", ) as NodeListOf; return Array.from(screenshotsEls) @@ -41,13 +41,13 @@ function initScreenshots(this: unknown, screenshotsId: string) { // We need to resize the iframe on window resize window.addEventListener( "resize", - debounce(iframeSize.bind(this, ".js-video-slide"), 100) + debounce(iframeSize.bind(this, ".js-video-slide"), 100), ); iframeSize(".js-video-slide"); const swipeContainer = screenshotsEl.querySelector( - ".swiper-container" + ".swiper-container", ) as HTMLElement; if (swipeContainer) { diff --git a/static/js/public/snap-details/videos.ts b/static/js/public/snap-details/videos.ts index 4ba89f782f..018068a9a5 100644 --- a/static/js/public/snap-details/videos.ts +++ b/static/js/public/snap-details/videos.ts @@ -8,7 +8,7 @@ function vimeo(): void { } const frame = document.getElementById( - "vimeoplayer" + "vimeoplayer", ) as HTMLIFrameElement | null; const vimeoReady = () => { diff --git a/static/js/publisher/build-status.ts b/static/js/publisher/build-status.ts index f2931e80d5..c943f59c5f 100644 --- a/static/js/publisher/build-status.ts +++ b/static/js/publisher/build-status.ts @@ -6,11 +6,11 @@ function getStatuses() { function addBuildStatus( row: HTMLElement, - data: Array<{ name: string; status: string }> + data: Array<{ name: string; status: string }>, ): void { const snapName: string | undefined = row.dataset.snapName; const releaseData: { name: string; status: string } | undefined = data.find( - (d) => d.name === snapName + (d) => d.name === snapName, ); let buildStatus: string | undefined; @@ -20,7 +20,7 @@ function addBuildStatus( } const buildColumn = row.querySelector( - "[data-js='snap-build-status']" + "[data-js='snap-build-status']", ) as HTMLElement; const failedStatuses: string[] = ["failed_to_build", "release_failed"]; @@ -49,7 +49,7 @@ function buildStatus(): void { getStatuses() .then((data) => { const snapListRows = document.querySelectorAll( - "[data-js='snap-list-row']" + "[data-js='snap-list-row']", ) as NodeListOf; snapListRows.forEach((row) => addBuildStatus(row, data)); diff --git a/static/js/publisher/builds/helpers.ts b/static/js/publisher/builds/helpers.ts index 47375137b8..72409604de 100644 --- a/static/js/publisher/builds/helpers.ts +++ b/static/js/publisher/builds/helpers.ts @@ -30,14 +30,14 @@ export const UserFacingStatus: { "Built, won’t be released", "Built", 6, - WONT_RELEASE + WONT_RELEASE, ), [RELEASED]: createStatus("Released", "Released", 5, "released"), [RELEASE_FAILED]: createStatus( "Built, failed to release", "Failed", 4, - RELEASE_FAILED + RELEASE_FAILED, ), [RELEASING_SOON]: createStatus("Releasing", "Releasing", 3, RELEASING_SOON), [IN_PROGRESS]: createStatus("In progress", "In progress", 2, IN_PROGRESS), @@ -45,7 +45,7 @@ export const UserFacingStatus: { "Failed to build", "Failed", 1, - FAILED_TO_BUILD + FAILED_TO_BUILD, ), [CANCELLED]: createStatus("Cancelled", "Cancelled", 8, CANCELLED), [UNKNOWN]: createStatus("Unknown", "Unknown", 8, NEVER_BUILT), @@ -55,7 +55,7 @@ export function createStatus( statusMessage: string, shortStatusMessage: string, priority: number, - badge: string + badge: string, ) { const loadingStatus = [IN_PROGRESS, RELEASING_SOON]; let icon; diff --git a/static/js/publisher/builds/repoDisconnect.ts b/static/js/publisher/builds/repoDisconnect.ts index caeaa84bf6..d796efa087 100644 --- a/static/js/publisher/builds/repoDisconnect.ts +++ b/static/js/publisher/builds/repoDisconnect.ts @@ -14,16 +14,16 @@ function initRepoDisconnect() { } const repoDisconnectButtons = document.querySelectorAll( - "[aria-controls='repo-disconnect-modal']" + "[aria-controls='repo-disconnect-modal']", ) as NodeList; const repoDisconnectConfirm = document.querySelector( - "[data-js='repo-disconnect-confirm']" + "[data-js='repo-disconnect-confirm']", ) as HTMLButtonElement; const repoDisconnectModal = document.querySelector( - "[data-js='repo-disconnect-modal']" + "[data-js='repo-disconnect-modal']", ) as HTMLElement; const repoDisconnectForm = document.getElementById( - "repoDisconnectForm" + "repoDisconnectForm", ) as HTMLElement; if ( diff --git a/static/js/publisher/form.ts b/static/js/publisher/form.ts index 0b012e2773..b11c7db420 100644 --- a/static/js/publisher/form.ts +++ b/static/js/publisher/form.ts @@ -8,6 +8,10 @@ import { initMedia } from "./market/initMedia"; import { initIcon } from "./market/initIcon"; import { initBanner } from "./market/initBanner"; +interface Image { + type: "icon" | "screenshot" | "banner"; +} + // https://gist.github.com/dperini/729294 // Luke 07-06-2018 made the protocol optional const URL_REGEXP = @@ -22,17 +26,30 @@ const IS_CHROMIUM = typeof window.chrome !== "undefined" && window.navigator.userAgent.indexOf("Edge") === -1; // Edge pretends to have window.chrome +interface InitFormConfig { + form: string; + snapIconHolder: HTMLElement | null; + formNotification: string; + mediaHolder: HTMLElement | null; + bannerHolder: HTMLElement | null; + licenseRadioContent: HTMLElement | null; +} + +interface InitialState { + [key: string]: unknown; +} + function initFormNotification(formElId: string, notificationElId: string) { - var form = document.getElementById(formElId) as HTMLFormElement; + const form = document.getElementById(formElId) as HTMLFormElement; form.addEventListener("change", function () { - var notification = document.getElementById(notificationElId); + const notification = document.getElementById(notificationElId); if (notification && notification.parentNode) { notification.parentNode.removeChild(notification); } }); - var notification = document.getElementById(notificationElId); + const notification = document.getElementById(notificationElId); if (notification) { setTimeout(function () { @@ -44,16 +61,9 @@ function initFormNotification(formElId: string, notificationElId: string) { } function initForm( - config: { - form: string; - snapIconHolder: any; - formNotification: string; - mediaHolder: any; - bannerHolder: any; - licenseRadioContent: any; - }, - initialState: any, - errors: string | any[] | undefined + config: InitFormConfig, + initialState: InitialState, + errors: string | string[] | undefined, ) { // if there are errors focus first error if (errors && errors.length) { @@ -67,7 +77,7 @@ function initForm( if (errorInput.scrollIntoView) { const stickyEl = document.querySelector( - ".snapcraft-p-sticky" + ".snapcraft-p-sticky", ) as HTMLElement; const stickyHeight = stickyEl.scrollHeight; errorInput.scrollIntoView(); @@ -77,13 +87,13 @@ function initForm( } // setup form functionality - const formEl = document.getElementById(config.form) as any; + const formEl = document.getElementById(config.form) as HTMLFormElement; const submitButton = formEl.querySelector( - ".js-form-submit" + ".js-form-submit", ) as HTMLButtonElement; const revertButton = formEl.querySelector(".js-form-revert") as HTMLElement; const previewButton = formEl.querySelector( - ".js-listing-preview" + ".js-listing-preview", ) as HTMLElement; const revertURL = revertButton.getAttribute("href") as string; const disabledRevertClass = "is-disabled"; @@ -110,7 +120,7 @@ function initForm( disableSubmit(); disableRevert(); - let state = JSON.parse(JSON.stringify(initialState)); + const state = JSON.parse(JSON.stringify(initialState)); const diffInput = document.createElement("input"); diffInput.type = "hidden"; @@ -121,36 +131,41 @@ function initForm( if (config.snapIconHolder) { const icons = state.images.filter( - (image: { type: string }) => image.type === "icon" + (image: { type: string }) => image.type === "icon", ); - initIcon(config.snapIconHolder, icons[0], state.title, (newIcon: any) => { - let noneIcons = state.images.filter( - (image: { type: string }) => image.type !== "icon" - ); + initIcon( + config.snapIconHolder, + icons[0], + state.title, + (newIcon: Image | null) => { + let noneIcons = state.images.filter( + (image: { type: string }) => image.type !== "icon", + ); - if (newIcon) { - noneIcons = noneIcons.concat([newIcon]); - } + if (newIcon) { + noneIcons = noneIcons.concat([newIcon]); + } - const newState = { - ...state, - images: noneIcons, - }; + const newState = { + ...state, + images: noneIcons, + }; - updateState(state, newState); - updateFormState(); - }); + updateState(state, newState); + updateFormState(); + }, + ); } initFormNotification(config.form, config.formNotification); if (config.mediaHolder) { const screenshots = state.images.filter( - (image: { type: string }) => image.type === "screenshot" + (image: { type: string }) => image.type === "screenshot", ); - initMedia(config.mediaHolder, screenshots, (newImages: any) => { + initMedia(config.mediaHolder, screenshots, (newImages: Image[]) => { const noneScreenshots = state.images.filter( - (item: { type: string }) => item.type !== "screenshot" + (item: { type: string }) => item.type !== "screenshot", ); const newState = { ...state, @@ -162,12 +177,12 @@ function initForm( } if (config.bannerHolder) { - let banners = state.images.filter( - (image: { type: string }) => image.type === "banner" + const banners = state.images.filter( + (image: { type: string }) => image.type === "banner", ); - initBanner(config.bannerHolder, banners, (image: any) => { + initBanner(config.bannerHolder, banners, (image: Image | null) => { let newImages = state.images.filter( - (image: { type: string }) => image.type !== "banner" + (image: { type: string }) => image.type !== "banner", ); if (image) { @@ -198,7 +213,7 @@ function initForm( // confirmation message (ignored by most browsers), // but may be showed by some older ones - var confirmationMessage = + const confirmationMessage = "Changes that you made will not be saved if you leave the page."; event.returnValue = confirmationMessage; // Gecko, Trident, Chrome 34+ @@ -237,7 +252,15 @@ function initForm( } } - function metadata(field: { checked: any }, state: { [x: string]: any }) { + interface Field { + checked: boolean; + } + + interface State { + [key: string]: unknown; + } + + function metadata(field: Field, state: State) { state["update_metadata_on_release"] = field.checked; } @@ -252,15 +275,21 @@ function initForm( if (formEl["license"]) { license(formEl); } - if (formEl.elements["primary_category"]) { + const primaryCategoryElement = + formEl.elements.namedItem("primary_category"); + if (primaryCategoryElement) { categories(formEl, state); } - if (formEl.elements["update_metadata_on_release"]) { - metadata(formEl.elements["update_metadata_on_release"], state); + const updateMetadataElement = formEl.elements.namedItem( + "update_metadata_on_release", + ) as HTMLInputElement; + if (updateMetadataElement) { + const fieldValue: Field = { checked: updateMetadataElement.checked }; + metadata(fieldValue, state); } - let formData = new FormData(formEl); + const formData = new FormData(formEl); // update state based on data of all inputs updateState(state, formData); @@ -329,19 +358,19 @@ function initForm( ignoreChangesOnUnload = true; const updateMetadataModal = document.querySelector( - ".update-metadata-warning" + ".update-metadata-warning", ); if (updateMetadataModal) { updateMetadataModal.classList.remove("u-hide"); const saveChangesButton = updateMetadataModal.querySelector( - ".js-save-changes" + ".js-save-changes", ) as HTMLButtonElement; - const closeModalButtons = - updateMetadataModal.querySelectorAll(".js-close-modal"); + const closeModalButtons: NodeListOf = + document.querySelectorAll(".close-modal-button"); - closeModalButtons.forEach((closeModalButton: any) => { + closeModalButtons.forEach((closeModalButton: HTMLElement) => { closeModalButton.addEventListener("click", () => { updateMetadataModal.classList.add("u-hide"); }); @@ -365,22 +394,30 @@ function initForm( } else { updateLocalStorage(); } - } + }, ); // client side validation - const validation: { [key: string]: any } = {}; + interface InputValidation { + isValid: boolean; + required?: boolean; + maxLength?: number; + counterEl?: HTMLElement; + mailto?: boolean; + url?: boolean; + } + const validation: { [key: string]: InputValidation } = {}; const validateInputs = Array.from(formEl.querySelectorAll("input,textarea")); function isFormValid() { // form is valid if every validated input is valid return Object.keys(validation).every( - (name: any) => validation[name].isValid + (name: string) => validation[name].isValid, ); } - function validateInput(input: any) { + function validateInput(input: HTMLInputElement | HTMLTextAreaElement) { const field = input.closest(".p-form-validation"); if (field) { @@ -403,12 +440,14 @@ function initForm( if (inputValidation.maxLength) { if (validation[input.name].maxLength === input.value.length) { - inputValidation.counterEl.innerHTML = `The maximum number of characters for this field is ${ - validation[input.name].maxLength - }.`; + if (inputValidation.counterEl) { + inputValidation.counterEl.innerHTML = `The maximum number of characters for this field is ${inputValidation.maxLength}.`; + } showCounter = true; } else { - inputValidation.counterEl.innerHTML = ""; + if (inputValidation.counterEl) { + inputValidation.counterEl.innerHTML = ""; + } showCounter = false; } } @@ -446,45 +485,68 @@ function initForm( } // prepare validation of inputs based on their HTML attributes - validateInputs.forEach((input: any) => { - const inputValidation: any = { isValid: true }; - - if (input.maxLength > 0) { - // save max length, but remove it from input so more chars can be entered - inputValidation.maxLength = input.maxLength; - - // prepare counter element to show how many chars need to be removed - const counter = document.createElement("p"); - counter.className = "p-form-help-text"; - inputValidation.counterEl = counter; - input.parentNode.appendChild(counter); - } + validateInputs.forEach((input) => { + const inputValidation: InputValidation = { isValid: true }; + + if ( + input instanceof HTMLInputElement || + input instanceof HTMLTextAreaElement + ) { + if (input.maxLength > 0) { + // save max length, but remove it from input so more chars can be entered + inputValidation.maxLength = input.maxLength; + + // prepare counter element to show how many chars need to be removed + const counter = document.createElement("p"); + counter.className = "p-form-help-text"; + inputValidation.counterEl = counter; + if (input.parentNode) { + input.parentNode.appendChild(counter); + } + } - if (input.required) { - inputValidation.required = true; - } + if (input.required) { + inputValidation.required = true; + } - if (input.type === "url") { - inputValidation.url = true; - } + if (input.type === "url") { + inputValidation.url = true; + } - // allow mailto: addresses for contact field - if (input.name === "contact") { - inputValidation.mailto = true; - } + // allow mailto: addresses for contact field + if (input.name === "contact") { + inputValidation.mailto = true; + } - validation[input.name] = inputValidation; + validation[input.name] = inputValidation; + } }); // validate inputs on change - formEl.addEventListener("input", function (event: { target: any }) { - validateInput(event.target); - updateFormState(); + formEl.addEventListener("input", function (event: Event) { + const target = event.target as unknown as + | HTMLInputElement + | HTMLTextAreaElement + | null; + + if ( + target && + (target instanceof HTMLInputElement || + target instanceof HTMLTextAreaElement) + ) { + validateInput(target); + updateFormState(); + } }); - const previewForm = document.getElementById("preview-form") as any; + const previewForm = document.getElementById( + "preview-form", + ) as HTMLFormElement; + const openPreview = () => { - const stateInput = previewForm.elements.state; + const stateInput = previewForm.elements.namedItem( + "state", + ) as HTMLInputElement; stateInput.value = JSON.stringify(state); }; @@ -493,7 +555,7 @@ function initForm( } // Prefix contact and website fields on blur if the user doesn't provide the protocol - function prefixInput(input: any) { + function prefixInput(input: HTMLInputElement) { if (["website", "contact"].includes(input.name)) { if ( validation[input.name].isValid && @@ -516,14 +578,15 @@ function initForm( } const prefixableFields = ["website", "contact"]; - prefixableFields.forEach((inputName: any) => { + prefixableFields.forEach((inputName: string) => { const input = formEl[inputName]; if (input) { input.addEventListener( "blur", function (event: { target: EventTarget | null }) { - prefixInput(event.target); - } + const target = event.target as HTMLInputElement; + prefixInput(target); + }, ); } }); diff --git a/static/js/publisher/listing/components/App/App.tsx b/static/js/publisher/listing/components/App/App.tsx index 897bc73ebf..7f87665e55 100644 --- a/static/js/publisher/listing/components/App/App.tsx +++ b/static/js/publisher/listing/components/App/App.tsx @@ -40,7 +40,7 @@ function App() { useState(false); const [formData, setFormData] = useState({}); const [updateMetadataOnRelease, setUpdateMetadataOnRelease] = useState( - snapData.update_metadata_on_release + snapData.update_metadata_on_release, ); const { @@ -116,7 +116,7 @@ function App() { useEffect(() => { const tourContainer = document.getElementById( - "tour-container" + "tour-container", ) as HTMLElement; initListingTour({ snapName, diff --git a/static/js/publisher/listing/components/App/__tests__/App.test.tsx b/static/js/publisher/listing/components/App/__tests__/App.test.tsx index dd1a2d3f3f..e212bf5612 100644 --- a/static/js/publisher/listing/components/App/__tests__/App.test.tsx +++ b/static/js/publisher/listing/components/App/__tests__/App.test.tsx @@ -13,7 +13,7 @@ const renderComponent = () => render( - + , ); window.listingData = mockListingData; @@ -22,12 +22,18 @@ window.tourSteps = mockListingData.tour_steps; describe("App", () => { it("shows 'Save' button as disabled by default", () => { renderComponent(); - expect(screen.getByRole("button", { name: "Save" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Save" })).toHaveAttribute( + "aria-disabled", + "true", + ); }); it("shows 'Revert' button as disabled by default", () => { renderComponent(); - expect(screen.getByRole("button", { name: "Revert" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Revert" })).toHaveAttribute( + "aria-disabled", + "true", + ); }); it("enables 'Save' button if a change is made to the form", async () => { @@ -35,7 +41,7 @@ describe("App", () => { renderComponent(); await user.type( screen.getByRole("textbox", { name: "Title:" }), - "new-name" + "new-name", ); expect(screen.getByRole("button", { name: "Save" })).not.toBeDisabled(); }); @@ -45,7 +51,7 @@ describe("App", () => { renderComponent(); await user.type( screen.getByRole("textbox", { name: "Title:" }), - "new-name" + "new-name", ); expect(screen.getByRole("button", { name: "Revert" })).not.toBeDisabled(); }); @@ -57,7 +63,10 @@ describe("App", () => { await user.type(input, "new-name"); await user.clear(input); await user.type(input, mockListingData.snap_title); - expect(screen.getByRole("button", { name: "Save" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Save" })).toHaveAttribute( + "aria-disabled", + "true", + ); }); it("disables 'Revert' button if a change is made to the form and then reset", async () => { @@ -67,6 +76,9 @@ describe("App", () => { await user.type(input, "new-name"); await user.clear(input); await user.type(input, mockListingData.snap_title); - expect(screen.getByRole("button", { name: "Revert" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Revert" })).toHaveAttribute( + "aria-disabled", + "true", + ); }); }); diff --git a/static/js/publisher/listing/components/CategoriesInput/CategoriesInput.test.tsx b/static/js/publisher/listing/components/CategoriesInput/CategoriesInput.test.tsx index cba65ee1aa..d2523ec983 100644 --- a/static/js/publisher/listing/components/CategoriesInput/CategoriesInput.test.tsx +++ b/static/js/publisher/listing/components/CategoriesInput/CategoriesInput.test.tsx @@ -29,14 +29,14 @@ jest.mock("nanoid", () => { test("the correct primary category is selected", () => { render(); expect(screen.getByRole("combobox", { name: "Category:" })).toHaveValue( - "productivity" + "productivity", ); }); test("the correct secondary category is selected", () => { render(); expect( - screen.getByRole("combobox", { name: "Second category:" }) + screen.getByRole("combobox", { name: "Second category:" }), ).toHaveValue("development"); }); @@ -45,7 +45,7 @@ test("the primary category option is disabled in the secondary field", () => { expect( screen .getByRole("combobox", { name: "Second category:" }) - .querySelector("option[value='productivity']") + .querySelector("option[value='productivity']"), ).toBeDisabled(); }); @@ -54,21 +54,21 @@ test("the secondary category option is disabled in the primary field", () => { expect( screen .getByRole("combobox", { name: "Category:" }) - .querySelector("option[value='development']") + .querySelector("option[value='development']"), ).toBeDisabled(); }); test("the second category field is not present if no second category", () => { render(); expect( - screen.queryByRole("combobox", { name: "Second category:" }) + screen.queryByRole("combobox", { name: "Second category:" }), ).not.toBeInTheDocument(); }); test("the second category field is present if there is second category", () => { render(); expect( - screen.getByRole("combobox", { name: "Second category:" }) + screen.getByRole("combobox", { name: "Second category:" }), ).toBeInTheDocument(); }); @@ -76,13 +76,13 @@ test("the add category button adds the second category field", () => { render(); expect( - screen.queryByRole("combobox", { name: "Second category:" }) + screen.queryByRole("combobox", { name: "Second category:" }), ).not.toBeInTheDocument(); fireEvent.click(screen.getByTestId("add-category-button")); expect( - screen.getByRole("combobox", { name: "Second category:" }) + screen.getByRole("combobox", { name: "Second category:" }), ).toBeInTheDocument(); }); @@ -90,12 +90,12 @@ test("the remove category button removes the second category field", () => { render(); expect( - screen.getByRole("combobox", { name: "Second category:" }) + screen.getByRole("combobox", { name: "Second category:" }), ).toBeInTheDocument(); fireEvent.click(screen.getByTestId("delete-category-button")); expect( - screen.queryByRole("combobox", { name: "Second category:" }) + screen.queryByRole("combobox", { name: "Second category:" }), ).not.toBeInTheDocument(); }); diff --git a/static/js/publisher/listing/components/CategoriesInput/CategoriesInput.tsx b/static/js/publisher/listing/components/CategoriesInput/CategoriesInput.tsx index 8078006a3f..1086f1a5ac 100644 --- a/static/js/publisher/listing/components/CategoriesInput/CategoriesInput.tsx +++ b/static/js/publisher/listing/components/CategoriesInput/CategoriesInput.tsx @@ -33,7 +33,7 @@ function CategoriesInput({ : ""; const [showSecondCategoryField, setShowSecondCategoryField] = useState( - secondaryCategory ? true : false + secondaryCategory ? true : false, ); useEffect(() => { diff --git a/static/js/publisher/listing/components/ImageUpload/ImageUpload.tsx b/static/js/publisher/listing/components/ImageUpload/ImageUpload.tsx index f99831501d..5133cd0af7 100644 --- a/static/js/publisher/listing/components/ImageUpload/ImageUpload.tsx +++ b/static/js/publisher/listing/components/ImageUpload/ImageUpload.tsx @@ -79,7 +79,7 @@ function ImageUpload({ setImageValidationError( `${image.name} file size is over ${ validationSchema?.maxFileSize / 1000 - }KB` + }KB`, ); return; } @@ -97,7 +97,7 @@ function ImageUpload({ ) { setImageIsValid(false); setImageValidationError( - `${image.name} (${renderedImage.width} x ${renderedImage.height} pixels) does not have the correct aspect ratio: it needs to be ${validationSchema?.aspectRatio?.width}:${validationSchema?.aspectRatio?.height} (e.g. ${validationSchema?.minWidth} x ${validationSchema?.minHeight}pixels)` + `${image.name} (${renderedImage.width} x ${renderedImage.height} pixels) does not have the correct aspect ratio: it needs to be ${validationSchema?.aspectRatio?.width}:${validationSchema?.aspectRatio?.height} (e.g. ${validationSchema?.minWidth} x ${validationSchema?.minHeight}pixels)`, ); } else if ( !validateImageDimensions(renderedImage.width, renderedImage.height, { @@ -109,7 +109,7 @@ function ImageUpload({ ) { setImageIsValid(false); setImageValidationError( - `${image.name} has dimension ${renderedImage.width} x ${renderedImage.height} pixels. It needs to be at least ${validationSchema?.minWidth} x ${validationSchema?.minHeight} and at most ${validationSchema?.maxWidth} x ${validationSchema?.maxHeight} pixels.` + `${image.name} has dimension ${renderedImage.width} x ${renderedImage.height} pixels. It needs to be at least ${validationSchema?.minWidth} x ${validationSchema?.minHeight} and at most ${validationSchema?.maxWidth} x ${validationSchema?.maxHeight} pixels.`, ); } else { setImageIsValid(true); @@ -204,7 +204,7 @@ function ImageUpload({ onChange: ( e: SyntheticEvent & { target: HTMLInputElement; - } + }, ) => { if (e.target.files) { setImage(e.target.files[0]); @@ -243,7 +243,7 @@ function ImageUpload({ onChange={( e: SyntheticEvent & { target: HTMLInputElement; - } + }, ) => { if (e.target.checked) { setDarkThemeEnabled(true); diff --git a/static/js/publisher/listing/components/LicenseInputs/LicenseSearch.tsx b/static/js/publisher/listing/components/LicenseInputs/LicenseSearch.tsx index 7a5336ecf3..1e48e53b51 100644 --- a/static/js/publisher/listing/components/LicenseInputs/LicenseSearch.tsx +++ b/static/js/publisher/listing/components/LicenseInputs/LicenseSearch.tsx @@ -31,7 +31,7 @@ function LicenseSearch({ }: Props) { const [suggestions, setSuggestions] = useState([]); const [selectedLicenseKeys, setSelectedLicenseKeys] = useState( - license?.split(" OR ") || [] + license?.split(" OR ") || [], ); const [selectedLicenses, setSelectedLicenses] = useState< (License | undefined)[] @@ -49,7 +49,7 @@ function LicenseSearch({ setSelectedLicenses( selectedLicenseKeys .filter((key) => licenses.find((l) => l.key === key)) - .map((key) => licenses.find((l) => l.key === key)) + .map((key) => licenses.find((l) => l.key === key)), ); }, [selectedLicenseKeys]); @@ -108,7 +108,7 @@ function LicenseSearch({ onClick={() => { const newSelectedLicenses: (License | undefined)[] = selectedLicenses.filter( - (item) => item?.key !== selectedLicense?.key + (item) => item?.key !== selectedLicense?.key, ); const newSelectedLicenseKeys: (string | undefined)[] = @@ -134,7 +134,7 @@ function LicenseSearch({ ( e: SyntheticEvent & { target: HTMLInputElement; - } + }, ) => { const value = e?.target?.value.toLowerCase(); @@ -144,17 +144,17 @@ function LicenseSearch({ !selectedLicenseKeys.includes(item?.key) && item?.name.toLowerCase().startsWith(value) ); - }) + }), ); }, 200, - false + false, ), onFocus: () => { setSuggestions( licenses.filter((item) => { return !selectedLicenseKeys.includes(item?.key); - }) + }), ); }, onBlur: () => { diff --git a/static/js/publisher/listing/components/MetricsInputs/MetricsInputs.test.tsx b/static/js/publisher/listing/components/MetricsInputs/MetricsInputs.test.tsx index 62109fc9d0..69e6eba461 100644 --- a/static/js/publisher/listing/components/MetricsInputs/MetricsInputs.test.tsx +++ b/static/js/publisher/listing/components/MetricsInputs/MetricsInputs.test.tsx @@ -17,13 +17,13 @@ jest.mock("nanoid", () => { test("public metrics are not enabled", () => { render(); expect( - screen.getByRole("checkbox", { name: "Display public popularity charts" }) + screen.getByRole("checkbox", { name: "Display public popularity charts" }), ).not.toBeChecked(); }); test("world map field is disabled", () => { render( - + , ); expect(screen.getByRole("checkbox", { name: "World map" })).toBeDisabled(); }); @@ -31,7 +31,7 @@ test("world map field is disabled", () => { test("Linux distros field is disabled", () => { render(); expect( - screen.getByRole("checkbox", { name: "Linux distributions" }) + screen.getByRole("checkbox", { name: "Linux distributions" }), ).toBeDisabled(); }); @@ -40,7 +40,7 @@ test("world map field not to be checked if value is in blacklist", () => { + />, ); expect(screen.getByRole("checkbox", { name: "World map" })).not.toBeChecked(); }); @@ -48,7 +48,7 @@ test("world map field not to be checked if value is in blacklist", () => { test("Linux distributions field to be checked if value is not in blacklist", () => { render(); expect( - screen.getByRole("checkbox", { name: "Linux distributions" }) + screen.getByRole("checkbox", { name: "Linux distributions" }), ).toBeChecked(); }); @@ -59,9 +59,9 @@ test("Linux distributions field not to be checked if value is in blacklist", () defaultPublicMetricsBlacklist={[ "weekly_installed_base_by_operating_system_normalized", ]} - /> + />, ); expect( - screen.getByRole("checkbox", { name: "Linux distributions" }) + screen.getByRole("checkbox", { name: "Linux distributions" }), ).not.toBeChecked(); }); diff --git a/static/js/publisher/listing/components/MetricsInputs/MetricsInputs.tsx b/static/js/publisher/listing/components/MetricsInputs/MetricsInputs.tsx index 7f99d7f3dd..18c4fe8286 100644 --- a/static/js/publisher/listing/components/MetricsInputs/MetricsInputs.tsx +++ b/static/js/publisher/listing/components/MetricsInputs/MetricsInputs.tsx @@ -16,13 +16,13 @@ function MetricsInputs({ defaultPublicMetricsBlacklist, }: Props) { const [publicMetricsBlacklist, setPublicMetricsBlacklist] = useState( - defaultPublicMetricsBlacklist + defaultPublicMetricsBlacklist, ); const [publicMetricsDistros, setPublicMetricsDistros] = useState( publicMetricsBlacklist.includes( - "weekly_installed_base_by_operating_system_normalized" - ) + "weekly_installed_base_by_operating_system_normalized", + ), ); const displayPublicChartsInputId = nanoid(); @@ -31,36 +31,36 @@ function MetricsInputs({ const updatePublicMetricsBlacklist = ( fieldName: string, - isChecked: boolean + isChecked: boolean, ) => { const newPublicMetricsBlacklist: Array = Array.prototype.concat( - publicMetricsBlacklist + publicMetricsBlacklist, ); if (fieldName === "public_metrics_territories") { if ( !newPublicMetricsBlacklist.includes( - "installed_base_by_country_percent" + "installed_base_by_country_percent", ) && !isChecked ) { setPublicMetricsBlacklist( newPublicMetricsBlacklist.concat([ "installed_base_by_country_percent", - ]) + ]), ); } if ( newPublicMetricsBlacklist.includes( - "installed_base_by_country_percent" + "installed_base_by_country_percent", ) && isChecked ) { setPublicMetricsBlacklist( newPublicMetricsBlacklist.filter( - (d) => d !== "installed_base_by_country_percent" - ) + (d) => d !== "installed_base_by_country_percent", + ), ); } } @@ -68,27 +68,27 @@ function MetricsInputs({ if (fieldName === "public_metrics_distros") { if ( !newPublicMetricsBlacklist.includes( - "weekly_installed_base_by_operating_system_normalized" + "weekly_installed_base_by_operating_system_normalized", ) && !isChecked ) { setPublicMetricsBlacklist( newPublicMetricsBlacklist.concat([ "weekly_installed_base_by_operating_system_normalized", - ]) + ]), ); } if ( newPublicMetricsBlacklist.includes( - "weekly_installed_base_by_operating_system_normalized" + "weekly_installed_base_by_operating_system_normalized", ) && isChecked ) { setPublicMetricsBlacklist( newPublicMetricsBlacklist.filter( - (d) => d !== "weekly_installed_base_by_operating_system_normalized" - ) + (d) => d !== "weekly_installed_base_by_operating_system_normalized", + ), ); } } @@ -137,11 +137,11 @@ function MetricsInputs({ onChange: ( e: SyntheticEvent & { target: HTMLInputElement; - } + }, ) => { updatePublicMetricsBlacklist( e.target.name, - e.target.checked + e.target.checked, ); }, })} @@ -167,12 +167,12 @@ function MetricsInputs({ onChange: ( e: SyntheticEvent & { target: HTMLInputElement; - } + }, ) => { setPublicMetricsDistros(!e.target.checked); updatePublicMetricsBlacklist( e.target.name, - e.target.checked + e.target.checked, ); }, })} diff --git a/static/js/publisher/listing/components/PreviewForm/PreviewForm.tsx b/static/js/publisher/listing/components/PreviewForm/PreviewForm.tsx index e2aaafabe3..1bedd96fb7 100644 --- a/static/js/publisher/listing/components/PreviewForm/PreviewForm.tsx +++ b/static/js/publisher/listing/components/PreviewForm/PreviewForm.tsx @@ -30,7 +30,7 @@ function PreviewForm({ snapName, getValues }: Props) { website: getValues("websites").map((item: { url: string }) => item.url), contact: getValues("contacts").map((item: { url: string }) => item.url), donations: getValues("donations").map( - (item: { url: string }) => item.url + (item: { url: string }) => item.url, ), source: getValues("source-code").map((item: { url: string }) => item.url), issues: getValues("issues").map((item: { url: string }) => item.url), @@ -76,7 +76,7 @@ function PreviewForm({ snapName, getValues }: Props) { window.localStorage.setItem( `${snapName}-initial`, - JSON.stringify(listingData) + JSON.stringify(listingData), ); window.localStorage.setItem(snapName, JSON.stringify(listingData)); diff --git a/static/js/publisher/listing/components/PrimaryDomainInput/PrimaryDomainInput.tsx b/static/js/publisher/listing/components/PrimaryDomainInput/PrimaryDomainInput.tsx index 02e6669ebb..3d04a591b1 100644 --- a/static/js/publisher/listing/components/PrimaryDomainInput/PrimaryDomainInput.tsx +++ b/static/js/publisher/listing/components/PrimaryDomainInput/PrimaryDomainInput.tsx @@ -132,7 +132,7 @@ function PrimaryDomainInput({ Unable to verify{" "} {getHostname( - formState.defaultValues.primary_website + formState.defaultValues.primary_website, )} {" "} with a path diff --git a/static/js/publisher/listing/components/PrimaryDomainInput/__tests__/PrimaryDomainInput.test.tsx b/static/js/publisher/listing/components/PrimaryDomainInput/__tests__/PrimaryDomainInput.test.tsx index a9504c735c..91f68e6d1f 100644 --- a/static/js/publisher/listing/components/PrimaryDomainInput/__tests__/PrimaryDomainInput.test.tsx +++ b/static/js/publisher/listing/components/PrimaryDomainInput/__tests__/PrimaryDomainInput.test.tsx @@ -98,7 +98,7 @@ describe("PrimaryDomainInput", () => { expect(input).toHaveValue("https://example.comabc"); expect( - screen.getByText(/Please save your changes to verify/) + screen.getByText(/Please save your changes to verify/), ).toBeInTheDocument(); }); @@ -147,7 +147,7 @@ describe("PrimaryDomainInput", () => { renderComponent({ primary_website: "https://launchpad.net" }); await user.type( screen.getByRole("textbox", { name: "Primary website:" }), - "/path" + "/path", ); expect(screen.getByText(/Unable to verify/)).toBeInTheDocument(); }); @@ -169,13 +169,13 @@ describe("PrimaryDomainInput", () => { const user = userEvent.setup(); renderComponent({ primary_website: "https://example.com" }); await user.click( - screen.getByRole("button", { name: "Verified ownership" }) + screen.getByRole("button", { name: "Verified ownership" }), ); expect( - screen.getByRole("heading", { level: 2, name: "Verify ownership" }) + screen.getByRole("heading", { level: 2, name: "Verify ownership" }), ).toBeInTheDocument(); expect( - screen.getByRole("textbox", { name: "DNS verification token" }) + screen.getByRole("textbox", { name: "DNS verification token" }), ).toHaveValue("SNAPCRAFT_IO_VERIFICATION=abc123"); }); @@ -191,7 +191,7 @@ describe("PrimaryDomainInput", () => { renderComponent({ primary_website: "https://example.com" }); expect(screen.queryByText("Verified ownership")).not.toBeInTheDocument(); expect( - screen.getByRole("button", { name: "Verify ownership" }) + screen.getByRole("button", { name: "Verify ownership" }), ).toBeInTheDocument(); }); @@ -213,10 +213,10 @@ describe("PrimaryDomainInput", () => { renderComponent({ primary_website: "https://example.com" }); await user.click(screen.getByRole("button", { name: "Verify ownership" })); expect( - screen.getByRole("heading", { level: 2, name: "Verify ownership" }) + screen.getByRole("heading", { level: 2, name: "Verify ownership" }), ).toBeInTheDocument(); expect( - screen.getByRole("textbox", { name: "DNS verification token" }) + screen.getByRole("textbox", { name: "DNS verification token" }), ).toHaveValue("SNAPCRAFT_IO_VERIFICATION=abc123"); }); @@ -237,7 +237,7 @@ describe("PrimaryDomainInput", () => { const user = userEvent.setup(); renderComponent({ primary_website: "https://example.com" }); expect( - screen.getByRole("button", { name: "Verify ownership" }) + screen.getByRole("button", { name: "Verify ownership" }), ).toBeDisabled(); }); }); diff --git a/static/js/publisher/listing/components/Screenshots/Screenshot.tsx b/static/js/publisher/listing/components/Screenshots/Screenshot.tsx index 146a493f4d..daf01680b7 100644 --- a/static/js/publisher/listing/components/Screenshots/Screenshot.tsx +++ b/static/js/publisher/listing/components/Screenshots/Screenshot.tsx @@ -87,7 +87,7 @@ function Screenshot({ onChange: ( e: SyntheticEvent & { target: HTMLInputElement; - } + }, ) => { if (e.target.files) { setImage(e.target.files[0]); diff --git a/static/js/publisher/listing/components/Screenshots/Screenshots.tsx b/static/js/publisher/listing/components/Screenshots/Screenshots.tsx index 1f365ada76..83104d5109 100644 --- a/static/js/publisher/listing/components/Screenshots/Screenshots.tsx +++ b/static/js/publisher/listing/components/Screenshots/Screenshots.tsx @@ -49,7 +49,7 @@ function Screenshots({ register, control, getValues, setValue }: Props) { setImageValidationError( `${image.name} file size is over ${ validationSchema?.maxFileSize / 1000000 - }KB` + }KB`, ); return; } @@ -69,7 +69,7 @@ function Screenshots({ register, control, getValues, setValue }: Props) { ) { setImageIsValid(false); setImageValidationError( - `${image.name} has dimension ${renderedImage.width} x ${renderedImage.height} pixels. It needs to be at least ${validationSchema?.minWidth} x ${validationSchema?.minHeight} and at most ${validationSchema?.maxWidth} x ${validationSchema?.maxHeight} pixels.` + `${image.name} has dimension ${renderedImage.width} x ${renderedImage.height} pixels. It needs to be at least ${validationSchema?.minWidth} x ${validationSchema?.minHeight} and at most ${validationSchema?.maxWidth} x ${validationSchema?.maxHeight} pixels.`, ); } else { setImageIsValid(true); diff --git a/static/js/publisher/listing/index.tsx b/static/js/publisher/listing/index.tsx index 9ba21f37cb..838277f7e1 100644 --- a/static/js/publisher/listing/index.tsx +++ b/static/js/publisher/listing/index.tsx @@ -17,5 +17,5 @@ const root = createRoot(container as HTMLElement); root.render( - + , ); diff --git a/static/js/publisher/listing/sections/AdditionalInformationSection/__tests__/AdditionalInformationSection.test.tsx b/static/js/publisher/listing/sections/AdditionalInformationSection/__tests__/AdditionalInformationSection.test.tsx index da3b789823..8fdae9434c 100644 --- a/static/js/publisher/listing/sections/AdditionalInformationSection/__tests__/AdditionalInformationSection.test.tsx +++ b/static/js/publisher/listing/sections/AdditionalInformationSection/__tests__/AdditionalInformationSection.test.tsx @@ -21,7 +21,7 @@ const renderComponent = () => { licenses: [], }} getValues={jest.fn()} - /> + />, ); }; @@ -29,7 +29,7 @@ describe("AdditionalInformationSection", () => { test("shows the correct section", () => { renderComponent(); expect( - screen.getByRole("heading", { level: 2, name: "Additional information" }) + screen.getByRole("heading", { level: 2, name: "Additional information" }), ).toBeInTheDocument(); }); }); diff --git a/static/js/publisher/listing/utils/__tests__/shouldShowUpdateMetadataWarning.test.ts b/static/js/publisher/listing/utils/__tests__/shouldShowUpdateMetadataWarning.test.ts index 411ac83f96..e678dc8161 100644 --- a/static/js/publisher/listing/utils/__tests__/shouldShowUpdateMetadataWarning.test.ts +++ b/static/js/publisher/listing/utils/__tests__/shouldShowUpdateMetadataWarning.test.ts @@ -7,7 +7,7 @@ describe("shouldShowUpdateMetadataWarning", () => { banner_url: true, icon_url: true, screenshot_urls: true, - }) + }), ).toBe(false); }); diff --git a/static/js/publisher/listing/utils/getChanges.ts b/static/js/publisher/listing/utils/getChanges.ts index 7c958449fb..ff27785be6 100644 --- a/static/js/publisher/listing/utils/getChanges.ts +++ b/static/js/publisher/listing/utils/getChanges.ts @@ -2,7 +2,7 @@ const formatImageChanges = ( bannerUrl: [string], iconUrl: string, screenshotUrls: [string], - screenshots: [FileList] + screenshots: [FileList], ) => { const images = []; @@ -50,7 +50,7 @@ const formatImageChanges = ( function getChanges( dirtyFields: { [key: string]: any }, - data: { [key: string]: any } + data: { [key: string]: any }, ) { const changes: { [key: string]: any } = {}; const keys = Object.keys(dirtyFields); @@ -71,7 +71,7 @@ function getChanges( const combineWebsites = ( primaryWebsite: string, - websites: Array<{ url: string }> + websites: Array<{ url: string }>, ) => { return [{ url: primaryWebsite }].concat(websites); }; @@ -98,12 +98,12 @@ function getChanges( : [], website: data.websites ? removeEmptyUrls( - combineWebsites(data.primary_website, data.websites) + combineWebsites(data.primary_website, data.websites), ).map((url: { url: string }) => url.url) : [], source: data["source-code"] ? removeEmptyUrls(data["source-code"]).map( - (url: { url: string }) => url.url + (url: { url: string }) => url.url, ) : [], }; @@ -121,7 +121,7 @@ function getChanges( data?.banner_url, data?.icon_url, data?.screenshot_urls, - data?.screenshots + data?.screenshots, ); } diff --git a/static/js/publisher/listing/utils/getFormData.ts b/static/js/publisher/listing/utils/getFormData.ts index a64ee98703..f9cfa14556 100644 --- a/static/js/publisher/listing/utils/getFormData.ts +++ b/static/js/publisher/listing/utils/getFormData.ts @@ -13,7 +13,7 @@ const formatLinkFields = (fields: Array<{ url: string }>) => { function getFormData( data: { [key: string]: any }, snapId: string | undefined, - changes: { [key: string]: any } + changes: { [key: string]: any }, ) { const formData = new FormData(); @@ -75,7 +75,7 @@ function getFormData( issues: formatLinkFields(data?.issues), "source-code": formatLinkFields(data?.["source-code"]), website: formatLinkFields(data?.websites), - }) + }), ); } @@ -98,7 +98,7 @@ function getFormData( // update changes object const imageIndex = changes.images.findIndex( - (image: any) => image.name === oldName + (image: any) => image.name === oldName, ); changes.images[imageIndex].name = newFile.name; changes.images[imageIndex].url = URL.createObjectURL(newFile); diff --git a/static/js/publisher/listing/utils/getListingData.ts b/static/js/publisher/listing/utils/getListingData.ts index 1566241020..5d1d2989e0 100644 --- a/static/js/publisher/listing/utils/getListingData.ts +++ b/static/js/publisher/listing/utils/getListingData.ts @@ -52,7 +52,7 @@ function getListingData(listingData: { [key: string]: any }) { type: "screenshot", status: "uploaded", }; - }) + }), ); } @@ -73,10 +73,10 @@ function getListingData(listingData: { [key: string]: any }) { "primary-category": listingData?.snap_categories?.categories[0], "secondary-category": listingData?.snap_categories?.categories[1], public_metrics_territories: !listingData?.public_metrics_blacklist.includes( - "installed_base_by_country_percent" + "installed_base_by_country_percent", ), public_metrics_distros: !listingData?.public_metrics_blacklist.includes( - "weekly_installed_base_by_operating_system_normalized" + "weekly_installed_base_by_operating_system_normalized", ), update_metadata_on_release: listingData?.update_metadata_on_release, contacts: diff --git a/static/js/publisher/listing/utils/validateAspectRatio.ts b/static/js/publisher/listing/utils/validateAspectRatio.ts index a0a1375d85..60a1f58dc2 100644 --- a/static/js/publisher/listing/utils/validateAspectRatio.ts +++ b/static/js/publisher/listing/utils/validateAspectRatio.ts @@ -1,7 +1,7 @@ function validateAspectRatio( width: number, height: number, - ratio: { width: number; height: number } + ratio: { width: number; height: number }, ): boolean { const aspectRatio = ratio.width / ratio.height; const expectedHeight = width / aspectRatio; diff --git a/static/js/publisher/listing/utils/validateImageDimensions.ts b/static/js/publisher/listing/utils/validateImageDimensions.ts index 5a6a42ba7a..e9d3128a4d 100644 --- a/static/js/publisher/listing/utils/validateImageDimensions.ts +++ b/static/js/publisher/listing/utils/validateImageDimensions.ts @@ -6,7 +6,7 @@ function validateImageDimensions( maxWidth: number; minHeight: number; maxHeight: number; - } + }, ) { return ( imageWidth >= dimensions.minWidth && diff --git a/static/js/publisher/market/categories.ts b/static/js/publisher/market/categories.ts index 2a857a1451..be51c5c58c 100644 --- a/static/js/publisher/market/categories.ts +++ b/static/js/publisher/market/categories.ts @@ -1,5 +1,5 @@ function categories(form: any, state: any) { - let categoriesList = []; + const categoriesList = []; if ( form.elements["primary_category"] && form.elements["primary_category"].value !== "" @@ -19,26 +19,26 @@ function categories(form: any, state: any) { function initCategories() { const categoryHelpTextEl = document.querySelector( - ".js-categories-category1-help-text" + ".js-categories-category1-help-text", ) as HTMLElement; const categorySecondaryAddEl = document.querySelector( - ".js-categories-category2-add" + ".js-categories-category2-add", ) as HTMLElement; const categorySecondaryPickerEl = document.querySelector( - ".js-categories-category2-picker" + ".js-categories-category2-picker", ) as HTMLElement; const categorySecondaryAddLink = document.querySelector( - ".js-categories-category2-add-link" + ".js-categories-category2-add-link", ) as HTMLElement; const secondaryCategoryRemove = document.querySelector( - ".js-categories-category2-remove" + ".js-categories-category2-remove", ) as HTMLElement; const primaryCategorySelectEl = document.querySelector( - "[name='primary_category']" + "[name='primary_category']", ) as HTMLSelectElement; const secondaryCategorySelectEl = document.querySelector( - "[name='secondary_category']" + "[name='secondary_category']", ) as HTMLSelectElement; const setSecondaryOptions = () => { @@ -60,7 +60,7 @@ function initCategories() { const resetSecondaryCategory = () => { secondaryCategorySelectEl.value = ""; secondaryCategorySelectEl.dispatchEvent( - new Event("change", { bubbles: true }) + new Event("change", { bubbles: true }), ); }; diff --git a/static/js/publisher/market/lightbox.ts b/static/js/publisher/market/lightbox.ts index 1ab8af159e..c56c6666f6 100644 --- a/static/js/publisher/market/lightbox.ts +++ b/static/js/publisher/market/lightbox.ts @@ -60,7 +60,7 @@ const initLightboxEl = () => { const loadLightboxImage = ( lightboxEl: HTMLElement, url: string | undefined, - images: Array + images: Array, ) => { const contentEl = lightboxEl.querySelector(".vbox-content") as HTMLElement; @@ -85,15 +85,15 @@ const loadLightboxImage = ( contentEl.style.opacity = "1"; const originalEl = document.body.querySelector( - `[data-original="${url}"]` + `[data-original="${url}"]`, ); if (originalEl) { const webm = originalEl.querySelector( - "[type='video/webm']" + "[type='video/webm']", ) as HTMLMediaElement; const mp4 = originalEl.querySelector( - "[type='video/mp4']" + "[type='video/mp4']", ) as HTMLMediaElement; if (media.canPlayType("video/webm") && webm) { @@ -156,7 +156,7 @@ const loadLightboxImage = ( const openLightboxEl = ( lightboxEl: HTMLElement, url: string, - images: Array + images: Array, ) => { // prepare navigating to next/prev images if (images && images.length) { diff --git a/static/js/publisher/market/storageCommands.ts b/static/js/publisher/market/storageCommands.ts index e94aff77a9..ff9e5971c8 100644 --- a/static/js/publisher/market/storageCommands.ts +++ b/static/js/publisher/market/storageCommands.ts @@ -3,7 +3,7 @@ function storageCommands( formEl: HTMLFormElement, snap_name: string, ignoreChangesOnUnload: Function, - context = window + context = window, ) { const key = `${snap_name}-command`; if (e.key === key) { diff --git a/static/js/publisher/metrics/graphs/activeDevicesGraph/dataProcessing.ts b/static/js/publisher/metrics/graphs/activeDevicesGraph/dataProcessing.ts index b245b92ed9..8354cf9b4b 100644 --- a/static/js/publisher/metrics/graphs/activeDevicesGraph/dataProcessing.ts +++ b/static/js/publisher/metrics/graphs/activeDevicesGraph/dataProcessing.ts @@ -18,7 +18,7 @@ function prepareStackedData(this: any) { }; this.rawData.buckets.forEach((bucket: any, i: number) => { - let obj: { + const obj: { [key: string]: any; } = { date: utcParse("%Y-%m-%d")(bucket), @@ -30,7 +30,7 @@ function prepareStackedData(this: any) { if (_keys.indexOf(series.name) < 0) { _keys.push(series.name); } - } + }, ); _data.push(obj); @@ -74,7 +74,7 @@ function prepareLineData(this: any) { }); }); data.push(obj); - } + }, ); this.rawData.buckets.forEach((bucket: any, i: number) => { @@ -97,7 +97,7 @@ function prepareLineData(this: any) { (acc: Array, current: { values: Array }) => { return acc.concat(current.values); }, - [] + [], ); this.data = _data; @@ -141,7 +141,7 @@ function prepareAnnotationsData(this: any) { let annotationsData: Array; annotationsData = []; this.options.annotations.buckets.forEach((bucket: any, i: number) => { - let obj: { + const obj: { [key: string]: any; } = { date: utcParse("%Y-%m-%d")(bucket), @@ -153,7 +153,7 @@ function prepareAnnotationsData(this: any) { if (this.keys.indexOf(series.name) < 0) { this.keys.push(series.name); } - } + }, ); annotationsData.push(obj); @@ -168,7 +168,7 @@ function prepareAnnotationsData(this: any) { if (annotationsData) { annotationsData = annotationsData .map((annotation) => { - let x = this.xScale(annotation.date); + const x = this.xScale(annotation.date); if (x < 0 + this.padding.left) { return false; @@ -221,7 +221,7 @@ function prepareAxis(this: any) { this.xAxis = axisBottom(this.xScale).tickValues(tickValues).tickPadding(16); this.yAxis = axisLeft(this.yScale).tickFormat((d) => - d === 0 ? "0" : this.shortValue(d) + d === 0 ? "0" : this.shortValue(d), ); } diff --git a/static/js/publisher/metrics/graphs/activeDevicesGraph/index.ts b/static/js/publisher/metrics/graphs/activeDevicesGraph/index.ts index 20d0315f9f..0d7702b84b 100644 --- a/static/js/publisher/metrics/graphs/activeDevicesGraph/index.ts +++ b/static/js/publisher/metrics/graphs/activeDevicesGraph/index.ts @@ -37,7 +37,7 @@ class ActiveDevicesGraph { bottom: 30, left: 50, }, - options.margin || {} + options.margin || {}, ); this.padding = Object.assign( @@ -47,7 +47,7 @@ class ActiveDevicesGraph { bottom: 16, left: 16, }, - options.padding || {} + options.padding || {}, ); this.width; diff --git a/static/js/publisher/metrics/graphs/activeDevicesGraph/tooltips.ts b/static/js/publisher/metrics/graphs/activeDevicesGraph/tooltips.ts index 70a5c13767..c213f993dd 100644 --- a/static/js/publisher/metrics/graphs/activeDevicesGraph/tooltips.ts +++ b/static/js/publisher/metrics/graphs/activeDevicesGraph/tooltips.ts @@ -13,16 +13,16 @@ export function tooltips(this: any) { const tooltipTemplate = (dateData: { date: Date }, currentHoverKey: any) => { const tooltipRows = ( dateData: { [x: string]: any; date?: Date }, - currentHoverKey: string + currentHoverKey: string, ) => { - let dataArr: { + const dataArr: { skip?: boolean; key: any; value: any; count: any; }[] = []; let total = 0; - let other = { + const other = { key: "other", value: 0, count: 0, @@ -85,7 +85,7 @@ export function tooltips(this: any) { `${item.key}`, ``, `${commaValue( - item.value + item.value, )} (${total !== 0 ? ((item.value / total) * 100).toFixed(2) : 0}%)`, ``, ].join(""); @@ -100,7 +100,7 @@ export function tooltips(this: any) { `
    `, ``, `${tooltipTimeFormat( - dateData.date + dateData.date, )}`, tooltipRows(dateData, currentHoverKey), ``, diff --git a/static/js/publisher/metrics/metrics.ts b/static/js/publisher/metrics/metrics.ts index 8b0a17c81e..63fbc4b894 100644 --- a/static/js/publisher/metrics/metrics.ts +++ b/static/js/publisher/metrics/metrics.ts @@ -37,7 +37,7 @@ type Metrics = { }; function renderMetrics(metrics: Metrics) { - let activeDevices: { + const activeDevices: { series: Array; buckets: Array; } = { @@ -46,7 +46,7 @@ function renderMetrics(metrics: Metrics) { }; metrics.activeDevices.metrics.series.forEach((series) => { - let fullSeries = series.values.map((value) => { + const fullSeries = series.values.map((value) => { return value === null ? 0 : value; }); activeDevices.series.push({ @@ -64,7 +64,7 @@ function renderMetrics(metrics: Metrics) { graphType: metrics.activeDevices.type, defaultTrack: metrics.defaultTrack, annotations: metrics.activeDevices.annotations, - } + }, ) .render() // @ts-ignore @@ -77,7 +77,7 @@ function renderMetrics(metrics: Metrics) { categories.addEventListener("mouseover", (e) => { const target = e.target as HTMLElement; const annotationHover = target.closest( - `[data-js="annotation-hover"]` + `[data-js="annotation-hover"]`, ) as HTMLElement; if (annotationHover) { const category = annotationHover.dataset.id; @@ -88,7 +88,7 @@ function renderMetrics(metrics: Metrics) { categories.addEventListener("mouseout", (e) => { const target = e.target as HTMLElement; const annotationHover = target.closest( - `[data-js="annotation-hover"]` + `[data-js="annotation-hover"]`, ) as HTMLElement; if (annotationHover) { const category = annotationHover.dataset.id; @@ -120,11 +120,11 @@ function renderPublisherMetrics(options: { { stacked: false, area: false, - } + }, ); const loader = document.querySelector( - ".snapcraft-metrics__loader" + ".snapcraft-metrics__loader", ) as HTMLElement; function getSnapDevices(snapList: any) { @@ -156,10 +156,10 @@ function renderPublisherMetrics(options: { json.snaps.forEach( (snap: { series: Array; name: string }) => { const continuedDevices = snap.series.filter( - (singleSeries) => singleSeries.name === "continued" + (singleSeries) => singleSeries.name === "continued", )[0]; const newDevices = snap.series.filter( - (singleSeries) => singleSeries.name === "new" + (singleSeries) => singleSeries.name === "new", )[0]; let totalSeries: Array = []; @@ -168,12 +168,12 @@ function renderPublisherMetrics(options: { totalSeries = continuedDevices.values.map( (continuedValue, index) => { return continuedValue + newDevices.values[index]; - } + }, ); } else { console.log( "There is no information available for continued or new devices.", - snap.series + snap.series, ); } @@ -181,7 +181,7 @@ function renderPublisherMetrics(options: { name: snap.name, values: totalSeries, }); - } + }, ); resolve(snaps); @@ -191,7 +191,7 @@ function renderPublisherMetrics(options: { } const snaps_arr: Array<{ name: string; id: string }> = Object.keys( - options.snaps + options.snaps, ).map((key) => { return { name: key, @@ -225,7 +225,7 @@ function renderPublisherMetrics(options: { _graph.rawData.buckets.length === 0 ) { const dashboardMetrics = document.querySelector( - `[data-js="dashboard-metrics"]` + `[data-js="dashboard-metrics"]`, ) as HTMLElement; dashboardMetrics.classList.add("u-hide"); diff --git a/static/js/publisher/preview.ts b/static/js/publisher/preview.ts index 11e1eb5c80..12a4249efe 100644 --- a/static/js/publisher/preview.ts +++ b/static/js/publisher/preview.ts @@ -147,12 +147,12 @@ function screenshotsAndVideos(screenshots: any[], video: string) { videoSlide.setAttribute("data-video-url", videoDetails.url); videoSlide.setAttribute("data-video-id", videoDetails.id); const videoTemplate = document.querySelector( - `#video-${videoDetails.type}-template` + `#video-${videoDetails.type}-template`, ); if (!videoTemplate) { throw new Error("Video template not available"); } - let videoHTML = videoTemplate.innerHTML + const videoHTML = videoTemplate.innerHTML .split("${url}") .join(videoDetails.url) .split("${id}") @@ -234,7 +234,7 @@ function render(packageName: string) {

    Something went wrong. Please ensure you have permission to preview this snap.

`; const snapHeading = document.querySelector( - ".p-snap-heading" + ".p-snap-heading", ) as HTMLElement; if (snapHeading.parentNode) { @@ -243,7 +243,7 @@ function render(packageName: string) { const el = document.createElement("div"); el.innerHTML = notification; return el; - })() + })(), ); } return; @@ -280,7 +280,7 @@ function render(packageName: string) { // Screenshots are a bit more involved, so treat them separately const screenshotsEl = document.querySelector( - `[data-live="screenshots"]` + `[data-live="screenshots"]`, ) as HTMLElement; if ( transformedState.video_urls !== "" || @@ -291,8 +291,8 @@ function render(packageName: string) { screenshotsEl, screenshotsAndVideos( transformedState.screenshot_urls, - transformedState.video_urls - ) + transformedState.video_urls, + ), ); hideMap.screenshots(screenshotsEl).classList.remove("u-hide"); terminateScreenshots("#js-snap-screenshots"); @@ -308,10 +308,10 @@ function render(packageName: string) { const metricsEl = document.querySelector(`[data-live="public_metrics_live"]`); if (metricsEl) { const mapEl = metricsEl.querySelector( - `[data-live="installed_base_by_country_percent"]` + `[data-live="installed_base_by_country_percent"]`, ); const distroEl = metricsEl.querySelector( - `[data-live="weekly_installed_base_by_operating_system_normalized"]` + `[data-live="weekly_installed_base_by_operating_system_normalized"]`, ); if (transformedState.public_metrics_enabled) { @@ -323,7 +323,7 @@ function render(packageName: string) { if (mapEl) { if ( transformedState.public_metrics_blacklist.indexOf( - "installed_base_by_country_percent" + "installed_base_by_country_percent", ) > -1 ) { mapEl.classList.add("u-hide"); @@ -335,7 +335,7 @@ function render(packageName: string) { if (distroEl) { if ( transformedState.public_metrics_blacklist.indexOf( - "weekly_installed_base_by_operating_system_normalized" + "weekly_installed_base_by_operating_system_normalized", ) > -1 ) { distroEl.classList.add("u-hide"); @@ -347,7 +347,7 @@ function render(packageName: string) { // Remove the notification that you can edit the snap const snapOwnerNotification = document.querySelector( - ".js-snap-owner-notification" + ".js-snap-owner-notification", ); if (snapOwnerNotification) { snapOwnerNotification.classList.add("u-hide"); @@ -356,7 +356,7 @@ function render(packageName: string) { function lostConnection(disableButtons: any) { const previewMessageEl = document.querySelector( - "#preview-message" + "#preview-message", ) as HTMLElement; previewMessageEl.innerHTML = ` This is taking longer then usual. You can click "Edit" to return to the form.`; disableButtons(); @@ -364,10 +364,10 @@ function lostConnection(disableButtons: any) { function establishedConnection( packageName: any, - enableButtons: { (): void; (): void; (): void } + enableButtons: { (): void; (): void; (): void }, ) { const previewMessageEl = document.querySelector( - "#preview-message" + "#preview-message", ) as HTMLElement; previewMessageEl.innerHTML = `You are previewing the listing page for ${packageName}`; enableButtons(); @@ -378,8 +378,8 @@ function establishedConnection( * @param packageName */ function preview(packageName: string) { - let packageNameInitial = window.localStorage.getItem( - `${packageName}-initial` + const packageNameInitial = window.localStorage.getItem( + `${packageName}-initial`, ); let initialState = packageNameInitial !== null ? JSON.parse(packageNameInitial) : ""; diff --git a/static/js/publisher/publicise.ts b/static/js/publisher/publicise.ts index 85b403eb5d..89cce8087f 100644 --- a/static/js/publisher/publicise.ts +++ b/static/js/publisher/publicise.ts @@ -7,7 +7,7 @@ function initSnapButtonsPicker() { const open = document.querySelector("#" + language + "_content"); const notHidden = document.querySelector( - ".js-language-content:not(.u-hide)" + ".js-language-content:not(.u-hide)", ); if (notHidden) { notHidden.classList.add("u-hide"); @@ -18,7 +18,7 @@ function initSnapButtonsPicker() { } let checked = document.querySelector( - "[name='language']:checked" + "[name='language']:checked", ) as HTMLInputElement; if (!checked) { @@ -42,7 +42,7 @@ function initSnapButtonsPicker() { const getCardPath = (snapName: any, options: { [key: string]: any } = {}) => { const path = `/${snapName}/embedded`; - let params: Array = []; + const params: Array = []; let paramsString: string = ""; if (options.button) { @@ -71,7 +71,7 @@ const getCardPath = (snapName: any, options: { [key: string]: any } = {}) => { const getCardEmbedHTML = (snapName: any, options: { [key: string]: any }) => { return `<iframe src="https://snapcraft.io${getCardPath( snapName, - options + options, )}" frameborder="0" width="100%" height="${ options.frameHeight }px" style="border: 1px solid #CCC; border-radius: 2px;"></iframe>`; @@ -82,7 +82,7 @@ const getCurrentFormState = (buttonRadios: any[], optionButtons: any[]) => { const state: { [key: string]: any } = {}; // get state of store button radio - let checked = buttonRadios.filter((b) => b.checked); + const checked = buttonRadios.filter((b) => b.checked); if (checked.length > 0) { state.button = checked[0].value; } @@ -105,7 +105,7 @@ function initEmbeddedCardPicker(options: { }) { const { snapName, previewFrame, codeElement } = options; const buttonRadios: Array = [].slice.call( - options.buttonRadios + options.buttonRadios, ); const optionButtons = [].slice.call(options.optionButtons); @@ -166,7 +166,7 @@ function initEmbeddedCardPicker(options: { if (previewFrame.offsetParent && previewFrame.contentWindow.document.body) { const height = Math.floor( - (previewFrame.contentWindow.document.body.clientHeight + 20) / 10 + (previewFrame.contentWindow.document.body.clientHeight + 20) / 10, ) * 10; if (height !== state.frameHeight) { @@ -199,7 +199,7 @@ const getBadgePath = ( snapName: any, badgeName = "badge", showName = true, - isPreview = false + isPreview = false, ) => { const params = []; if (!showName) { @@ -218,14 +218,14 @@ const getBadgePath = ( const getBadgePreview = ( snapName: any, badgeName: string | undefined, - showName: boolean | undefined + showName: boolean | undefined, ) => { return ` ${snapName} `; }; @@ -233,13 +233,13 @@ const getBadgePreview = ( const getBadgeHTML = ( snapName: any, badgeName: string | undefined, - showName: boolean | undefined + showName: boolean | undefined, ) => { return `<a href="https://snapcraft.io/${snapName}"> <img alt="${snapName}" src="https://snapcraft.io${getBadgePath( snapName, badgeName, - showName + showName, )}" /> </a>`; }; @@ -247,12 +247,12 @@ const getBadgeHTML = ( const getBadgeMarkdown = ( snapName: any, badgeName: string | undefined, - showName: boolean | undefined + showName: boolean | undefined, ) => { return `[![${snapName}](https://snapcraft.io${getBadgePath( snapName, badgeName, - showName + showName, )})](https://snapcraft.io/${snapName})`; }; diff --git a/static/js/publisher/release.tsx b/static/js/publisher/release.tsx index 90ce63db91..7d9fc1bf82 100644 --- a/static/js/publisher/release.tsx +++ b/static/js/publisher/release.tsx @@ -6,7 +6,7 @@ import { Provider } from "react-redux"; import { DndProvider } from "react-dnd"; import ReleasesController from "./release/releasesController"; import releases from "./release/reducers"; -import { ReleasesData, ChannelMap, Track, Options } from "./types/releaseTypes" +import { ReleasesData, ChannelMap, Track, Options } from "./types/releaseTypes"; // setup redux store with thunk middleware and devtools extension: // https://github.com/zalmoxisus/redux-devtools-extension#12-advanced-store-setup @@ -32,7 +32,7 @@ const initReleases = ( tracks, }, }, - composeEnhancers(applyMiddleware(thunk)) + composeEnhancers(applyMiddleware(thunk)), ); const container = document.querySelector(id); if (!container) throw new Error(`Container with id ${id} not found`); @@ -48,7 +48,7 @@ const initReleases = ( options={options} /> - + , ); }; diff --git a/static/js/publisher/release/actions/releases.ts b/static/js/publisher/release/actions/releases.ts index 822213f351..24d76deb29 100644 --- a/static/js/publisher/release/actions/releases.ts +++ b/static/js/publisher/release/actions/releases.ts @@ -28,7 +28,7 @@ function updateReleasesData(releasesData: { revisions: any; releases: any }) { | { releases: any } | { revisions: any } | { architectures: any[] }; - }) => void + }) => void, ) => { // init channel data in revisions list const revisionsMap = getRevisionsMap(releasesData.revisions); @@ -53,8 +53,8 @@ export function handleCloseResponse(dispatch: any, json: any, channels: any) { }); } } else { - let error = new Error( - `Error while closing channels: ${channels.join(", ")}.` + const error = new Error( + `Error while closing channels: ${channels.join(", ")}.`, ); // @ts-ignore error.json = json; @@ -93,7 +93,7 @@ export function handleReleaseResponse( dispatch: any, json: any, release: any, - revisions: any + revisions: any, ) { if (json.success) { // Update channel map based on the response @@ -122,10 +122,10 @@ export function handleReleaseResponse( }; } - let channel = `${trackKey}/${map.channel}`; + const channel = `${trackKey}/${map.channel}`; dispatch(releaseRevisionSuccess(revision, channel)); } - } + }, ); }); }); @@ -167,7 +167,7 @@ export function releaseRevisions() { pendingCloses: any; revisions: any; options: any; - } + }, ) => { const { pendingReleases, pendingCloses, revisions, options } = getState(); const { csrfToken, snapName } = options; @@ -191,7 +191,7 @@ export function releaseRevisions() { } else { const releaseIndex = regularReleases.findIndex( (release: { revision: { revision: number } }) => - release.revision.revision === parseInt(revId) + release.revision.revision === parseInt(revId), ); if (releaseIndex === -1) { regularReleases.push(mapToRelease(pendingRelease)); @@ -206,7 +206,7 @@ export function releaseRevisions() { const _handleReleaseResponse = ( json: { success: any; channel_map_tree: any; errors?: any }, - release: { id: number; revision: number; channels: string[] }[] + release: { id: number; revision: number; channels: string[] }[], ) => { return handleReleaseResponse(dispatch, json, release, revisions); }; @@ -223,7 +223,7 @@ export function releaseRevisions() { dispatch(hideNotification()); return fetchReleases(_handleReleaseResponse, releases, csrfToken, snapName) .then(() => - fetchCloses(_handleCloseResponse, csrfToken, snapName, pendingCloses) + fetchCloses(_handleCloseResponse, csrfToken, snapName, pendingCloses), ) .then(() => fetchReleasesHistory(csrfToken, snapName)) .then((json) => dispatch(updateReleasesData(json))) @@ -233,8 +233,8 @@ export function releaseRevisions() { status: "error", appearance: "negative", content: getErrorMessage(error), - }) - ) + }), + ), ) .then(() => dispatch(cancelPendingReleases())) .then(() => dispatch(closeHistory())); diff --git a/static/js/publisher/release/components/releasesConfirm.tsx b/static/js/publisher/release/components/releasesConfirm.tsx index 3b5a666096..26482af010 100644 --- a/static/js/publisher/release/components/releasesConfirm.tsx +++ b/static/js/publisher/release/components/releasesConfirm.tsx @@ -51,7 +51,7 @@ class ReleasesConfirm extends Component { const topPosition = top + scrollX; this.stickyBar.current.classList.toggle("is-pinned", topPosition === 0); - }, 500) + }, 500), ); } @@ -90,7 +90,7 @@ class ReleasesConfirm extends Component { toggleDetails() { this.props.triggerGAEvent( - `click-${this.state.showDetails ? "hide" : "show"}-details` + `click-${this.state.showDetails ? "hide" : "show"}-details`, ); this.setState({ showDetails: !this.state.showDetails, diff --git a/static/js/publisher/release/components/releasesTable/channelHeading.tsx b/static/js/publisher/release/components/releasesTable/channelHeading.tsx index 4135b23882..5d038d3bd2 100644 --- a/static/js/publisher/release/components/releasesTable/channelHeading.tsx +++ b/static/js/publisher/release/components/releasesTable/channelHeading.tsx @@ -46,7 +46,7 @@ const disabledBecauseNotSelected = "Select some revisions to promote them."; // TODO: move to selectors or helpers? const compareRevisionsPerArch = ( currentRevisionsByArch: { [x: string]: { revision: any } }, - targetRevisionsByArch: { [x: string]: { revision: any } } + targetRevisionsByArch: { [x: string]: { revision: any } }, ) => { if (currentRevisionsByArch) { return Object.keys(currentRevisionsByArch).every((arch) => { @@ -192,7 +192,7 @@ const ReleasesTableChannelHeading = (props: { if ( compareRevisionsPerArch( rowRevisions, - pendingChannelMap[targetChannel.channel] + pendingChannelMap[targetChannel.channel], ) ) { targetChannel.isDisabled = true; @@ -206,7 +206,7 @@ const ReleasesTableChannelHeading = (props: { // order the channel names // @ts-ignore const channelOrder = sortChannels( - targetChannels.map((channel) => channel.channel) + targetChannels.map((channel) => channel.channel), ).list; // remap targetchannels to this new order @@ -221,12 +221,12 @@ const ReleasesTableChannelHeading = (props: { let hasSameVersion = false; let channelVersion = ""; - let versionsMap: any = {}; + const versionsMap: any = {}; let isLaunchpadBuild = false; let channelBuild = ""; let channelBuildDate = null; - let buildMap: any = {}; + const buildMap: any = {}; if (rowRevisions) { // calculate map of architectures for each version @@ -262,7 +262,7 @@ const ReleasesTableChannelHeading = (props: { channelBuildDate = Object.values(revisions)[0].attributes["build-request-timestamp"] && new Date( - Object.values(revisions)[0].attributes["build-request-timestamp"] + Object.values(revisions)[0].attributes["build-request-timestamp"], ); } } @@ -286,7 +286,7 @@ const ReleasesTableChannelHeading = (props: { Object.values(rowRevisions).forEach( (revision: any) => canBeReleased(revision) && - props.promoteRevision(revision, targetChannel) + props.promoteRevision(revision, targetChannel), ); }; @@ -404,5 +404,5 @@ const mapDispatchToProps = (dispatch: any) => { export default connect( mapStateToProps, - mapDispatchToProps + mapDispatchToProps, )(ReleasesTableChannelHeading); diff --git a/static/js/publisher/release/components/releasesTable/droppableRow.tsx b/static/js/publisher/release/components/releasesTable/droppableRow.tsx index 6a60a40772..311f302a59 100644 --- a/static/js/publisher/release/components/releasesTable/droppableRow.tsx +++ b/static/js/publisher/release/components/releasesTable/droppableRow.tsx @@ -16,7 +16,7 @@ import ReleasesTableChannelRow from "./channelRow"; const getRevisionsToDrop = ( revisions: any[], targetChannel: string, - channelMap: { [x: string]: any } + channelMap: { [x: string]: any }, ) => { const targetChannelArchs = channelMap[targetChannel]; @@ -65,14 +65,14 @@ const ReleasesTableDroppableRow = (props: { drop: (item: any) => { item.revisions.forEach( (r: { status: string }) => - canBeReleased(r) && promoteRevision(r, channel) + canBeReleased(r) && promoteRevision(r, channel), ); if (item.revisions.length > 1) { triggerGAEvent( "drop-channel", `${currentTrack}/${item.risk}/${item.branch ? item.branch : null}`, - `${currentTrack}/${risk}/${branchName}` + `${currentTrack}/${risk}/${branchName}`, ); } else { triggerGAEvent( @@ -80,7 +80,7 @@ const ReleasesTableDroppableRow = (props: { `${currentTrack}/${item.risk}/${item.branch ? item.branch : null}/${ item.architectures[0] }`, - `${currentTrack}/${risk}/${branchName}/${item.architectures[0]}` + `${currentTrack}/${risk}/${branchName}/${item.architectures[0]}`, ); } }, @@ -92,7 +92,7 @@ const ReleasesTableDroppableRow = (props: { const draggedChannel = getChannelName( currentTrack, item.risk, - item.branch + item.branch, ); const dropChannel = getChannelName(currentTrack, risk, branchName); @@ -149,7 +149,7 @@ const ReleasesTableDroppableRow = (props: { const currentVersions: any[] = []; if (versions) { - for (let [value] of Object.entries(versions)) { + for (const [value] of Object.entries(versions)) { if (value && !currentVersions.includes(versions[value].version)) { currentVersions.push(versions[value].version); } @@ -228,5 +228,5 @@ const mapDispatchToProps = (dispatch: any) => { export default connect( mapStateToProps, - mapDispatchToProps + mapDispatchToProps, )(ReleasesTableDroppableRow); diff --git a/static/js/publisher/release/helpers.ts b/static/js/publisher/release/helpers.ts index 25a1a3a4b4..e63ad13236 100644 --- a/static/js/publisher/release/helpers.ts +++ b/static/js/publisher/release/helpers.ts @@ -9,7 +9,7 @@ export function isInDevmode(revision: any) { export function getChannelName( track: string, risk: string, - branch?: string | undefined + branch?: string | undefined, ) { if (risk === AVAILABLE) { return AVAILABLE; @@ -49,7 +49,7 @@ export function getRevisionsArchitectures(revisions: any[]) { export function isSameVersion(revisions: { version: string }[]) { let hasSameVersion = false; - let versionsMap: any = {}; + const versionsMap: any = {}; if (revisions) { // calculate map of architectures for each version @@ -105,9 +105,13 @@ export function resizeAsidePanel(panelType: string) { let asidePanel; if (panelType === "add") { - asidePanel = document.querySelector("#add-track-aside-panel") as HTMLElement; + asidePanel = document.querySelector( + "#add-track-aside-panel", + ) as HTMLElement; } else { - asidePanel = document.querySelector("#request-track-aside-panel") as HTMLElement; + asidePanel = document.querySelector( + "#request-track-aside-panel", + ) as HTMLElement; } if (targetComponent && asidePanel) { @@ -172,6 +176,6 @@ export async function hasTrackGuardrails(snap: string) { const data = await response.json(); return { "track-guardrails": data.data["track-guardrails"] }; } catch (e) { - return { "error": e }; + return { error: e }; } } diff --git a/static/js/publisher/release/reducers/pendingReleases.ts b/static/js/publisher/release/reducers/pendingReleases.ts index 1fb6c339eb..f90f945b48 100644 --- a/static/js/publisher/release/reducers/pendingReleases.ts +++ b/static/js/publisher/release/reducers/pendingReleases.ts @@ -16,7 +16,7 @@ import { jsonClone } from "../helpers"; function removePendingRelease( state: any, revision: { revision: string | number }, - channel: string | number + channel: string | number, ) { const newState = jsonClone(state); if (newState[revision.revision]) { @@ -37,7 +37,7 @@ function releaseRevision( revision: { architectures: any[]; revision: number }, channel: string, progressive: null, - previousRevisions: undefined + previousRevisions: undefined, ) { state = { ...state }; @@ -55,7 +55,7 @@ function releaseRevision( state = removePendingRelease( state, pendingRelease[channel].revision, - channel + channel, ); } }); @@ -85,14 +85,14 @@ function releaseRevision( function closeChannel( state: { [s: string]: unknown } | ArrayLike, - channel: string | number + channel: string | number, ) { Object.values(state).forEach((pendingRelease: any) => { if (pendingRelease[channel]) { state = removePendingRelease( state, pendingRelease[channel].revision, - channel + channel, ); } }); @@ -102,7 +102,7 @@ function closeChannel( function setProgressiveRelease( state: any, - progressive: { percentage: number } + progressive: { percentage: number }, ) { const nextState = jsonClone(state); @@ -127,7 +127,7 @@ function setProgressiveRelease( function updateProgressiveRelease( state: any, - progressive: { percentage: any } + progressive: { percentage: any }, ) { const nextState = jsonClone(state); @@ -176,7 +176,7 @@ function resumeProgressiveRelease(state: any) { // same key are not affected. function cancelProgressiveRelease( state: any, - previousRevision: { revision: any; architectures?: any[] } + previousRevision: { revision: any; architectures?: any[] }, ) { let nextState = jsonClone(state); @@ -216,7 +216,7 @@ function cancelProgressiveRelease( // to prevent duplication of revison data export default function pendingReleases( state = {}, - action: { type?: any; payload?: any } + action: { type?: any; payload?: any }, ) { switch (action.type) { case RELEASE_REVISION: @@ -225,13 +225,13 @@ export default function pendingReleases( action.payload.revision, action.payload.channel, action.payload.progressive, - action.payload.previousRevisions + action.payload.previousRevisions, ); case UNDO_RELEASE: return removePendingRelease( state, action.payload.revision, - action.payload.channel + action.payload.channel, ); case CANCEL_PENDING_RELEASES: return {}; diff --git a/static/js/publisher/release/selectors/index.ts b/static/js/publisher/release/selectors/index.ts index bdb93ce080..3526defe66 100644 --- a/static/js/publisher/release/selectors/index.ts +++ b/static/js/publisher/release/selectors/index.ts @@ -52,7 +52,7 @@ export function getFilteredReleaseHistory( pendingReleases: any; revisions: any; releases: any[]; - }> + }>, ) { const releases = state.releases; const revisions = state.revisions; @@ -115,11 +115,11 @@ export function getSelectedRevisions( pendingReleases: any; revisions: any; releases: any[]; - }> + }>, ) { if (state.channelMap[AVAILABLE]) { return Object.values(state.channelMap[AVAILABLE]).map( - (revision: any) => revision.revision + (revision: any) => revision.revision, ); } @@ -144,7 +144,7 @@ export function getSelectedRevision( revisions: any; releases: any[]; }>, - arch: string + arch: string, ) { if (state.channelMap[AVAILABLE]) { return state.channelMap[AVAILABLE][arch]; @@ -168,7 +168,7 @@ export function getSelectedArchitectures( pendingReleases: any; revisions: any; releases: any[]; - }> + }>, ) { if (state.channelMap[AVAILABLE]) { return Object.keys(state.channelMap[AVAILABLE]); @@ -194,7 +194,7 @@ export function hasDevmodeRevisions( pendingReleases: any; revisions: any; releases: any[]; - }> + }>, ) { return Object.values(state.channelMap).some((archReleases: any) => { return Object.values(archReleases).some(isInDevmode); @@ -239,7 +239,7 @@ export function getUnreleasedRevisions(state: { revisions: ArrayLike | { [s: string]: unknown }; }) { return getAllRevisions(state).filter( - (revision: any) => !revision.channels || revision.channels.length === 0 + (revision: any) => !revision.channels || revision.channels.length === 0, ); } @@ -249,7 +249,7 @@ export function getRecentRevisions(state: { }) { const interval = 1000 * 60 * 60 * 24 * 7; // 7 days return getUnreleasedRevisions(state).filter( - (r: any) => Date.now() - new Date(r.created_at).getTime() < interval + (r: any) => Date.now() - new Date(r.created_at).getTime() < interval, ); } @@ -262,7 +262,7 @@ export function getAvailableRevisionsBySelection( | { [s: string]: unknown } | { [s: string]: unknown }; }, - value: any + value: any, ) { switch (value) { case AVAILABLE_REVISIONS_SELECT_RECENT: @@ -293,7 +293,7 @@ export function getFilteredAvailableRevisions( pendingReleases: any; revisions: any; releases: any[]; - }> + }>, ) { const { availableRevisionsSelect } = state; return getAvailableRevisionsBySelection(state, availableRevisionsSelect); @@ -318,10 +318,10 @@ export function getFilteredAvailableRevisionsForArch( revisions: any; releases: any[]; }>, - arch: string + arch: string, ) { return getFilteredAvailableRevisions(state).filter((revision: any) => - revision.architectures.includes(arch) + revision.architectures.includes(arch), ); } @@ -374,7 +374,7 @@ export function getTracks(state: { } export function getBranches(state: { currentTrack: any; releases: any }) { - let branches: Array = []; + const branches: Array = []; const { currentTrack, releases } = state; const now = Date.now(); @@ -397,7 +397,7 @@ export function getBranches(state: { currentTrack: any; releases: any }) { const exists = branches.filter( (b: any) => - b.track === track && b.risk === risk && b.branch === branch + b.track === track && b.risk === risk && b.branch === branch, ).length > 0; if (!exists) { @@ -410,7 +410,7 @@ export function getBranches(state: { currentTrack: any; releases: any }) { expiration: item["expiration-date"], }); } - } + }, ); return branches @@ -426,10 +426,10 @@ export function hasPendingRelease(state: any, channel: string, arch: string) { const pendingChannelMap = getPendingChannelMap(state); // current revision to show (released or pending) - let currentRevision = + const currentRevision = pendingChannelMap[channel] && pendingChannelMap[channel][arch]; // already released revision - let releasedRevision = channelMap[channel] && channelMap[channel][arch]; + const releasedRevision = channelMap[channel] && channelMap[channel][arch]; // check if there is a pending release in this cell return ( @@ -445,10 +445,10 @@ export function getTrackRevisions( }: { channelMap: any; }, - track: string + track: string, ) { const trackKeys = Object.keys(channelMap).filter( - (trackName) => trackName.indexOf(track) == 0 + (trackName) => trackName.indexOf(track) == 0, ); return trackKeys.map((trackName) => channelMap[trackName]); } @@ -570,10 +570,10 @@ export function getRevisionsFromBuild( pendingReleases?: any; releases?: any; }, - buildId: string + buildId: string, ) { return getAllRevisions(state).filter( - (revision: any) => getBuildId(revision) === buildId + (revision: any) => getBuildId(revision) === buildId, ); } @@ -586,7 +586,7 @@ export function getProgressiveState( state: any, channel: string, arch: string, - isPending: undefined + isPending: undefined, ) { if (!isProgressiveReleaseEnabled(state)) { return [null, null, null]; @@ -599,7 +599,7 @@ export function getProgressiveState( const allReleases = releases.filter( (item: { architecture: string }) => - channel === getChannelString(item) && arch === item.architecture + channel === getChannelString(item) && arch === item.architecture, ); const release = allReleases[0]; @@ -659,13 +659,13 @@ export function hasRelease( releases: any[]; }>, channel: string, - architecture: string + architecture: string, ) { const { releases } = state; const filteredReleases = releases.filter( (release) => release.architecture === architecture && - getChannelString(release) === channel + getChannelString(release) === channel, ); return filteredReleases && @@ -692,7 +692,7 @@ export function getSeparatePendingReleases( pendingReleases: any; revisions: any; releases: any[]; - }> + }>, ) { const { pendingReleases } = state; const isProgressiveEnabled = isProgressiveReleaseEnabled(state); @@ -783,7 +783,7 @@ export function getPendingRelease( releases: any; }, channel: string, - arch: string + arch: string, ) { // for each release return Object.keys(pendingReleases).map((releasedRevision) => { @@ -806,10 +806,10 @@ export function getReleases( releases, }: { releases: never[] | { architecture: string; channel: string }[] }, archs: string | any[], - channel: string + channel: string, ) { return releases.filter( (release: any) => - archs.includes(release.architecture) && release.channel === channel + archs.includes(release.architecture) && release.channel === channel, ); } diff --git a/static/js/publisher/settings/components/App/__tests__/UnregisterSnapModal.test.tsx b/static/js/publisher/settings/components/App/__tests__/UnregisterSnapModal.test.tsx index 8248cbb572..08085fee9d 100644 --- a/static/js/publisher/settings/components/App/__tests__/UnregisterSnapModal.test.tsx +++ b/static/js/publisher/settings/components/App/__tests__/UnregisterSnapModal.test.tsx @@ -1,27 +1,29 @@ -import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from "@testing-library/user-event"; -import '@testing-library/jest-dom'; -import { UnregisterSnapModal } from '../../UnregisterSnapModal'; +import React from "react"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import "@testing-library/jest-dom"; +import { UnregisterSnapModal } from "../../UnregisterSnapModal"; // Mock the global fetch function -global.fetch = jest.fn(() => Promise.resolve({ - ok: true, - json: () => Promise.resolve({}), -})) as jest.Mock; +global.fetch = jest.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({}), + }), +) as jest.Mock; const mockSetUnregisterModalOpen = jest.fn(); const mockSetUnregisterError = jest.fn(); const mockSetUnregisterErrorMessage = jest.fn(); const defaultProps = { - snapName: 'test-snap', + snapName: "test-snap", setUnregisterModalOpen: mockSetUnregisterModalOpen, setUnregisterError: mockSetUnregisterError, setUnregisterErrorMessage: mockSetUnregisterErrorMessage, }; -describe('UnregisterSnapModal', () => { +describe("UnregisterSnapModal", () => { beforeEach(() => { (global.fetch as jest.Mock).mockClear(); mockSetUnregisterModalOpen.mockClear(); @@ -29,69 +31,78 @@ describe('UnregisterSnapModal', () => { mockSetUnregisterErrorMessage.mockClear(); }); - test('renders the modal with the correct snap name', () => { + test("renders the modal with the correct snap name", () => { render(); expect(screen.getByText('Unregister "test-snap"')).toBeInTheDocument(); }); - test('closes the modal when Cancel button is clicked', async () => { + test("closes the modal when Cancel button is clicked", async () => { const user = userEvent.setup(); render(); - await user.click(screen.getByText('Cancel')); + await user.click(screen.getByText("Cancel")); expect(mockSetUnregisterModalOpen).toHaveBeenCalledWith(false); }); - test('disables the Unregister button and shows spinner when clicked', async () => { + test("disables the Unregister button and shows spinner when clicked", async () => { const user = userEvent.setup(); render(); - const unregisterButton = screen.getByText('Unregister'); + const unregisterButton = screen.getByText("Unregister"); await user.click(unregisterButton); - expect(unregisterButton).toBeDisabled(); - expect(screen.getByText('Unregistering...')).toBeInTheDocument(); + expect(unregisterButton).toHaveAttribute("aria-disabled", "true"); + expect(screen.getByText("Unregistering...")).toBeInTheDocument(); }); - test('calls fetch with correct parameters and redirects on success', async () => { + test("calls fetch with correct parameters and redirects on success", async () => { const user = userEvent.setup(); - Object.defineProperty(window, 'location', { - value: { href: '' }, + Object.defineProperty(window, "location", { + value: { href: "" }, writable: true, }); render(); - await user.click(screen.getByText('Unregister')); + await user.click(screen.getByText("Unregister")); await waitFor(() => { - expect(global.fetch).toHaveBeenCalledWith('/packages/test-snap', expect.objectContaining({ - method: 'DELETE', - headers: { - 'X-CSRFToken': window['CSRF_TOKEN'], - }, - })); - expect(window.location.href).toBe('/snaps'); + expect(global.fetch).toHaveBeenCalledWith( + "/packages/test-snap", + expect.objectContaining({ + method: "DELETE", + headers: { + "X-CSRFToken": window["CSRF_TOKEN"], + }, + }), + ); + expect(window.location.href).toBe("/snaps"); }); }); - test('handles errors correctly', async () => { + test("handles errors correctly", async () => { const user = userEvent.setup(); - (global.fetch as jest.Mock).mockImplementationOnce(() => Promise.resolve({ - ok: false, - json: () => Promise.resolve({ error: 'Some error occurred' }), - })); + (global.fetch as jest.Mock).mockImplementationOnce(() => + Promise.resolve({ + ok: false, + json: () => Promise.resolve({ error: "Some error occurred" }), + }), + ); render(); - await user.click(screen.getByText('Unregister')); + await user.click(screen.getByText("Unregister")); await waitFor(() => { expect(mockSetUnregisterModalOpen).toHaveBeenCalledWith(false); expect(mockSetUnregisterError).toHaveBeenCalledWith(true); - expect(mockSetUnregisterErrorMessage).toHaveBeenCalledWith('Some error occurred'); + expect(mockSetUnregisterErrorMessage).toHaveBeenCalledWith( + "Some error occurred", + ); }); }); - test('logs error to console if fetch throws', async () => { + test("logs error to console if fetch throws", async () => { const user = userEvent.setup(); console.error = jest.fn(); - (global.fetch as jest.Mock).mockImplementationOnce(() => Promise.reject('Fetch error')); + (global.fetch as jest.Mock).mockImplementationOnce(() => + Promise.reject("Fetch error"), + ); render(); - await user.click(screen.getByText('Unregister')); + await user.click(screen.getByText("Unregister")); await waitFor(() => { - expect(console.error).toHaveBeenCalledWith('Fetch error'); + expect(console.error).toHaveBeenCalledWith("Fetch error"); }); }); }); diff --git a/static/js/publisher/settings/types/SettingsData.d.ts b/static/js/publisher/settings/types/SettingsData.d.ts index f238d788fb..5a2f6a1c0a 100644 --- a/static/js/publisher/settings/types/SettingsData.d.ts +++ b/static/js/publisher/settings/types/SettingsData.d.ts @@ -15,7 +15,7 @@ type SettingsData = { whitelist_country_keys: string; blacklist_country_keys: string; country_keys_status: string | null; - visibility_locked: boolean + visibility_locked: boolean; }; export type { SettingsData }; diff --git a/static/js/publisher/settings/utils/getFormData.ts b/static/js/publisher/settings/utils/getFormData.ts index 2c22e46841..43955597d8 100644 --- a/static/js/publisher/settings/utils/getFormData.ts +++ b/static/js/publisher/settings/utils/getFormData.ts @@ -5,7 +5,7 @@ import type { SettingsData } from "../types/SettingsData"; function getFormData( settingsData: SettingsData, dirtyFields: { [key: string]: any }, - data: any + data: any, ) { const changes = getChanges(dirtyFields, data); const formData = new FormData(); diff --git a/static/js/publisher/settings/utils/getSettingsData.ts b/static/js/publisher/settings/utils/getSettingsData.ts index 2e6245ef71..3bbe7ed1cc 100644 --- a/static/js/publisher/settings/utils/getSettingsData.ts +++ b/static/js/publisher/settings/utils/getSettingsData.ts @@ -33,9 +33,8 @@ function getVisibilityStatus(data: SettingsData) { function getSettingsData(settingsData: SettingsData) { settingsData.visibility = getVisibilityStatus(settingsData); - settingsData.territory_distribution_status = getTerritoryDistributionStatus( - settingsData - ); + settingsData.territory_distribution_status = + getTerritoryDistributionStatus(settingsData); settingsData.whitelist_country_keys = settingsData?.whitelist_countries .sort() .join(" "); diff --git a/static/js/publisher/shared/PageHeader/PageHeader.test.tsx b/static/js/publisher/shared/PageHeader/PageHeader.test.tsx index 6c0450f646..0c43780561 100644 --- a/static/js/publisher/shared/PageHeader/PageHeader.test.tsx +++ b/static/js/publisher/shared/PageHeader/PageHeader.test.tsx @@ -13,48 +13,48 @@ const props = { test("the page displays the correct name for the snap", () => { render(); expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent( - "test-snap-name" + "test-snap-name", ); }); test("the 'Listing' tab has the correct path", () => { render(); expect( - screen.getByRole("link", { name: "Listing" }).getAttribute("href") + screen.getByRole("link", { name: "Listing" }).getAttribute("href"), ).toBe(`/${snapName}/listing`); }); test("the 'Builds' tab has the correct path", () => { render(); expect( - screen.getByRole("link", { name: "Builds" }).getAttribute("href") + screen.getByRole("link", { name: "Builds" }).getAttribute("href"), ).toBe(`/${snapName}/builds`); }); test("the 'Releases' tab has the correct path", () => { render(); expect( - screen.getByRole("link", { name: "Releases" }).getAttribute("href") + screen.getByRole("link", { name: "Releases" }).getAttribute("href"), ).toBe(`/${snapName}/releases`); }); test("the 'Metrics' tab has the correct path", () => { render(); expect( - screen.getByRole("link", { name: "Metrics" }).getAttribute("href") + screen.getByRole("link", { name: "Metrics" }).getAttribute("href"), ).toBe(`/${snapName}/metrics`); }); test("the 'Publicise' tab has the correct path", () => { render(); expect( - screen.getByRole("link", { name: "Publicise" }).getAttribute("href") + screen.getByRole("link", { name: "Publicise" }).getAttribute("href"), ).toBe(`/${snapName}/publicise`); }); test("the 'Settings' tab has the correct path", () => { render(); expect( - screen.getByRole("link", { name: "Settings" }).getAttribute("href") + screen.getByRole("link", { name: "Settings" }).getAttribute("href"), ).toBe(`/${snapName}/settings`); }); diff --git a/static/js/publisher/shared/RenderErrors/__tests__/RenderErrors.test.tsx b/static/js/publisher/shared/RenderErrors/__tests__/RenderErrors.test.tsx index 5d9fdebd57..e9fffa622b 100644 --- a/static/js/publisher/shared/RenderErrors/__tests__/RenderErrors.test.tsx +++ b/static/js/publisher/shared/RenderErrors/__tests__/RenderErrors.test.tsx @@ -10,7 +10,7 @@ describe("RenderErrors", () => { errors={{ file: ["error message one", "error message two"], }} - /> + />, ); expect(screen.getByText(/error message one/)).toBeInTheDocument(); expect(screen.getByText(/error message two/)).toBeInTheDocument(); diff --git a/static/js/publisher/shared/SaveAndPreview/SaveAndPreview.test.tsx b/static/js/publisher/shared/SaveAndPreview/SaveAndPreview.test.tsx index 6da75e9ea5..8be08303c0 100644 --- a/static/js/publisher/shared/SaveAndPreview/SaveAndPreview.test.tsx +++ b/static/js/publisher/shared/SaveAndPreview/SaveAndPreview.test.tsx @@ -9,7 +9,7 @@ const reset = jest.fn(); const renderComponent = ( isDirty: boolean, isSaving: boolean, - isValid: boolean + isValid: boolean, ) => { return render( + />, ); }; test("the 'Revert' button is disabled by default", () => { renderComponent(false, false, true); - expect(screen.getByRole("button", { name: "Revert" })).toBeDisabled(); + 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(); + expect(screen.getByRole("button", { name: "Revert" })).not.toHaveAttribute( + "aria-disabled", + "true", + ); }); test("the 'Save' button is disabled by default", () => { renderComponent(false, false, true); - expect(screen.getByRole("button", { name: "Save" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Save" })).toHaveAttribute( + "aria-disabled", + "true", + ); }); test("the 'Save' button is enabled is data is dirty", () => { @@ -49,12 +58,18 @@ test("the 'Save' button shows loading state if saving", () => { test("the 'Save' button is disabled when saving", () => { renderComponent(true, true, true); - expect(screen.getByRole("button", { name: "Saving" })).toBeDisabled(); + 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" })).toBeDisabled(); + expect(screen.getByRole("button", { name: "Save" })).toHaveAttribute( + "aria-disabled", + "true", + ); }); test("revert button resets the form", async () => { diff --git a/static/js/publisher/shared/SaveAndPreview/SaveAndPreview.tsx b/static/js/publisher/shared/SaveAndPreview/SaveAndPreview.tsx index a6b71a5685..d48c6e20d2 100644 --- a/static/js/publisher/shared/SaveAndPreview/SaveAndPreview.tsx +++ b/static/js/publisher/shared/SaveAndPreview/SaveAndPreview.tsx @@ -23,7 +23,7 @@ function SaveAndPreview({ const handleScroll = () => { stickyBar?.current?.classList.toggle( "sticky-shadow", - stickyBar?.current?.getBoundingClientRect()?.top === 0 + stickyBar?.current?.getBoundingClientRect()?.top === 0, ); }; diff --git a/static/js/publisher/shared/SaveStateNotifications/__tests__/SaveStateNotifications.test.tsx b/static/js/publisher/shared/SaveStateNotifications/__tests__/SaveStateNotifications.test.tsx index a0f1ff20fb..903e991de4 100644 --- a/static/js/publisher/shared/SaveStateNotifications/__tests__/SaveStateNotifications.test.tsx +++ b/static/js/publisher/shared/SaveStateNotifications/__tests__/SaveStateNotifications.test.tsx @@ -18,7 +18,7 @@ const renderComponent = (options: Options) => { setHasSaved={options.setHasSaved || jest.fn()} savedError={options.savedError || false} setSavedError={options.setSavedError || jest.fn()} - /> + />, ); }; @@ -26,14 +26,14 @@ describe("SaveStateNotifications", () => { test("shows success notification if saved", () => { renderComponent({ hasSaved: true }); expect( - screen.getByRole("heading", { name: "Changes applied successfully." }) + 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." }) + screen.queryByRole("heading", { name: "Changes applied successfully." }), ).not.toBeInTheDocument(); }); @@ -42,7 +42,7 @@ describe("SaveStateNotifications", () => { const setHasSaved = jest.fn(); renderComponent({ hasSaved: true, setHasSaved }); await user.click( - screen.getByRole("button", { name: "Close notification" }) + screen.getByRole("button", { name: "Close notification" }), ); expect(setHasSaved).toHaveBeenCalled(); }); @@ -50,14 +50,14 @@ describe("SaveStateNotifications", () => { test("shows error notification if saved", () => { renderComponent({ savedError: true }); expect( - screen.getByText(/Changes have not been saved./) + 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./) + screen.queryByText(/Changes have not been saved./), ).not.toBeInTheDocument(); }); @@ -82,7 +82,7 @@ describe("SaveStateNotifications", () => { const setHasSaved = jest.fn(); renderComponent({ savedError: true, setHasSaved }); await user.click( - screen.getByRole("button", { name: "Close notification" }) + screen.getByRole("button", { name: "Close notification" }), ); expect(setHasSaved).toHaveBeenCalled(); }); @@ -92,7 +92,7 @@ describe("SaveStateNotifications", () => { const setSavedError = jest.fn(); renderComponent({ savedError: true, setSavedError }); await user.click( - screen.getByRole("button", { name: "Close notification" }) + screen.getByRole("button", { name: "Close notification" }), ); expect(setSavedError).toHaveBeenCalled(); }); diff --git a/static/js/publisher/shared/SearchAutocomplete/SearchAutocomplete.tsx b/static/js/publisher/shared/SearchAutocomplete/SearchAutocomplete.tsx index 69e492e6b3..b35a4c074b 100644 --- a/static/js/publisher/shared/SearchAutocomplete/SearchAutocomplete.tsx +++ b/static/js/publisher/shared/SearchAutocomplete/SearchAutocomplete.tsx @@ -83,7 +83,7 @@ function SearchAutocomplete({ className="p-icon--close p-multiselect__item-remove" onClick={() => { const newSelections = selections.filter( - (item: DataItem) => item.key !== suggestion.key + (item: DataItem) => item.key !== suggestion.key, ); const newSelectionsKeys = getNewSelectionKeys(newSelections); @@ -115,7 +115,7 @@ function SearchAutocomplete({ (item) => !inputValue || item.key.toLowerCase().includes(inputValue) || - item.name.toLowerCase().includes(inputValue) + item.name.toLowerCase().includes(inputValue), ) .map((item, index) => (
  • { setShowMetadataWarningModal={setShowMetadataWarningModal} submitForm={submitForm} formData={formData} - /> + />, ); }; diff --git a/static/js/publisher/submitEnabler.ts b/static/js/publisher/submitEnabler.ts index 102669cbce..8274a1595e 100644 --- a/static/js/publisher/submitEnabler.ts +++ b/static/js/publisher/submitEnabler.ts @@ -2,7 +2,7 @@ import shallowDiff from "../libs/shallowDiff"; function submitEnabler( formSelector: string | undefined, - buttonSelectors: any[] | undefined + buttonSelectors: any[] | undefined, ) { if (!formSelector) { throw new TypeError("`formSelector` argument is required"); @@ -19,7 +19,7 @@ function submitEnabler( } const buttonEls = buttonSelectors.map((selector) => - document.querySelector(selector) + document.querySelector(selector), ); const initialState = new FormData(formEl); diff --git a/static/js/publisher/tour.tsx b/static/js/publisher/tour.tsx index fa83cb6d88..2f84c682ca 100644 --- a/static/js/publisher/tour.tsx +++ b/static/js/publisher/tour.tsx @@ -44,7 +44,7 @@ export function initTour({ onTourStarted={onTourStarted} onTourClosed={onTourClosed} startTour={startTour} - /> + />, ); } diff --git a/static/js/publisher/tour/helpers.ts b/static/js/publisher/tour/helpers.ts index cf0f4457ad..2dc5b87c50 100644 --- a/static/js/publisher/tour/helpers.ts +++ b/static/js/publisher/tour/helpers.ts @@ -13,7 +13,7 @@ export function prepareSteps( elements: HTMLElement[]; content: string; title: string; - }> + }>, ): Array<{ id: string; position: string; @@ -26,7 +26,7 @@ export function prepareSteps( return { ...step, elements: [].slice.apply( - document.querySelectorAll(`[data-tour="${step.id}"]`) + document.querySelectorAll(`[data-tour="${step.id}"]`), ), position: step.position || "bottom-left", }; @@ -37,15 +37,15 @@ export function prepareSteps( // get rectangle of given DOM element // relative to the page, taking scroll into account const getRectFromEl = ( - el: HTMLElement + el: HTMLElement, ): { top: number; left: number; width: number; height: number; } => { - let clientRect = el.getBoundingClientRect(); - let ret = { + const clientRect = el.getBoundingClientRect(); + const ret = { top: clientRect.top + (window.pageYOffset || document.documentElement.scrollTop), @@ -81,8 +81,8 @@ const getMaskFromRect = (rect: { left = 0; } - let bottom = rect.top + rect.height + MASK_OFFSET; - let right = rect.left + rect.width + MASK_OFFSET; + const bottom = rect.top + rect.height + MASK_OFFSET; + const right = rect.left + rect.width + MASK_OFFSET; return { top, @@ -116,6 +116,6 @@ export const getMaskFromElements = (elements: Array) => { left: Infinity, right: 0, bottom: 0, - } + }, ); }; diff --git a/static/js/publisher/tour/metricsEvents.test.ts b/static/js/publisher/tour/metricsEvents.test.ts index 442b7f083d..7f74a20497 100644 --- a/static/js/publisher/tour/metricsEvents.test.ts +++ b/static/js/publisher/tour/metricsEvents.test.ts @@ -16,7 +16,7 @@ describe("metricsEvents", () => { "tour-started-by-user", expect.anything(), expect.anything(), - expect.anything() + expect.anything(), ); }); }); @@ -28,7 +28,7 @@ describe("metricsEvents", () => { "tour-started-automatically", expect.anything(), expect.anything(), - expect.anything() + expect.anything(), ); }); }); @@ -40,7 +40,7 @@ describe("metricsEvents", () => { "tour-finished", expect.anything(), expect.anything(), - expect.stringContaining("test-step") + expect.stringContaining("test-step"), ); }); }); @@ -52,7 +52,7 @@ describe("metricsEvents", () => { "tour-skipped", expect.anything(), expect.anything(), - expect.stringContaining("test-step") + expect.stringContaining("test-step"), ); }); }); diff --git a/static/js/publisher/tour/metricsEvents.ts b/static/js/publisher/tour/metricsEvents.ts index dfb92cc685..72807f9397 100644 --- a/static/js/publisher/tour/metricsEvents.ts +++ b/static/js/publisher/tour/metricsEvents.ts @@ -5,7 +5,7 @@ export const tourStartedByUser = () => "tour-started-by-user", window.location.href, "", - `Tour started manually by user on "${document.title}" page` + `Tour started manually by user on "${document.title}" page`, ); export const tourStartedAutomatically = () => @@ -13,7 +13,7 @@ export const tourStartedAutomatically = () => "tour-started-automatically", window.location.href, "", - `Tour started automatically on "${document.title}" page` + `Tour started automatically on "${document.title}" page`, ); export const tourFinished = (stepId: string) => @@ -21,7 +21,7 @@ export const tourFinished = (stepId: string) => "tour-finished", window.location.href, "", - `Tour finished on "${document.title}" page on step ${stepId}` + `Tour finished on "${document.title}" page on step ${stepId}`, ); export const tourSkipped = (stepId: string) => @@ -29,5 +29,5 @@ export const tourSkipped = (stepId: string) => "tour-skipped", window.location.href, "", - `Tour skipped on "${document.title}" page on step ${stepId}` + `Tour skipped on "${document.title}" page on step ${stepId}`, ); diff --git a/static/js/publisher/tour/tour.test.tsx b/static/js/publisher/tour/tour.test.tsx index a9d7f0d13c..878dee5bd9 100644 --- a/static/js/publisher/tour/tour.test.tsx +++ b/static/js/publisher/tour/tour.test.tsx @@ -29,7 +29,7 @@ describe("Tour", () => { startTour={false} onTourClosed={jest.fn()} onTourStarted={jest.fn()} - /> + />, ); const button = getByText("Tour"); @@ -46,7 +46,7 @@ describe("Tour", () => { startTour={false} onTourClosed={jest.fn()} onTourStarted={jest.fn()} - /> + />, ); const button = getByText("Tour"); @@ -56,7 +56,7 @@ describe("Tour", () => { expect.objectContaining({ steps, }), - expect.any(Object) + expect.any(Object), ); }); }); @@ -69,14 +69,14 @@ describe("Tour", () => { startTour={true} onTourClosed={jest.fn()} onTourStarted={jest.fn()} - /> + />, ); expect(TourOverlay).toBeCalledWith( expect.objectContaining({ steps, }), - expect.any(Object) + expect.any(Object), ); }); @@ -87,7 +87,7 @@ describe("Tour", () => { startTour={true} onTourClosed={jest.fn()} onTourStarted={jest.fn()} - /> + />, ); expect(tourStartedAutomatically).toBeCalled(); }); @@ -107,7 +107,7 @@ describe("Tour", () => { startTour={true} onTourStarted={onTourStarted} onTourClosed={jest.fn()} - /> + />, ); expect(onTourStarted).toBeCalled(); @@ -120,7 +120,7 @@ describe("Tour", () => { onTourStarted={onTourStarted} startTour={false} onTourClosed={jest.fn()} - /> + />, ); fireEvent.click(getByText("Tour")); @@ -142,7 +142,7 @@ describe("Tour", () => { startTour={true} onTourClosed={onTourClosed} onTourStarted={jest.fn()} - /> + />, ); expect(onTourClosed).not.toBeCalled(); @@ -155,7 +155,7 @@ describe("Tour", () => { onTourClosed={onTourClosed} startTour={false} onTourStarted={jest.fn()} - /> + />, ); expect(onTourClosed).toBeCalled(); diff --git a/static/js/publisher/tour/tourOverlay.test.tsx b/static/js/publisher/tour/tourOverlay.test.tsx index 8214fd8fbd..ed38a1a648 100644 --- a/static/js/publisher/tour/tourOverlay.test.tsx +++ b/static/js/publisher/tour/tourOverlay.test.tsx @@ -36,8 +36,8 @@ prepareSteps.mockImplementation( elements: HTMLElement[]; content: string; title: string; - }> - ) => steps + }>, + ) => steps, ); describe("TourOverlay", () => { @@ -65,7 +65,7 @@ describe("TourOverlay", () => { it("should render TourStepCard for first step", () => { const { getByText } = render( - + , ); expect(getByText(steps[0].title)).toBeDefined(); @@ -74,7 +74,7 @@ describe("TourOverlay", () => { describe("when moving to next step", () => { it("should render TourStepCard for next step", () => { const { getByText } = render( - + , ); fireEvent.click(getByText("Next step")); @@ -84,7 +84,7 @@ describe("TourOverlay", () => { it("should scroll next step into view", () => { const { getByText } = render( - + , ); fireEvent.click(getByText("Next step")); @@ -96,7 +96,7 @@ describe("TourOverlay", () => { describe("when moving to previous step", () => { it("should render TourStepCard for previous step", () => { const { getByText } = render( - + , ); fireEvent.click(getByText("Previous step")); @@ -106,7 +106,7 @@ describe("TourOverlay", () => { it("should scroll previous step into view", () => { const { getByText } = render( - + , ); fireEvent.click(getByText("Previous step")); @@ -120,7 +120,7 @@ describe("TourOverlay", () => { const hideTour = jest.fn(); const { getByText } = render( - + , ); fireEvent.click(getByText("Skip tour")); @@ -132,7 +132,7 @@ describe("TourOverlay", () => { const hideTour = jest.fn(); const { getByText } = render( - + , ); fireEvent.click(getByText("Skip tour")); @@ -147,7 +147,7 @@ describe("TourOverlay", () => { const hideTour = jest.fn(); const { getByText } = render( - + , ); fireEvent.click(getByText("Finish tour")); @@ -159,7 +159,7 @@ describe("TourOverlay", () => { const hideTour = jest.fn(); const { getByText } = render( - + , ); fireEvent.click(getByText("Finish tour")); @@ -195,7 +195,7 @@ describe("TourOverlay", () => { const hideTour = jest.fn(); render( - + , ); fireEvent.keyUp(document, { key: "Escape", keyCode: 27 }); diff --git a/static/js/publisher/tour/tourOverlay.tsx b/static/js/publisher/tour/tourOverlay.tsx index db2c756a9f..f961158b85 100644 --- a/static/js/publisher/tour/tourOverlay.tsx +++ b/static/js/publisher/tour/tourOverlay.tsx @@ -55,7 +55,7 @@ export default function TourOverlay({ // we scroll relative to top of the screen, but we want to stick to bottom // so we need to substract the window height mask.bottom - window.innerHeight, - -SCROLL_OFFSET_BOTTOM + -SCROLL_OFFSET_BOTTOM, ); } } @@ -71,7 +71,7 @@ export default function TourOverlay({ } } }, - [currentStepIndex] // refresh effect on step changes, to scroll to correct step + [currentStepIndex], // refresh effect on step changes, to scroll to correct step ); const overlayEl = useRef(null); @@ -98,7 +98,7 @@ export default function TourOverlay({ window.removeEventListener("scroll", afterScroll); }; }, - [] // don't refresh the effect on every render + [], // don't refresh the effect on every render ); // rerender after resize (to adjust to new positions of elements) @@ -122,7 +122,7 @@ export default function TourOverlay({ window.removeEventListener("resize", afterResize); }; }, - [] // don't refresh the effect on every render + [], // don't refresh the effect on every render ); const onNextClick = () => @@ -159,7 +159,7 @@ export default function TourOverlay({ window.removeEventListener("keyup", escClick); }; }, - [currentStepIndex] // refresh effect when step changes, to pass correct step id into skip metrics + [currentStepIndex], // refresh effect when step changes, to pass correct step id into skip metrics ); return ( diff --git a/static/js/publisher/tour/tourOverlayMask.tsx b/static/js/publisher/tour/tourOverlayMask.tsx index 4bd19d7f8e..436db46359 100644 --- a/static/js/publisher/tour/tourOverlayMask.tsx +++ b/static/js/publisher/tour/tourOverlayMask.tsx @@ -8,7 +8,7 @@ type Props = { }; const getClipPathFromMask = ({ top, bottom, left, right }: Props): string => { - let mask = [ + const mask = [ `${left}px ${top}px`, `${left}px ${bottom}px`, `${right}px ${bottom}px`, diff --git a/static/js/publisher/tour/tourStepCard.test.tsx b/static/js/publisher/tour/tourStepCard.test.tsx index c788cda3f9..43c47f55b0 100644 --- a/static/js/publisher/tour/tourStepCard.test.tsx +++ b/static/js/publisher/tour/tourStepCard.test.tsx @@ -55,7 +55,7 @@ describe("TourStepCard", () => { onSkipClick={onSkipClick} onNextClick={onNextClick} onPrevClick={onPrevClick} - /> + />, ); }; diff --git a/static/js/store/pages/Packages/Packages.tsx b/static/js/store/pages/Packages/Packages.tsx index f6302f7fef..a79bcd4b54 100644 --- a/static/js/store/pages/Packages/Packages.tsx +++ b/static/js/store/pages/Packages/Packages.tsx @@ -35,7 +35,7 @@ function Packages(): ReactNode { return { ...item, id: uuidv4(), - } + }; }); return { @@ -54,7 +54,12 @@ function Packages(): ReactNode { const currentPage = searchParams.get("page") || "1"; let queryString = search; - if (!search || (!searchParams.get("categories") && !searchParams.get("q") && !searchParams.get("architecture"))) { + if ( + !search || + (!searchParams.get("categories") && + !searchParams.get("q") && + !searchParams.get("architecture")) + ) { queryString = "?categories=featured"; } @@ -63,7 +68,7 @@ function Packages(): ReactNode { () => getData(queryString), { keepPreviousData: true, - } + }, ); const searchRef = useRef(null); @@ -71,7 +76,11 @@ function Packages(): ReactNode { useEffect(() => { if (initialLoad) { - if (!searchParams.get("categories") && !searchParams.get("q") && !searchParams.get("architecture")) { + if ( + !searchParams.get("categories") && + !searchParams.get("q") && + !searchParams.get("architecture") + ) { searchParams.set("categories", "featured"); setSearchParams(searchParams); } @@ -88,20 +97,22 @@ function Packages(): ReactNode { const getCategoryDisplayName = (name: string) => { const category = data?.categories?.find( - (cat: Category) => cat.name === name + (cat: Category) => cat.name === name, ); return category?.display_name; }; - const selectedCategories = searchParams.get("categories")?.split(",").filter(Boolean) || []; + const selectedCategories = + searchParams.get("categories")?.split(",").filter(Boolean) || []; const isFeatured = - selectedCategories.length === 0 || (selectedCategories.length === 1 && selectedCategories[0] === "featured"); + selectedCategories.length === 0 || + (selectedCategories.length === 1 && selectedCategories[0] === "featured"); let showAllCategories = false; const selectedFiltersNotVisible = ( selectedFilters: Array, - allFilters: Array + allFilters: Array, ) => { const sortedFilters = [] as Array; @@ -120,7 +131,7 @@ function Packages(): ReactNode { return selectedFilters.some((selectedFilter) => { const currentFilter = allFilters.find( - (filter: Category) => filter.name === selectedFilter + (filter: Category) => filter.name === selectedFilter, ); if (currentFilter) { @@ -210,15 +221,24 @@ function Packages(): ReactNode { categories={data?.categories || []} selectedCategories={selectedCategories} setSelectedCategories={( - items: Array<{ - display_name: string; - name: string; - }> | string[] + items: + | Array<{ + display_name: string; + name: string; + }> + | string[], ) => { - const categoryNames = items.map(item => typeof item === 'string' ? item : item.name).filter(Boolean); + const categoryNames = items + .map((item) => + typeof item === "string" ? item : item.name, + ) + .filter(Boolean); if (categoryNames.length > 0) { if (categoryNames.includes("featured")) { - categoryNames.splice(categoryNames.indexOf("featured"), 1); + categoryNames.splice( + categoryNames.indexOf("featured"), + 1, + ); } searchParams.set("categories", categoryNames.join(",")); } else { diff --git a/static/js/store/pages/Packages/__tests__/Packages.test.tsx b/static/js/store/pages/Packages/__tests__/Packages.test.tsx index 21ba2de34f..f7b8663cae 100644 --- a/static/js/store/pages/Packages/__tests__/Packages.test.tsx +++ b/static/js/store/pages/Packages/__tests__/Packages.test.tsx @@ -117,7 +117,7 @@ const queryClient = new QueryClient(); const renderComponent = ( useMemoryRouter?: boolean, - initialEntries?: Array + initialEntries?: Array, ) => { if (useMemoryRouter && initialEntries) { return render( @@ -125,7 +125,7 @@ const renderComponent = ( - + , ); } @@ -134,7 +134,7 @@ const renderComponent = ( - + , ); }; @@ -143,7 +143,7 @@ describe("Packages", () => { renderComponent(); await waitFor(() => { expect(global.fetch).toHaveBeenCalledWith( - "/beta/store.json?categories=featured" + "/beta/store.json?categories=featured", ); }); }); @@ -154,7 +154,7 @@ describe("Packages", () => { await user.click(screen.getByLabelText("Development")); await waitFor(() => { expect(global.fetch).toHaveBeenCalledWith( - "/beta/store.json?categories=development" + "/beta/store.json?categories=development", ); }); }); @@ -167,7 +167,7 @@ describe("Packages", () => { await user.click(screen.getByLabelText("Development")); await waitFor(() => { expect(global.fetch).toHaveBeenCalledWith( - "/beta/store.json?categories=development&q=code" + "/beta/store.json?categories=development&q=code", ); }); }); @@ -186,10 +186,10 @@ describe("Packages", () => { renderComponent(); await waitFor(() => { expect( - screen.getByRole("button", { name: /Show more/ }) + screen.getByRole("button", { name: /Show more/ }), ).toBeInTheDocument(); expect( - screen.queryByRole("button", { name: /Show less/ }) + screen.queryByRole("button", { name: /Show less/ }), ).not.toBeInTheDocument(); }); }); @@ -200,10 +200,10 @@ describe("Packages", () => { await user.click(screen.getByRole("button", { name: /Show more/ })); await waitFor(() => { expect( - screen.queryByRole("button", { name: /Show more/ }) + screen.queryByRole("button", { name: /Show more/ }), ).not.toBeInTheDocument(); expect( - screen.getByRole("button", { name: /Show less/ }) + screen.getByRole("button", { name: /Show less/ }), ).toBeInTheDocument(); }); }); @@ -222,10 +222,10 @@ describe("Packages", () => { renderComponent(true, ["?categories=science"]); await waitFor(() => { expect( - screen.queryByRole("button", { name: "Show more" }) + screen.queryByRole("button", { name: "Show more" }), ).not.toBeInTheDocument(); expect( - screen.getByRole("button", { name: "Show less" }) + screen.getByRole("button", { name: "Show less" }), ).toBeInTheDocument(); }); }); @@ -237,7 +237,7 @@ describe("Packages", () => { screen.getByRole("heading", { level: 2, name: "Development and 1 more category", - }) + }), ).toBeInTheDocument(); }); }); @@ -251,7 +251,7 @@ describe("Packages", () => { screen.getByRole("heading", { level: 2, name: "Development and 4 more categories", - }) + }), ).toBeInTheDocument(); }); }); @@ -262,7 +262,7 @@ describe("Packages", () => { await user.click(screen.getByLabelText("Development")); await waitFor(() => { expect(global.fetch).toHaveBeenCalledWith( - "/beta/store.json?categories=development" + "/beta/store.json?categories=development", ); }); }); diff --git a/yarn.lock b/yarn.lock index bd0f239921..6fbde310c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1200,17 +1200,29 @@ dependencies: tslib "^2.0.0" -"@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" +<<<<<<< HEAD +"@eslint-community/regexpp@^4.10.0": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== + "@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== +======= +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": version "4.11.0" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== +>>>>>>> WD-13579-fix-exisiting-js-linting-issues "@eslint/eslintrc@^2.1.4": version "2.1.4" @@ -2427,6 +2439,57 @@ dependencies: "@types/node" "*" +<<<<<<< HEAD +"@typescript-eslint/eslint-plugin@^7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz#f5f5da52db674b1f2cdb9d5f3644e5b2ec750465" + integrity sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "7.16.1" + "@typescript-eslint/type-utils" "7.16.1" + "@typescript-eslint/utils" "7.16.1" + "@typescript-eslint/visitor-keys" "7.16.1" +======= +"@typescript-eslint/eslint-plugin@^7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz#c8ed1af1ad2928ede5cdd207f7e3090499e1f77b" + integrity sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "7.17.0" + "@typescript-eslint/type-utils" "7.17.0" + "@typescript-eslint/utils" "7.17.0" + "@typescript-eslint/visitor-keys" "7.17.0" +>>>>>>> WD-13579-fix-exisiting-js-linting-issues + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +<<<<<<< HEAD +"@typescript-eslint/parser@^7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.16.1.tgz#84c581cf86c8b2becd48d33ddc41a6303d57b274" + integrity sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA== + dependencies: + "@typescript-eslint/scope-manager" "7.16.1" + "@typescript-eslint/types" "7.16.1" + "@typescript-eslint/typescript-estree" "7.16.1" + "@typescript-eslint/visitor-keys" "7.16.1" +======= +"@typescript-eslint/parser@^7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.17.0.tgz#be8e32c159190cd40a305a2121220eadea5a88e7" + integrity sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A== + dependencies: + "@typescript-eslint/scope-manager" "7.17.0" + "@typescript-eslint/types" "7.17.0" + "@typescript-eslint/typescript-estree" "7.17.0" + "@typescript-eslint/visitor-keys" "7.17.0" +>>>>>>> WD-13579-fix-exisiting-js-linting-issues + debug "^4.3.4" + "@typescript-eslint/scope-manager@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" @@ -2435,11 +2498,59 @@ "@typescript-eslint/types" "5.62.0" "@typescript-eslint/visitor-keys" "5.62.0" +<<<<<<< HEAD +"@typescript-eslint/scope-manager@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz#2b43041caabf8ddd74512b8b550b9fc53ca3afa1" + integrity sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw== + dependencies: + "@typescript-eslint/types" "7.16.1" + "@typescript-eslint/visitor-keys" "7.16.1" + +"@typescript-eslint/type-utils@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz#4d7ae4f3d9e3c8cbdabae91609b1a431de6aa6ca" + integrity sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA== + dependencies: + "@typescript-eslint/typescript-estree" "7.16.1" + "@typescript-eslint/utils" "7.16.1" +======= +"@typescript-eslint/scope-manager@7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz#e072d0f914662a7bfd6c058165e3c2b35ea26b9d" + integrity sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA== + dependencies: + "@typescript-eslint/types" "7.17.0" + "@typescript-eslint/visitor-keys" "7.17.0" + +"@typescript-eslint/type-utils@7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz#c5da78feb134c9c9978cbe89e2b1a589ed22091a" + integrity sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA== + dependencies: + "@typescript-eslint/typescript-estree" "7.17.0" + "@typescript-eslint/utils" "7.17.0" +>>>>>>> WD-13579-fix-exisiting-js-linting-issues + debug "^4.3.4" + ts-api-utils "^1.3.0" + "@typescript-eslint/types@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== +<<<<<<< HEAD +"@typescript-eslint/types@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.16.1.tgz#bbab066276d18e398bc64067b23f1ce84dfc6d8c" + integrity sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ== +======= +"@typescript-eslint/types@7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.17.0.tgz#7ce8185bdf06bc3494e73d143dbf3293111b9cff" + integrity sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A== +>>>>>>> WD-13579-fix-exisiting-js-linting-issues + "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" @@ -2453,6 +2564,52 @@ semver "^7.3.7" tsutils "^3.21.0" +<<<<<<< HEAD +"@typescript-eslint/typescript-estree@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz#9b145ba4fd1dde1986697e1ce57dc501a1736dd3" + integrity sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ== + dependencies: + "@typescript-eslint/types" "7.16.1" + "@typescript-eslint/visitor-keys" "7.16.1" +======= +"@typescript-eslint/typescript-estree@7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz#dcab3fea4c07482329dd6107d3c6480e228e4130" + integrity sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw== + dependencies: + "@typescript-eslint/types" "7.17.0" + "@typescript-eslint/visitor-keys" "7.17.0" +>>>>>>> WD-13579-fix-exisiting-js-linting-issues + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +<<<<<<< HEAD +"@typescript-eslint/utils@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.16.1.tgz#df42dc8ca5a4603016fd102db0346cdab415cdb7" + integrity sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "7.16.1" + "@typescript-eslint/types" "7.16.1" + "@typescript-eslint/typescript-estree" "7.16.1" +======= +"@typescript-eslint/utils@7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.17.0.tgz#815cd85b9001845d41b699b0ce4f92d6dfb84902" + integrity sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "7.17.0" + "@typescript-eslint/types" "7.17.0" + "@typescript-eslint/typescript-estree" "7.17.0" +>>>>>>> WD-13579-fix-exisiting-js-linting-issues + "@typescript-eslint/utils@^5.10.0": version "5.62.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" @@ -2475,6 +2632,23 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" +<<<<<<< HEAD +"@typescript-eslint/visitor-keys@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz#4287bcf44c34df811ff3bb4d269be6cfc7d8c74b" + integrity sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg== + dependencies: + "@typescript-eslint/types" "7.16.1" +======= +"@typescript-eslint/visitor-keys@7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz#680465c734be30969e564b4647f38d6cdf49bfb0" + integrity sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A== + dependencies: + "@typescript-eslint/types" "7.17.0" +>>>>>>> WD-13579-fix-exisiting-js-linting-issues + eslint-visitor-keys "^3.4.3" + "@ungap/structured-clone@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" @@ -2768,7 +2942,7 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@5.1.3: +aria-query@5.1.3, aria-query@~5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== @@ -2790,6 +2964,14 @@ array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1: call-bind "^1.0.5" is-array-buffer "^3.0.4" +array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== + dependencies: + call-bind "^1.0.5" + is-array-buffer "^3.0.4" + array-includes@^3.1.6: version "3.1.8" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" @@ -2802,6 +2984,18 @@ array-includes@^3.1.6: get-intrinsic "^1.2.4" is-string "^1.0.7" +array-includes@^3.1.8: + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + is-string "^1.0.7" + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -2817,7 +3011,7 @@ array.prototype.flat@^1.3.1: es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.flatmap@^1.3.1: +array.prototype.flatmap@^1.3.1, array.prototype.flatmap@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== @@ -2852,6 +3046,25 @@ arraybuffer.prototype.slice@^1.0.3: is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" + is-shared-array-buffer "^1.0.2" + +ast-types-flow@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" + integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== + astral-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" @@ -2881,6 +3094,25 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +axe-core@^4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.1.tgz#fcd0f4496dad09e0c899b44f6c4bb7848da912ae" + integrity sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw== + +axobject-query@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1" + integrity sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg== + dependencies: + deep-equal "^2.0.5" + babel-jest@29.7.0, babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" @@ -3001,6 +3233,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" @@ -3067,6 +3306,17 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: get-intrinsic "^1.2.4" set-function-length "^1.2.1" +call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -3663,6 +3913,11 @@ d3@7.8.5: d3-transition "3" d3-zoom "3" +damerau-levenshtein@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" + integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== + data-urls@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" @@ -3776,7 +4031,20 @@ define-data-property@^1.0.1, define-data-property@^1.1.4: es-errors "^1.3.0" gopd "^1.0.1" +<<<<<<< HEAD +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: +======= define-properties@^1.2.0, define-properties@^1.2.1: +>>>>>>> WD-13579-fix-exisiting-js-linting-issues version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== @@ -3942,10 +4210,69 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +<<<<<<< HEAD +es-abstract@^1.17.5, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2, es-abstract@^1.23.3: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" + is-callable "^1.2.7" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.3" + is-string "^1.0.7" + is-typed-array "^1.1.13" + is-weakref "^1.0.2" + object-inspect "^1.13.1" + object-keys "^1.1.1" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.6" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.15" + +es-abstract@^1.22.1: + version "1.22.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" + integrity sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA== +======= es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3: version "1.23.3" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== +>>>>>>> WD-13579-fix-exisiting-js-linting-issues dependencies: array-buffer-byte-length "^1.0.1" arraybuffer.prototype.slice "^1.0.3" @@ -4006,6 +4333,18 @@ es-errors@^1.2.1, es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + es-get-iterator@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" @@ -4041,6 +4380,26 @@ es-iterator-helpers@^1.0.12: iterator.prototype "^1.1.2" safe-array-concat "^1.1.2" +es-iterator-helpers@^1.0.19: + version "1.0.19" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz#117003d0e5fec237b4b5c08aded722e0c6d50ca8" + integrity sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + globalthis "^1.0.3" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + internal-slot "^1.0.7" + iterator.prototype "^1.1.2" + safe-array-concat "^1.1.2" + es-module-lexer@^1.2.1: version "1.5.4" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" @@ -4050,6 +4409,16 @@ es-object-atoms@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== +<<<<<<< HEAD + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz#11f7cc9f63376930a5f20be4915834f4bc74f9c9" + integrity sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q== +======= +>>>>>>> WD-13579-fix-exisiting-js-linting-issues dependencies: es-errors "^1.3.0" @@ -4062,7 +4431,11 @@ es-set-tostringtag@^2.0.3: has-tostringtag "^1.0.2" hasown "^2.0.1" +<<<<<<< HEAD +es-shim-unscopables@^1.0.0: +======= es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2: +>>>>>>> WD-13579-fix-exisiting-js-linting-issues version "1.0.2" resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== @@ -4121,6 +4494,28 @@ eslint-plugin-jest@27.6.1: dependencies: "@typescript-eslint/utils" "^5.10.0" +eslint-plugin-jsx-a11y@^6.9.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz#67ab8ff460d4d3d6a0b4a570e9c1670a0a8245c8" + integrity sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g== + dependencies: + aria-query "~5.1.3" + array-includes "^3.1.8" + array.prototype.flatmap "^1.3.2" + ast-types-flow "^0.0.8" + axe-core "^4.9.1" + axobject-query "~3.1.1" + damerau-levenshtein "^1.0.8" + emoji-regex "^9.2.2" + es-iterator-helpers "^1.0.19" + hasown "^2.0.2" + jsx-ast-utils "^3.3.5" + language-tags "^1.0.9" + minimatch "^3.1.2" + object.fromentries "^2.0.8" + safe-regex-test "^1.0.3" + string.prototype.includes "^2.0.0" + eslint-plugin-prettier@5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz#17cfade9e732cef32b5f5be53bd4e07afd8e67e1" @@ -4565,6 +4960,17 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -4596,6 +5002,15 @@ get-symbol-description@^1.0.2: es-errors "^1.3.0" get-intrinsic "^1.2.4" +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== + dependencies: + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -4781,10 +5196,29 @@ has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: dependencies: es-define-property "^1.0.0" +<<<<<<< HEAD +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== +======= has-proto@^1.0.1, has-proto@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== +>>>>>>> WD-13579-fix-exisiting-js-linting-issues + +has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" @@ -4798,10 +5232,31 @@ has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: dependencies: has-symbols "^1.0.3" +<<<<<<< HEAD +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== +======= hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== +>>>>>>> WD-13579-fix-exisiting-js-linting-issues + dependencies: + function-bind "^1.1.2" + +hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" @@ -4870,7 +5325,12 @@ identity-obj-proxy@3.0.0: dependencies: harmony-reflect "^1.4.6" -ignore@^5.2.0, ignore@^5.2.4, ignore@^5.3.0: +ignore@^5.2.0, ignore@^5.2.4: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" + integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== + +ignore@^5.3.0, ignore@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== @@ -4950,6 +5410,15 @@ internal-slot@^1.0.4, internal-slot@^1.0.7: hasown "^2.0.0" side-channel "^1.0.4" +internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + "internmap@1 - 2": version "2.0.3" resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" @@ -4983,6 +5452,14 @@ is-array-buffer@^3.0.2, is-array-buffer@^3.0.4: call-bind "^1.0.2" get-intrinsic "^1.2.1" +is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -5036,6 +5513,13 @@ is-data-view@^1.0.1: dependencies: is-typed-array "^1.1.13" +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== + dependencies: + is-typed-array "^1.1.13" + is-date-object@^1.0.1, is-date-object@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" @@ -5089,6 +5573,11 @@ is-negative-zero@^2.0.3: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + is-number-object@^1.0.4: version "1.0.7" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" @@ -5143,6 +5632,13 @@ is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: dependencies: call-bind "^1.0.7" +is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== + dependencies: + call-bind "^1.0.7" + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -5169,10 +5665,24 @@ is-typed-array@^1.1.13: dependencies: which-typed-array "^1.1.14" +<<<<<<< HEAD +is-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== + dependencies: + which-typed-array "^1.1.14" + +is-weakmap@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" + integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== +======= is-weakmap@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== +>>>>>>> WD-13579-fix-exisiting-js-linting-issues is-weakref@^1.0.2: version "1.0.2" @@ -5780,7 +6290,7 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -"jsx-ast-utils@^2.4.1 || ^3.0.0": +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: version "3.3.5" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== @@ -5812,10 +6322,24 @@ known-css-properties@^0.29.0: resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.29.0.tgz#e8ba024fb03886f23cb882e806929f32d814158f" integrity sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ== +<<<<<<< HEAD +language-subtag-registry@^0.3.20: + version "0.3.23" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7" + integrity sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ== + +language-tags@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" + integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== + dependencies: + language-subtag-registry "^0.3.20" +======= known-css-properties@^0.34.0: version "0.34.0" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.34.0.tgz#ccd7e9f4388302231b3f174a8b1d5b1f7b576cea" integrity sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ== +>>>>>>> WD-13579-fix-exisiting-js-linting-issues leven@^3.1.0: version "3.1.0" @@ -6058,6 +6582,20 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimatch@~0.2.11: version "0.2.14" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" @@ -6187,6 +6725,16 @@ object.fromentries@^2.0.6: es-abstract "^1.23.2" es-object-atoms "^1.0.0" +object.fromentries@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + object.hasown@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" @@ -6859,6 +7407,16 @@ regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: es-errors "^1.3.0" set-function-name "^2.0.1" +regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== + dependencies: + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" + regexpu-core@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" @@ -6989,6 +7547,16 @@ safe-array-concat@^1.1.2: has-symbols "^1.0.3" isarray "^2.0.5" +safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== + dependencies: + call-bind "^1.0.7" + get-intrinsic "^1.2.4" + has-symbols "^1.0.3" + isarray "^2.0.5" + safe-buffer@5.2.1, safe-buffer@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -7003,6 +7571,15 @@ safe-regex-test@^1.0.3: es-errors "^1.3.0" is-regex "^1.1.4" +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-regex "^1.1.4" + "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -7067,7 +7644,17 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.4, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: +semver@^7.3.4, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +semver@^7.6.0: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +semver@^7.6.0: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -7091,10 +7678,29 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" +<<<<<<< HEAD +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.0, set-function-name@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== +======= set-function-name@^2.0.1, set-function-name@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== +>>>>>>> WD-13579-fix-exisiting-js-linting-issues dependencies: define-data-property "^1.1.4" es-errors "^1.3.0" @@ -7246,6 +7852,26 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +<<<<<<< HEAD +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string.prototype.includes@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz#8986d57aee66d5460c144620a6d873778ad7289f" + integrity sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +======= +>>>>>>> WD-13579-fix-exisiting-js-linting-issues string.prototype.matchall@^4.0.8: version "4.0.11" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" @@ -7274,15 +7900,58 @@ string.prototype.trim@^1.2.9: es-abstract "^1.23.0" es-object-atoms "^1.0.0" +<<<<<<< HEAD +string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" + +string.prototype.trimend@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" + integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== +======= string.prototype.trimend@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== +>>>>>>> WD-13579-fix-exisiting-js-linting-issues dependencies: call-bind "^1.0.7" define-properties "^1.2.1" es-object-atoms "^1.0.0" +<<<<<<< HEAD +string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== +======= +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== +>>>>>>> WD-13579-fix-exisiting-js-linting-issues + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +<<<<<<< HEAD string.prototype.trimstart@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" @@ -7292,7 +7961,10 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +======= strip-ansi@^6.0.0, strip-ansi@^6.0.1: +>>>>>>> WD-13579-fix-exisiting-js-linting-issues version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -7611,6 +8283,11 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== +ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + ts-jest@29.1.1: version "29.1.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" @@ -7684,6 +8361,34 @@ typed-array-buffer@^1.0.2: es-errors "^1.3.0" is-typed-array "^1.1.13" +<<<<<<< HEAD +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== +======= +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== +>>>>>>> WD-13579-fix-exisiting-js-linting-issues + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +<<<<<<< HEAD typed-array-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" @@ -7695,6 +8400,25 @@ typed-array-byte-length@^1.0.1: has-proto "^1.0.3" is-typed-array "^1.1.13" +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== +======= +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== +>>>>>>> WD-13579-fix-exisiting-js-linting-issues + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +<<<<<<< HEAD typed-array-byte-offset@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" @@ -7707,6 +8431,24 @@ typed-array-byte-offset@^1.0.2: has-proto "^1.0.3" is-typed-array "^1.1.13" +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== +======= +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== +>>>>>>> WD-13579-fix-exisiting-js-linting-issues + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + typed-array-length@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" @@ -8045,6 +8787,17 @@ which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15: gopd "^1.0.1" has-tostringtag "^1.0.2" +which-typed-array@^1.1.14, which-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"