From eb1a54129a60683fbb4447dba03149464fe55c46 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 26 Jun 2025 14:46:57 -0500 Subject: [PATCH] chore: Jest to vitestga! --- jest-component-unit-tests/babel.config.json | 3 - .../billing.jesttest.tsx | 299 --------------- jest-component-unit-tests/jest.config.ts | 25 -- jest-component-unit-tests/tsconfig.json | 35 -- package.json | 3 +- src/components/billing.test.tsx | 347 ++++++++++++++++++ 6 files changed, 348 insertions(+), 364 deletions(-) delete mode 100644 jest-component-unit-tests/babel.config.json delete mode 100644 jest-component-unit-tests/billing.jesttest.tsx delete mode 100644 jest-component-unit-tests/jest.config.ts delete mode 100644 jest-component-unit-tests/tsconfig.json create mode 100644 src/components/billing.test.tsx diff --git a/jest-component-unit-tests/babel.config.json b/jest-component-unit-tests/babel.config.json deleted file mode 100644 index 4cd0f6e82..000000000 --- a/jest-component-unit-tests/babel.config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["babel-preset-vite"] -} diff --git a/jest-component-unit-tests/billing.jesttest.tsx b/jest-component-unit-tests/billing.jesttest.tsx deleted file mode 100644 index 263cf6e13..000000000 --- a/jest-component-unit-tests/billing.jesttest.tsx +++ /dev/null @@ -1,299 +0,0 @@ -// Test runner -import {expect, test, beforeAll, afterEach, afterAll} from '@jest/globals'; - -// Render mocking, DOM querying -import {act, render, within} from '@testing-library/react' -import '@testing-library/jest-dom' // Required for .toHaveStyle - -// Request mocking -import {http, HttpResponse} from 'msw' -import {setupServer} from 'msw/node' - -// React and XState code to test -import { Models } from '@kittycad/lib' -import { createActor } from 'xstate' -import { - BillingRemaining, - BillingRemainingMode, -} from '@src/components/BillingRemaining' -import { BillingDialog, } from '@src/components/BillingDialog' -import { BILLING_CONTEXT_DEFAULTS, billingMachine, BillingTransition } from '@src/machines/billingMachine' - -// Setup basic request mocking -const server = setupServer() -beforeAll(() => server.listen()) -afterEach(() => server.resetHandlers()) -afterAll(() => server.close()) - -// Data ripped from docs.zoo.dev -const createUserPaymentBalanceResponse = (opts: { - monthlyApiCreditsRemaining, - stableApiCreditsRemaining, -}): Models['CustomerBalance_type'] => ({ - "created_at": "2025-05-05T16:05:47.317Z", - "id": "de607b7e-90ba-4977-8561-16e8a9ea0e50", - "map_id": "d7f7de34-9bc3-4b8b-9951-cdee03fc792d", - "modeling_app_enterprise_price": { - "type": "enterprise" - }, - "monthly_api_credits_remaining": opts.monthlyApiCreditsRemaining, - "monthly_api_credits_remaining_monetary_value": "22.47", - "stable_api_credits_remaining": opts.stableApiCreditsRemaining, - "stable_api_credits_remaining_monetary_value": "18.91", - "subscription_details": undefined, - "subscription_id": "Hnd3jalJkHA3lb1YexOTStZtPYHTM", - "total_due": "100.08", - "updated_at": "2025-05-05T16:05:47.317Z" -}) - -const createOrgResponse = (opts: { -}): Models['Org_type'] => ({ - "allow_users_in_domain_to_auto_join": true, - "billing_email": "m@dN9MCH.com", - "billing_email_verified": "2025-05-05T18:52:02.021Z", - "block": "payment_method_failed", - "can_train_on_data": true, - "created_at": "2025-05-05T18:52:02.021Z", - "domain": "Ctxde1hpG8xTvvlef5SEPm7", - "id": "78432284-8660-46bf-ac65-d00bf9b18c3e", - "image": "https://Rt0.yK.com/R2SoRtl/tpUdckyDJ", - "name": "AevRR4w42KdkA487dh", - "phone": "+1-696-641-2790", - "stripe_id": "sCfjVscpLyOBYUWO7Vlx", - "updated_at": "2025-05-05T18:52:02.021Z" -}) - -const createUserPaymentSubscriptionsResponse = (opts: { - monthlyPayAsYouGoApiCreditsTotal, - name, -}): Models['ZooProductSubscriptions_type'] => ({ - "modeling_app": { - "annual_discount": 10, - "description": "1ztERftrU3L3yOnv5epTLcM", - "endpoints_included": [ - "modeling" - ], - "features": [ - { - "info": "zZcZKHejXabT5HMZDkSkDGD2bfzkAt" - } - ], - "monthly_pay_as_you_go_api_credits": opts.monthlyPayAsYouGoApiCreditsTotal, - "monthly_pay_as_you_go_api_credits_monetary_value": "55.85", - "name": opts.name, - "pay_as_you_go_api_credit_price": "18.49", - "price": { - "interval": "year", - "price": "50.04", - "type": "per_user" - }, - "share_links": [ - "password_protected" - ], - "support_tier": "community", - "training_data_behavior": "default_on", - "type": { - "saml_sso": true, - "type": "organization" - }, - "zoo_tools_included": [ - "text_to_cad" - ] - } -}) - -test('Shows a loading spinner when uninitialized credit count', async () => { - server.use( - http.get('*/user/payment/balance', (req, res, ctx) => { - return HttpResponse.json({}) - }), - http.get('*/user/payment/subscriptions', (req, res, ctx) => { - return HttpResponse.json({}) - }), - http.get('*/org', (req, res, ctx) => { - return new HttpResponse(403) - }), - ) - - const billingActor = createActor(billingMachine, { input: BILLING_CONTEXT_DEFAULTS }).start() - - const { queryByTestId } = render() - - await expect(queryByTestId('spinner')).toBeVisible() -}) - -test('Shows the total credits for Unknown subscription', async () => { - const data = { - balance: { - monthlyApiCreditsRemaining: 10, - stableApiCreditsRemaining: 25, - }, - subscriptions: { - monthlyPayAsYouGoApiCreditsTotal: 20, - name: "unknown", - } - } - - server.use( - http.get('*/user/payment/balance', (req, res, ctx) => { - return HttpResponse.json(createUserPaymentBalanceResponse(data.balance)) - }), - http.get('*/user/payment/subscriptions', (req, res, ctx) => { - return HttpResponse.json(createUserPaymentSubscriptionsResponse(data.subscriptions)) - }), - http.get('*/org', (req, res, ctx) => { - return new HttpResponse(403) - }), - ) - - const billingActor = createActor(billingMachine, { input: BILLING_CONTEXT_DEFAULTS }).start() - - const { queryByTestId } = render() - - await act(() => { - billingActor.send({ type: BillingTransition.Update, apiToken: "it doesn't matter wtf this is :)" }) - }) - - const totalCredits = data.balance.monthlyApiCreditsRemaining + data.balance.stableApiCreditsRemaining - await expect(billingActor.getSnapshot().context.credits).toBe(totalCredits) - await within(queryByTestId('billing-credits')).getByText(totalCredits) -}) - -test('Progress bar reflects ratio left of Free subscription', async () => { - const data = { - balance: { - monthlyApiCreditsRemaining: 10, - stableApiCreditsRemaining: 0, - }, - subscriptions: { - monthlyPayAsYouGoApiCreditsTotal: 20, - name: "free", - } - } - - server.use( - http.get('*/user/payment/balance', (req, res, ctx) => { - return HttpResponse.json(createUserPaymentBalanceResponse(data.balance)) - }), - http.get('*/user/payment/subscriptions', (req, res, ctx) => { - return HttpResponse.json(createUserPaymentSubscriptionsResponse(data.subscriptions)) - }), - http.get('*/org', (req, res, ctx) => { - return new HttpResponse(403) - }), - ) - - const billingActor = createActor(billingMachine, { input: BILLING_CONTEXT_DEFAULTS }).start() - - const { queryByTestId } = render() - - await act(() => { - billingActor.send({ type: BillingTransition.Update, apiToken: "it doesn't matter wtf this is :)" }) - }) - - const totalCredits = data.balance.monthlyApiCreditsRemaining + data.balance.stableApiCreditsRemaining - const monthlyCredits = data.subscriptions.monthlyPayAsYouGoApiCreditsTotal - const context = billingActor.getSnapshot().context - await expect(context.credits).toBe(totalCredits) - await expect(context.allowance).toBe(monthlyCredits) - - await within(queryByTestId('billing-credits')).getByText(totalCredits) - await expect(queryByTestId('billing-remaining-progress-bar-inner')).toHaveStyle({ - width: "50.00%" - }) -}) -test('Shows infinite credits for Pro subscription', async () => { - const data = { - // These are all ignored - balance: { - monthlyApiCreditsRemaining: 10, - stableApiCreditsRemaining: 0, - }, - subscriptions: { - // This should be ignored because it's Pro tier. - monthlyPayAsYouGoApiCreditsTotal: 20, - name: "pro", - } - } - - server.use( - http.get('*/user/payment/balance', (req, res, ctx) => { - return HttpResponse.json(createUserPaymentBalanceResponse(data.balance)) - }), - http.get('*/user/payment/subscriptions', (req, res, ctx) => { - return HttpResponse.json(createUserPaymentSubscriptionsResponse(data.subscriptions)) - }), - http.get('*/org', (req, res, ctx) => { - return new HttpResponse(403) - }), - ) - - const billingActor = createActor(billingMachine, { input: BILLING_CONTEXT_DEFAULTS }).start() - - const { queryByTestId } = render() - - await act(() => { - billingActor.send({ type: BillingTransition.Update, apiToken: "aosetuhsatuh" }) - }) - - await expect(queryByTestId('infinity')).toBeVisible() - // You can't do `.not.toBeVisible` folks. When the query fails it's because - // no element could be found. toBeVisible should be used on an element - // that's found but may not be visible due to `display` or others. - await expect(queryByTestId('billing-remaining-progress-bar-inline')).toBe(null) -}) -test('Shows infinite credits for Enterprise subscription', async () => { - const data = { - // These are all ignored, user is part of an org. - balance: { - monthlyApiCreditsRemaining: 10, - stableApiCreditsRemaining: 0, - }, - subscriptions: { - // This should be ignored because it's Pro tier. - monthlyPayAsYouGoApiCreditsTotal: 20, - // This should be ignored because the user is part of an Org. - name: "free", - } - } - - server.use( - http.get('*/user/payment/balance', (req, res, ctx) => { - return HttpResponse.json(createUserPaymentBalanceResponse(data.balance)) - }), - http.get('*/user/payment/subscriptions', (req, res, ctx) => { - return HttpResponse.json(createUserPaymentSubscriptionsResponse(data.subscriptions)) - }), - // Ok finally the first use of an org lol - http.get('*/org', (req, res, ctx) => { - return HttpResponse.json(createOrgResponse()) - }), - ) - - const billingActor = createActor(billingMachine, { input: BILLING_CONTEXT_DEFAULTS }).start() - - const { queryByTestId } = render() - - await act(() => { - billingActor.send({ type: BillingTransition.Update, apiToken: "aosetuhsatuh" }) - }) - - // The result should be the same as Pro users. - await expect(queryByTestId('infinity')).toBeVisible() - await expect(queryByTestId('billing-remaining-progress-bar-inline')).toBe(null) -}) diff --git a/jest-component-unit-tests/jest.config.ts b/jest-component-unit-tests/jest.config.ts deleted file mode 100644 index 29ede1387..000000000 --- a/jest-component-unit-tests/jest.config.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { pathsToModuleNameMapper } from 'ts-jest' -// In the following statement, replace `./tsconfig` with the path to your `tsconfig` file -// which contains the path mapping (ie the `compilerOptions.paths` option): -import { compilerOptions } from './tsconfig.json' -import type { Config } from 'jest' - -const jestConfig: Config = { - // [...] - preset: "ts-jest", - transform: { - "^.+\.tsx?$": ["ts-jest",{ babelConfig: true }], - }, - testEnvironment: "jest-fixed-jsdom", - // Include both standard test patterns and our custom .jesttest. pattern - testMatch: [ - "**/__tests__/**/*.[jt]s?(x)", - "**/?(*.)+(spec|test).[tj]s?(x)", - "**/?(*.)+(jesttest).[tj]s?(x)" - ], - // TAG: paths, path, baseUrl, alias - // This is necessary to use tsconfig path aliases. - moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '/../' }), -} - -export default jestConfig diff --git a/jest-component-unit-tests/tsconfig.json b/jest-component-unit-tests/tsconfig.json deleted file mode 100644 index 8e8f9e738..000000000 --- a/jest-component-unit-tests/tsconfig.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "compilerOptions": { - "noErrorTruncation": true, - "baseUrl": "../../", - "paths": { - "@kittycad/codemirror-lsp-client": [ - "./packages/codemirror-lsp-client/src/index.ts" - ], - "@kittycad/codemirror-lang-kcl": [ - "./packages/codemirror-lang-kcl/src/index.ts" - ], - "@rust/*": ["./rust/*"], - "@e2e/*": ["./e2e/*"], - "@src/*": ["./src/*"], - "@public/*": ["./public/*"], - "@root/*": ["./*"] - }, - "target": "esnext", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "commonjs", - "moduleResolution": "node", - "resolveJsonModule": true, - "composite": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx" - } -} diff --git a/package.json b/package.json index 5d56502a2..8b12ab3c2 100644 --- a/package.json +++ b/package.json @@ -131,8 +131,7 @@ "test": "vitest --mode development", "test:rust": "(cd rust && just test && just lint)", "test:snapshots": "cross-env TARGET=web NODE_ENV=development playwright test --config=playwright.config.ts --grep=@snapshot --trace=on", - "test:unit": "vitest run --mode development --exclude **/jest-component-unit-tests/*", - "test:unit:components": "jest -c jest-component-unit-tests/jest.config.ts --rootDir jest-component-unit-tests/", + "test:unit": "vitest run --mode development", "test:e2e:web": "cross-env TARGET=web NODE_ENV=development playwright test --config=playwright.config.ts --project=\"Google Chrome\" --grep=@web", "test:e2e:desktop": "cross-env TARGET=desktop playwright test --config=playwright.electron.config.ts --grep-invert=\"@snapshot|@web\"", "test:e2e:desktop:local": "cross-env TARGET=desktop npm run tronb:vite:dev && playwright test --config=playwright.electron.config.ts --grep-invert=\"@snapshot|@web\" --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"", diff --git a/src/components/billing.test.tsx b/src/components/billing.test.tsx new file mode 100644 index 000000000..f98b4cf56 --- /dev/null +++ b/src/components/billing.test.tsx @@ -0,0 +1,347 @@ +// @ts-nocheck +// This was moved out of the Jest workflow. Needs proper TS TLC. + +// Test runner + +// Render mocking, DOM querying +import { act, render, within } from '@testing-library/react' +import '@testing-library/jest-dom' // Required for .toHaveStyle + +// Request mocking +import { http, HttpResponse } from 'msw' +import { setupServer } from 'msw/node' + +// React and XState code to test +import type { Models } from '@kittycad/lib' +import { createActor } from 'xstate' +import { + BillingRemaining, + BillingRemainingMode, +} from '@src/components/BillingRemaining' +import { + BILLING_CONTEXT_DEFAULTS, + billingMachine, + BillingTransition, +} from '@src/machines/billingMachine' + +// Setup basic request mocking +const server = setupServer() +beforeAll(() => server.listen()) +afterEach(() => server.resetHandlers()) +afterAll(() => server.close()) + +// Data ripped from docs.zoo.dev +const createUserPaymentBalanceResponse = (opts: { + monthlyApiCreditsRemaining + stableApiCreditsRemaining +}): Models['CustomerBalance_type'] => ({ + created_at: '2025-05-05T16:05:47.317Z', + id: 'de607b7e-90ba-4977-8561-16e8a9ea0e50', + map_id: 'd7f7de34-9bc3-4b8b-9951-cdee03fc792d', + modeling_app_enterprise_price: { + type: 'enterprise', + }, + monthly_api_credits_remaining: opts.monthlyApiCreditsRemaining, + monthly_api_credits_remaining_monetary_value: '22.47', + stable_api_credits_remaining: opts.stableApiCreditsRemaining, + stable_api_credits_remaining_monetary_value: '18.91', + subscription_details: undefined, + subscription_id: 'Hnd3jalJkHA3lb1YexOTStZtPYHTM', + total_due: '100.08', + updated_at: '2025-05-05T16:05:47.317Z', +}) + +const createOrgResponse = (): Models['Org_type'] => ({ + allow_users_in_domain_to_auto_join: true, + billing_email: 'm@dN9MCH.com', + billing_email_verified: '2025-05-05T18:52:02.021Z', + block: 'payment_method_failed', + can_train_on_data: true, + created_at: '2025-05-05T18:52:02.021Z', + domain: 'Ctxde1hpG8xTvvlef5SEPm7', + id: '78432284-8660-46bf-ac65-d00bf9b18c3e', + image: 'https://Rt0.yK.com/R2SoRtl/tpUdckyDJ', + name: 'AevRR4w42KdkA487dh', + phone: '+1-696-641-2790', + stripe_id: 'sCfjVscpLyOBYUWO7Vlx', + updated_at: '2025-05-05T18:52:02.021Z', +}) + +const createUserPaymentSubscriptionsResponse = (opts: { + monthlyPayAsYouGoApiCreditsTotal + name +}): Models['ZooProductSubscriptions_type'] => ({ + modeling_app: { + annual_discount: 10, + description: '1ztERftrU3L3yOnv5epTLcM', + endpoints_included: ['modeling'], + features: [ + { + info: 'zZcZKHejXabT5HMZDkSkDGD2bfzkAt', + }, + ], + monthly_pay_as_you_go_api_credits: opts.monthlyPayAsYouGoApiCreditsTotal, + monthly_pay_as_you_go_api_credits_monetary_value: '55.85', + name: opts.name, + pay_as_you_go_api_credit_price: '18.49', + price: { + interval: 'year', + price: '50.04', + type: 'per_user', + }, + share_links: ['password_protected'], + support_tier: 'community', + training_data_behavior: 'default_on', + type: { + saml_sso: true, + type: 'organization', + }, + zoo_tools_included: ['text_to_cad'], + }, +}) + +test('Shows a loading spinner when uninitialized credit count', async () => { + server.use( + http.get('*/user/payment/balance', (req, res, ctx) => { + return HttpResponse.json({}) + }), + http.get('*/user/payment/subscriptions', (req, res, ctx) => { + return HttpResponse.json({}) + }), + http.get('*/org', (req, res, ctx) => { + return new HttpResponse(403) + }) + ) + + const billingActor = createActor(billingMachine, { + input: BILLING_CONTEXT_DEFAULTS, + }).start() + + const { queryByTestId } = render( + + ) + + await expect(queryByTestId('spinner')).toBeVisible() +}) + +test('Shows the total credits for Unknown subscription', async () => { + const data = { + balance: { + monthlyApiCreditsRemaining: 10, + stableApiCreditsRemaining: 25, + }, + subscriptions: { + monthlyPayAsYouGoApiCreditsTotal: 20, + name: 'unknown', + }, + } + + server.use( + http.get('*/user/payment/balance', (req, res, ctx) => { + return HttpResponse.json(createUserPaymentBalanceResponse(data.balance)) + }), + http.get('*/user/payment/subscriptions', (req, res, ctx) => { + return HttpResponse.json( + createUserPaymentSubscriptionsResponse(data.subscriptions) + ) + }), + http.get('*/org', (req, res, ctx) => { + return new HttpResponse(403) + }) + ) + + const billingActor = createActor(billingMachine, { + input: BILLING_CONTEXT_DEFAULTS, + }).start() + + const { queryByTestId } = render( + + ) + + await act(() => { + billingActor.send({ + type: BillingTransition.Update, + apiToken: "it doesn't matter wtf this is :)", + }) + }) + + const totalCredits = + data.balance.monthlyApiCreditsRemaining + + data.balance.stableApiCreditsRemaining + await expect(billingActor.getSnapshot().context.credits).toBe(totalCredits) + await within(queryByTestId('billing-credits')).getByText(totalCredits) +}) + +test('Progress bar reflects ratio left of Free subscription', async () => { + const data = { + balance: { + monthlyApiCreditsRemaining: 10, + stableApiCreditsRemaining: 0, + }, + subscriptions: { + monthlyPayAsYouGoApiCreditsTotal: 20, + name: 'free', + }, + } + + server.use( + http.get('*/user/payment/balance', (req, res, ctx) => { + return HttpResponse.json(createUserPaymentBalanceResponse(data.balance)) + }), + http.get('*/user/payment/subscriptions', (req, res, ctx) => { + return HttpResponse.json( + createUserPaymentSubscriptionsResponse(data.subscriptions) + ) + }), + http.get('*/org', (req, res, ctx) => { + return new HttpResponse(403) + }) + ) + + const billingActor = createActor(billingMachine, { + input: BILLING_CONTEXT_DEFAULTS, + }).start() + + const { queryByTestId } = render( + + ) + + await act(() => { + billingActor.send({ + type: BillingTransition.Update, + apiToken: "it doesn't matter wtf this is :)", + }) + }) + + const totalCredits = + data.balance.monthlyApiCreditsRemaining + + data.balance.stableApiCreditsRemaining + const monthlyCredits = data.subscriptions.monthlyPayAsYouGoApiCreditsTotal + const context = billingActor.getSnapshot().context + await expect(context.credits).toBe(totalCredits) + await expect(context.allowance).toBe(monthlyCredits) + + await within(queryByTestId('billing-credits')).getByText(totalCredits) + await expect( + queryByTestId('billing-remaining-progress-bar-inner') + ).toHaveStyle({ + width: '50.00%', + }) +}) +test('Shows infinite credits for Pro subscription', async () => { + const data = { + // These are all ignored + balance: { + monthlyApiCreditsRemaining: 10, + stableApiCreditsRemaining: 0, + }, + subscriptions: { + // This should be ignored because it's Pro tier. + monthlyPayAsYouGoApiCreditsTotal: 20, + name: 'pro', + }, + } + + server.use( + http.get('*/user/payment/balance', (req, res, ctx) => { + return HttpResponse.json(createUserPaymentBalanceResponse(data.balance)) + }), + http.get('*/user/payment/subscriptions', (req, res, ctx) => { + return HttpResponse.json( + createUserPaymentSubscriptionsResponse(data.subscriptions) + ) + }), + http.get('*/org', (req, res, ctx) => { + return new HttpResponse(403) + }) + ) + + const billingActor = createActor(billingMachine, { + input: BILLING_CONTEXT_DEFAULTS, + }).start() + + const { queryByTestId } = render( + + ) + + await act(() => { + billingActor.send({ + type: BillingTransition.Update, + apiToken: 'aosetuhsatuh', + }) + }) + + await expect(queryByTestId('infinity')).toBeVisible() + // You can't do `.not.toBeVisible` folks. When the query fails it's because + // no element could be found. toBeVisible should be used on an element + // that's found but may not be visible due to `display` or others. + await expect(queryByTestId('billing-remaining-progress-bar-inline')).toBe( + null + ) +}) +test('Shows infinite credits for Enterprise subscription', async () => { + const data = { + // These are all ignored, user is part of an org. + balance: { + monthlyApiCreditsRemaining: 10, + stableApiCreditsRemaining: 0, + }, + subscriptions: { + // This should be ignored because it's Pro tier. + monthlyPayAsYouGoApiCreditsTotal: 20, + // This should be ignored because the user is part of an Org. + name: 'free', + }, + } + + server.use( + http.get('*/user/payment/balance', (req, res, ctx) => { + return HttpResponse.json(createUserPaymentBalanceResponse(data.balance)) + }), + http.get('*/user/payment/subscriptions', (req, res, ctx) => { + return HttpResponse.json( + createUserPaymentSubscriptionsResponse(data.subscriptions) + ) + }), + // Ok finally the first use of an org lol + http.get('*/org', (req, res, ctx) => { + return HttpResponse.json(createOrgResponse()) + }) + ) + + const billingActor = createActor(billingMachine, { + input: BILLING_CONTEXT_DEFAULTS, + }).start() + + const { queryByTestId } = render( + + ) + + await act(() => { + billingActor.send({ + type: BillingTransition.Update, + apiToken: 'aosetuhsatuh', + }) + }) + + // The result should be the same as Pro users. + await expect(queryByTestId('infinity')).toBeVisible() + await expect(queryByTestId('billing-remaining-progress-bar-inline')).toBe( + null + ) +})