From af92ad672f7617210a30284d507e7d581f949211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Buchowski?= Date: Mon, 11 Aug 2025 10:13:09 +0200 Subject: [PATCH] Revert "cleanup svg icon usage, types and href generation (#13746)" This reverts commit 5e7a2a13ff379b1d4b890d192d75ac11998308ca. --- .../dashboard/actions/index.ts | 34 ++++-- .../dashboard/mock/react-stripe.tsx | 110 ++++++++++++++++++ .../integration-test/dashboard/mock/stripe.ts | 26 +++++ app/gui/package.json | 2 + app/gui/playwright.config.ts | 4 +- .../src/dashboard/components/Icon/Icon.tsx | 9 +- .../src/project-view/assets/icon-missing.svg | 6 - .../GraphEditor/widgets/WidgetIcon.vue | 5 +- .../components/StandaloneButton.vue | 5 +- .../src/project-view/components/SvgButton.vue | 5 +- .../src/project-view/components/SvgIcon.vue | 20 +++- .../visualizations/TableVisualization.vue | 9 +- .../components/visualizations/toolbar.ts | 5 +- app/gui/src/project-view/util/getIconName.ts | 6 +- app/gui/src/project-view/util/icons.ts | 46 +++++--- app/gui/vite.test.config.ts | 8 ++ pnpm-lock.yaml | 27 +++++ 17 files changed, 269 insertions(+), 58 deletions(-) create mode 100644 app/gui/integration-test/dashboard/mock/react-stripe.tsx create mode 100644 app/gui/integration-test/dashboard/mock/stripe.ts delete mode 100644 app/gui/src/project-view/assets/icon-missing.svg diff --git a/app/gui/integration-test/dashboard/actions/index.ts b/app/gui/integration-test/dashboard/actions/index.ts index aab8c37decb5..504db8e6227a 100644 --- a/app/gui/integration-test/dashboard/actions/index.ts +++ b/app/gui/integration-test/dashboard/actions/index.ts @@ -14,7 +14,7 @@ import { type LocalTrackedCalls, } from './localApi' import LoginPageActions from './LoginPageActions' -import { passAgreementsDialog, TEXT, type MockParams } from './utilities' +import { passAgreementsDialog, TEXT, VALID_PASSWORD, type MockParams } from './utilities' export * from './utilities' export const getText = (key: TextId, ...replacements: Replacements[TextId]) => { @@ -28,23 +28,35 @@ export function getAuthFilePath() { } /** Perform a successful login. */ -async function loginIfNeeded(page: Page, actions: LoginPageActions) { +async function login({ page }: MockParams, email = 'email@example.com', password = VALID_PASSWORD) { const authFile = getAuthFilePath() + + await waitForLoaded(page) const isLoggedIn = (await page.getByTestId('before-auth-layout').count()) === 0 + if (isLoggedIn) { test.info().annotations.push({ type: 'skip', description: 'Already logged in', }) - const agreementModalVisible = (await page.locator('#agreements-modal').count()) > 0 - if (agreementModalVisible) { - await passAgreementsDialog({ page }) - await page.context().storageState({ path: authFile }) - } - } else { - await actions.login() - await page.context().storageState({ path: authFile }) + return } + + return test.step('Login', async () => { + test.info().annotations.push({ + type: 'Login', + description: 'Performing login', + }) + await page.getByPlaceholder(TEXT.emailPlaceholder).fill(email) + await page.getByPlaceholder(TEXT.passwordPlaceholder).fill(password) + await page.getByRole('button', { name: TEXT.login, exact: true }).getByText(TEXT.login).click() + + await expect(page.getByText(TEXT.loadingAppMessage)).not.toBeVisible() + + await passAgreementsDialog({ page }) + + await page.context().storageState({ path: authFile }) + }) } /** Wait for the page to load. */ @@ -132,7 +144,7 @@ export function mockAllAndLogin({ const actions = mockAll({ page, setupAPI, setupLocalAPI }) const driveActions = actions - .step('Pass login screen', (page, _ctx, actions) => loginIfNeeded(page, actions)) + .step('Login', (page) => login({ page })) .step('Wait for dashboard to load', waitForDashboardToLoad) .into(DrivePageActions) return goToCloudFirst ? driveActions.goToCategory.cloud() : driveActions diff --git a/app/gui/integration-test/dashboard/mock/react-stripe.tsx b/app/gui/integration-test/dashboard/mock/react-stripe.tsx new file mode 100644 index 000000000000..59b79c0b4a5a --- /dev/null +++ b/app/gui/integration-test/dashboard/mock/react-stripe.tsx @@ -0,0 +1,110 @@ +/** @file Mock for `@stripe/react-stripe-js` */ + +import type { + CardElementProps, + ElementsConsumer as StripeElementConsumer, + Elements as StripeElements, +} from '@stripe/react-stripe-js' +import { createContext, useContext, useEffect, useState } from 'react' + +/** */ +type ElementsContextValue = Parameters[0]['children']>[0] + +const ElementsContext = createContext(null!) + +/** Elements provider for Stripe. */ +export function Elements(...[props]: Parameters) { + const { stripe: stripeRaw, children } = props + const [stripe, setStripe] = useState(stripeRaw && 'then' in stripeRaw ? null : stripeRaw) + const [elements] = useState(() => { + return { + getElement: (type) => { + switch (type) { + case 'card': { + return CardElement + } + default: { + return (<>) as any + } + } + }, + } satisfies Partial< + ElementsContextValue['elements'] + > as unknown as ElementsContextValue['elements'] + }) + + useEffect(() => { + let canceled = false + if (stripeRaw && 'then' in stripeRaw) { + void stripeRaw.then((awaitedStripe) => { + if (!canceled) { + setStripe(awaitedStripe) + } + }) + } + return () => { + canceled = true + } + }, [stripeRaw]) + + return ( + stripe && ( + + {children} + + ) + ) +} + +/** Elements consumer for Stripe. */ +export function ElementsConsumer(...[props]: Parameters) { + return props.children(useContext(ElementsContext)) +} + +/** Card element for Stripe. */ +export function CardElement(props: CardElementProps) { + const { onReady: onReadyRaw, onChange: onChangeRaw } = props + const onReady = onReadyRaw ?? (() => {}) + const onChange = onChangeRaw ?? (() => {}) + + useEffect(() => { + onReady({ + blur: () => {}, + clear: () => {}, + destroy: () => {}, + focus: () => {}, + mount: () => {}, + unmount: () => {}, + update: () => {}, + on: () => null!, + off: () => null!, + once: () => null!, + }) + }, [onReady]) + + useEffect(() => { + onChange({ + elementType: 'card', + empty: false, + complete: true, + error: undefined, + value: { postalCode: '40001' }, + brand: 'mastercard', + }) + }, [onChange]) + + return <> +} + +export const useStripe = () => ({ + confirmCardSetup: () => {}, +}) + +export const useElements = () => ({ + getElement: () => {}, +}) diff --git a/app/gui/integration-test/dashboard/mock/stripe.ts b/app/gui/integration-test/dashboard/mock/stripe.ts new file mode 100644 index 000000000000..d28a13f605ff --- /dev/null +++ b/app/gui/integration-test/dashboard/mock/stripe.ts @@ -0,0 +1,26 @@ +/** @file Mock for `@stripe/stripe-js` */ +import type { Stripe } from '@stripe/stripe-js' + +export const loadStripe = (): Promise => + Promise.resolve({ + createPaymentMethod: () => + Promise.resolve({ + paymentMethod: { + id: '', + object: 'payment_method', + // eslint-disable-next-line camelcase + billing_details: { + address: null, + email: null, + name: null, + phone: null, + }, + created: Number(new Date()) / 1_000, + customer: null, + livemode: true, + metadata: {}, + type: '', + }, + error: undefined, + }), + } satisfies Partial as Partial as Stripe) diff --git a/app/gui/package.json b/app/gui/package.json index 84d86b2be9ea..90cd6d159fe5 100644 --- a/app/gui/package.json +++ b/app/gui/package.json @@ -67,6 +67,8 @@ "@react-aria/interactions": "3.23.0", "@sentry/vite-plugin": "^2.22.7", "@sentry/vue": "^7.120.2", + "@stripe/react-stripe-js": "^2.9.0", + "@stripe/stripe-js": "^3.5.0", "@tanstack/react-query": "5.59.20", "@tanstack/vue-query": "5.59.20", "@vue/reactivity": "^3.5.13", diff --git a/app/gui/playwright.config.ts b/app/gui/playwright.config.ts index 3163a1bff5ae..668b7765fb85 100644 --- a/app/gui/playwright.config.ts +++ b/app/gui/playwright.config.ts @@ -78,9 +78,7 @@ export default defineConfig({ fullyParallel: true, ...(WORKERS ? { workers: WORKERS } : {}), forbidOnly: isCI, - // Make test preview use the same port as test URL, so that svg icons are properly displayed. - // Unfortunately we can't make it work for both dashboard and project-view at the same time. - reporter: isCI ? [['list'], ['blob']] : [['html', { port: ports.projectView }]], + reporter: isCI ? [['list'], ['blob']] : [['html']], retries: isCI ? 1 : 0, use: { actionTimeout: 5000, diff --git a/app/gui/src/dashboard/components/Icon/Icon.tsx b/app/gui/src/dashboard/components/Icon/Icon.tsx index baf0b2af0f94..138d515ea9ca 100644 --- a/app/gui/src/dashboard/components/Icon/Icon.tsx +++ b/app/gui/src/dashboard/components/Icon/Icon.tsx @@ -12,8 +12,8 @@ import type { TestIdProps, } from '#/components/types' import { tv, type VariantProps } from '#/utilities/tailwindVariants' +import icons from '@/assets/icons.svg' import { isIconName, type Icon as PossibleIcon } from '@/util/iconMetadata/iconName' -import { svgUseHref } from '@/util/icons' import { memo } from 'react' import SvgMask from '../SvgMask' @@ -171,7 +171,12 @@ export function SvgUse(props: SvgUseProps) { preserveAspectRatio="xMidYMid slice" aria-label={alt} > -