You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用CreateRemixStub与Vitest测试组件时遇"Expression Expected"错误

Remix Hydrogen项目中AddToFavoritesButton测试报错"Expression Expected"

我基于Remix Run开发Shopify Hydrogen v2项目,为AddToFavoritesButton组件编写测试时,使用createRemixStub模拟隔离的Action来验证API与渲染逻辑,但执行npx vitest运行测试时,出现"Expression Expected"错误,错误截图如下:
测试错误截图

以下是相关文件代码:

components/AddToFavoritesButton.jsx

import React, {useCallback} from 'react';
import {useFetcher} from '@remix-run/react';

export default function AddToFavoritesButton({
  product_id,
  customer_id,
  isFavoriteProduct,
}) {
  const fetcher = useFetcher();
  const addToFavorites = useCallback(() => {
    fetcher.submit(
      {intent: 'add-to-favorites', product_id, customer_id},
      {method: 'post'},
    );
  }, [fetcher, product_id, customer_id]);

  const removeFromFavorites = useCallback(() => {
    fetcher.submit(
      {intent: 'remove-from-favorites', product_id, customer_id},
      {method: 'post'},
    );
  }, [fetcher, product_id, customer_id]);

  return (
    <>
      {isFavoriteProduct ? (
        <button onClick={removeFromFavorites}>Remove from favorites</button>
      ) : (
        <button onClick={addToFavorites}>Add to favorites</button>
      )}
    </>
  );
}

tests/favorite-button.test.js

import React from 'react';
import {render, waitFor, screen, fireEvent} from '@testing-library/react';
import {createRemixStub} from '@remix-run/testing';
import AddToFavoritesButton from '../app/components/AddToFavoritesButton';
import {describe, expect, it, beforeEach, afterEach, vi} from 'vitest';

// Mocking useFetcher from @remix-run/react
vi.mock('@remix-run/react', () => {
  return {
    useFetcher: vi.fn().mockReturnValue({state: 'idle'}),
  };
});

// Simulate the FavoriteProduct module's methods
const FavoriteProduct = {
  saveFavorite: vi.fn().mockResolvedValue({success: true}),
  deleteFavorite: vi.fn().mockResolvedValue({success: true}),
};

const fakeProduct = {
  product_id: '123',
  customer_id: 'abc',
  isFavoriteProduct: false,
};

describe('AddToFavoritesButton', () => {
  let RemixStub;

  beforeEach(() => {
    RemixStub = createRemixStub([
      {
        path: '/',
        element: <FavoriteProductTest />,
        loader: () => fakeProduct,
      },
      {
        path: '/favorites',
        action: async ({request}) => {
          const formData = await request.formData();
          const intent = formData.get('intent');
          if (intent === 'add-to-favorites') {
            fakeProduct.isFavoriteProduct = true;
            await FavoriteProduct.saveFavorite(
              formData.get('product_id'),
              formData.get('customer_id'),
            );
          } else if (intent === 'remove-from-favorites') {
            fakeProduct.isFavoriteProduct = false;
            await FavoriteProduct.deleteFavorite(
              formData.get('product_id'),
              formData.get('customer_id'),
            );
          }
          return null;
        },
      },
    ]);
  });

  function FavoriteProductTest() {
    const {product_id, customer_id, isFavoriteProduct} = fakeProduct;
    return (
      <AddToFavoritesButton
        product_id={product_id}
        customer_id={customer_id}
        isFavoriteProduct={isFavoriteProduct}
      />
    );
  }

  afterEach(() => {
    vi.clearAllMocks();
    fakeProduct.isFavoriteProduct = false;
  });

  it('should add to favorites', async () => {
    render(<RemixStub initialEntries={['/']} />);

    fireEvent.click(screen.getByText('Add to favorites'));

    expect(FavoriteProduct.saveFavorite).toHaveBeenCalledWith(
      fakeProduct.product_id,
      fakeProduct.customer_id,
    );
    await waitFor(() =>
      expect(screen.getByText('Remove from favorites')).toBeInTheDocument(),
    );
    expect(fakeProduct.isFavoriteProduct).toBe(true);
  });
});

vitest.config.js

import {defineConfig} from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: './setup-tests.js', // if you have a setup file
  },
});

setup-tests.js

import '@testing-library/jest-dom';

package.json

{
  "name": "hydrogen-certification",
  "private": true,
  "sideEffects": false,
  "version": "2024.4.4",
  "type": "module",
  "scripts": {
    "build": "shopify hydrogen build --codegen",
    "dev": "shopify hydrogen dev --codegen",
    "preview": "npm run build && shopify hydrogen preview",
    "lint": "eslint --no-error-on-unmatched-pattern --ext .js,.ts,.jsx,.tsx .",
    "codegen": "shopify hydrogen codegen"
  },
  "prettier": "@shopify/prettier-config",
  "dependencies": {
    "@remix-run/react": "^2.8.0",
    "@remix-run/server-runtime": "^2.8.0",
    "@remix-run/testing": "^2.9.2",
    "@shopify/cli": "3.59.2",
    "@shopify/cli-hydrogen": "^8.0.4",
    "@shopify/hydrogen": "2024.4.2",
    "@shopify/remix-oxygen": "^2.0.4",
    "@testing-library/jest-dom": "^6.4.6",
    "@testing-library/react": "^16.0.0",
    "@vitejs/plugin-react": "^4.3.1",
    "axios": "^1.7.2",
    "graphql": "^16.6.0",
    "graphql-tag": "^2.12.6",
    "isbot": "^3.8.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "vitest": "^1.6.0"
  },
  "devDependencies": {
    "@graphql-codegen/cli": "5.0.2",
    "@remix-run/dev": "^2.8.0",
    "@remix-run/eslint-config": "^2.8.0",
    "@shopify/hydrogen-codegen": "^0.3.1",
    "@shopify/mini-oxygen": "^3.0.2",
    "@shopify/oxygen-workers-types": "^4.0.0",
    "@shopify/prettier-config": "^1.1.2",
    "@total-typescript/ts-reset": "^0.4.2",
    "@types/eslint": "^8.4.10",
    "@types/react": "^18.2.22",
    "@types/react-dom": "^18.2.7",
    "eslint": "^8.20.0",
    "eslint-plugin-hydrogen": "0.12.2",
    "jsdom": "^24.1.0",
    "prettier": "^2.8.4",
    "typescript": "^5.2.2",
    "vite": "^5.1.0",
    "vite-tsconfig-paths": "^4.3.1"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}

内容的提问来源于stack exchange,提问作者Jose Miguel Vega Lopez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 12:15:54