diff --git a/src/internal/base-component/__tests__/use-base-component.test.tsx b/src/internal/base-component/__tests__/use-base-component.test.tsx index ed893b5..8c711ae 100644 --- a/src/internal/base-component/__tests__/use-base-component.test.tsx +++ b/src/internal/base-component/__tests__/use-base-component.test.tsx @@ -1,14 +1,13 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { render } from "@testing-library/react"; -import { expect, test, vi } from "vitest"; +import { afterEach, expect, test, vi } from "vitest"; -import { COMPONENT_METADATA_KEY } from "@cloudscape-design/component-toolkit/internal"; +import { COMPONENT_METADATA_KEY, useComponentMetrics } from "@cloudscape-design/component-toolkit/internal"; import useBaseComponent, { InternalBaseComponentProps, } from "../../../../lib/components/internal/base-component/use-base-component"; -import { useTelemetry } from "../../../../lib/components/internal/base-component/use-telemetry"; import { PACKAGE_VERSION } from "../../../../lib/components/internal/environment"; type InternalDemoProps = InternalBaseComponentProps; @@ -16,24 +15,39 @@ function InternalDemo({ __internalRootRef }: InternalDemoProps) { return
Internal Demo Component
; } +declare global { + interface Node { + [COMPONENT_METADATA_KEY]?: { name: string; version: string }; + } +} + function Demo({ variant }: { variant: string }) { const baseComponentProps = useBaseComponent("DemoComponent", { props: { variant } }); return ; } -vi.mock("../../../../lib/components/internal/base-component/use-telemetry", () => { - return { useTelemetry: vi.fn(() => null) }; +vi.mock("@cloudscape-design/component-toolkit/internal", async (importOriginal) => { + return { ...(await importOriginal()), useComponentMetrics: vi.fn(() => {}) }; +}); + +afterEach(() => { + vi.resetAllMocks(); }); test("should attach the metadata to the returned root DOM node", () => { const { container } = render(); - const rootNode: any = container.firstChild; - expect(rootNode[COMPONENT_METADATA_KEY]?.name).toBe("DemoComponent"); - expect(rootNode[COMPONENT_METADATA_KEY]?.version).toBe(PACKAGE_VERSION); + const rootNode = container.firstChild; + expect(rootNode![COMPONENT_METADATA_KEY]!.name).toBe("DemoComponent"); + expect(rootNode![COMPONENT_METADATA_KEY]!.version).toBe(PACKAGE_VERSION); }); test("should call the useTelemetry hook passing down the given component name and its props", () => { - vi.resetAllMocks(); render(); - expect(useTelemetry).toHaveBeenCalledWith("DemoComponent", { props: { variant: "default" } }); + expect(useComponentMetrics).toHaveBeenCalledWith( + "DemoComponent", + expect.objectContaining({ packageVersion: PACKAGE_VERSION }), + { + props: { variant: "default" }, + }, + ); }); diff --git a/src/internal/base-component/use-base-component.ts b/src/internal/base-component/use-base-component.ts index 700b9cd..538b6e1 100644 --- a/src/internal/base-component/use-base-component.ts +++ b/src/internal/base-component/use-base-component.ts @@ -7,11 +7,13 @@ import { ComponentConfiguration, initAwsUiVersions, useComponentMetadata, + useComponentMetrics, + useFocusVisible, } from "@cloudscape-design/component-toolkit/internal"; -import { PACKAGE_SOURCE, PACKAGE_VERSION } from "../environment"; -import useFocusVisible from "../utils/focus-visible"; -import { useTelemetry } from "./use-telemetry"; +import { PACKAGE_SOURCE, PACKAGE_VERSION, THEME } from "../environment"; +import { getVisualTheme } from "../utils/get-visual-theme"; +import { useVisualRefresh } from "./use-visual-refresh"; initAwsUiVersions(PACKAGE_SOURCE, PACKAGE_VERSION); @@ -25,8 +27,14 @@ export interface InternalBaseComponentProps { * root DOM node and emits the telemetry for this component. */ export default function useBaseComponent(componentName: string, config?: ComponentConfiguration) { - useTelemetry(componentName, config); + const isVisualRefresh = useVisualRefresh(); + const theme = getVisualTheme(THEME, isVisualRefresh); + useComponentMetrics(componentName, { packageSource: PACKAGE_SOURCE, packageVersion: PACKAGE_VERSION, theme }, config); useFocusVisible(); - const elementRef = useComponentMetadata(componentName, PACKAGE_VERSION); + const elementRef = useComponentMetadata(componentName, { + packageName: PACKAGE_SOURCE, + version: PACKAGE_VERSION, + theme, + }); return { __internalRootRef: elementRef }; } diff --git a/src/internal/base-component/use-telemetry.ts b/src/internal/base-component/use-telemetry.ts deleted file mode 100644 index 3327ce4..0000000 --- a/src/internal/base-component/use-telemetry.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { ComponentConfiguration, useComponentMetrics } from "@cloudscape-design/component-toolkit/internal"; - -import { PACKAGE_SOURCE, PACKAGE_VERSION, THEME } from "../environment"; -import { getVisualTheme } from "../utils/get-visual-theme"; -import { useVisualRefresh } from "./use-visual-refresh"; - -export function useTelemetry(componentName: string, config?: ComponentConfiguration) { - const isVisualRefresh = useVisualRefresh(); - const theme = getVisualTheme(THEME, isVisualRefresh); - useComponentMetrics(componentName, { packageSource: PACKAGE_SOURCE, packageVersion: PACKAGE_VERSION, theme }, config); -} diff --git a/src/internal/utils/__tests__/focus-visible.test.tsx b/src/internal/utils/__tests__/focus-visible.test.tsx deleted file mode 100644 index f310d23..0000000 --- a/src/internal/utils/__tests__/focus-visible.test.tsx +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { cleanup, fireEvent, render } from "@testing-library/react"; -import { afterEach, describe, expect, test, vi } from "vitest"; - -import useFocusVisible from "../../../../lib/components/internal/utils/focus-visible"; - -function Fixture() { - useFocusVisible(); - return ; -} - -describe("Focus visible", () => { - afterEach(() => { - cleanup(); - }); - - test("should disable focus by default", () => { - render(); - expect(document.body).not.toHaveAttribute("data-awsui-focus-visible"); - }); - - [ - { key: "Shift", keyCode: 16 }, - { key: "Alt", keyCode: 17 }, - { key: "Control", keyCode: 18 }, - { key: "Meta", keyCode: 91 }, - ].forEach((key) => { - test(`should not enable focus when ${key.key} key is pressed`, () => { - render(); - fireEvent.keyDown(document.body, key); - expect(document.body).not.toHaveAttribute("data-awsui-focus-visible"); - }); - }); - - test(`should enable focus when shift-tab is pressed`, () => { - render(); - fireEvent.keyDown(document.body, { key: "Tab", keyCode: 65, shiftKey: true }); - expect(document.body).toHaveAttribute("data-awsui-focus-visible", "true"); - }); - - test("should enable focus when keyboard interaction happened", () => { - render(); - fireEvent.keyDown(document.body); - expect(document.body).toHaveAttribute("data-awsui-focus-visible", "true"); - }); - - test("should disable focus when mouse is used after keyboard", () => { - render(); - fireEvent.keyDown(document.body); - fireEvent.mouseDown(document.body); - expect(document.body).not.toHaveAttribute("data-awsui-focus-visible"); - }); - - test("should work with multiple components", () => { - render( - <> - - - , - ); - fireEvent.keyDown(document.body); - expect(document.body).toHaveAttribute("data-awsui-focus-visible", "true"); - }); - - test("should add listeners only once", () => { - vi.spyOn(document, "addEventListener"); - vi.spyOn(document, "removeEventListener"); - const { rerender } = render( - <> - - - , - ); - expect(document.addEventListener).toHaveBeenCalledTimes(2); - expect(document.removeEventListener).toHaveBeenCalledTimes(0); - rerender(); - expect(document.removeEventListener).toHaveBeenCalledTimes(0); - rerender(); - expect(document.removeEventListener).toHaveBeenCalledTimes(2); - }); - - test("should initialize late components with updated state", () => { - const { rerender } = render(); - fireEvent.keyDown(document.body); - expect(document.body).toHaveAttribute("data-awsui-focus-visible", "true"); - rerender(); - expect(document.body).toHaveAttribute("data-awsui-focus-visible", "true"); - }); -}); diff --git a/src/internal/utils/focus-visible.ts b/src/internal/utils/focus-visible.ts deleted file mode 100644 index faee34e..0000000 --- a/src/internal/utils/focus-visible.ts +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 -import { useEffect } from "react"; - -import { KeyCode } from "../keycode"; - -export function isModifierKey(event: KeyboardEvent) { - // we do not want to highlight focused element - // when special keys are pressed - return [KeyCode.shift, KeyCode.alt, KeyCode.control, KeyCode.meta].indexOf(event.keyCode) > -1; -} - -function setIsKeyboard(active: boolean) { - if (active) { - document.body.setAttribute("data-awsui-focus-visible", "true"); - } else { - document.body.removeAttribute("data-awsui-focus-visible"); - } -} - -function handleMousedown() { - return setIsKeyboard(false); -} - -function handleKeydown(event: KeyboardEvent) { - if (!isModifierKey(event)) { - setIsKeyboard(true); - } -} - -let componentsCount = 0; - -function addListeners() { - document.addEventListener("mousedown", handleMousedown); - document.addEventListener("keydown", handleKeydown); -} - -function removeListeners() { - document.removeEventListener("mousedown", handleMousedown); - document.removeEventListener("keydown", handleKeydown); -} - -export default function useFocusVisible() { - useEffect(() => { - if (componentsCount === 0) { - addListeners(); - } - componentsCount++; - return () => { - componentsCount--; - if (componentsCount === 0) { - removeListeners(); - } - }; - }, []); -}