如何在单个故事中模拟窗口变量

Posted

技术标签:

【中文标题】如何在单个故事中模拟窗口变量【英文标题】:How to mock a window variable in a single story 【发布时间】:2022-01-03 22:18:42 【问题描述】:

我在我的 React 应用程序中使用Navigator online 来确定客户端是否在线。当客户端脱机时,我正在显示脱机后备组件。现在我已经制作了纯组件 - 所以我可以通过将在线状态作为属性传递来在 Storybook 中显示它。但这并不总是一个合适的解决方案。

所以我想知道如何在 Storybook 中模拟单个故事的全局(窗口)变量?唯一 - 非常肮脏的解决方案 - 我发现如下:

ClientOffline.decorators = [
  (Story) => 
    const navigatiorInitally = global.navigator

    // I am overwritting the navigator object instead of directly the 
    // online value as this throws an 'TypeError: "x" is read-only' Error
    Object.defineProperty(global, 'navigator', 
      value:  onLine: false ,
      writable: false,
    )

    useEffect(() => 
      return () => 
        Object.defineProperty(global, 'navigator', 
          value: navigatiorInitally,
          writable: false,
        )
       location.reload() //needed because otherwilse other stories throw an error
      
    , [])

    return <Story />
  ,
]

有人有更直接的解决方案吗?

【问题讨论】:

你想做什么?用户在加载这部分页面时应该看到什么?他们看到了什么? Storybook 是什么?为什么将在线状态作为属性传递并不总是一个合适的解决方案?您遇到了什么具体情况而这不起作用? 你为什么在一个带有Storybook标签的问题中问Storybook是什么? 【参考方案1】:

在这个答案中,我假设所有副作用(与 Navigator 通信是一种副作用)都从组件主体中分离到钩子中。

假设您的组件如下所示:

function AwesomeComponent () 
  let [connectionStatus, setConnectionStatus] = useState('unknown');
  useEffect(function checkConnection() 
    if (typeof window === 'undefined' || window.navigator.onLine) setConnectionStatus('online'); 
    else setConnectionStatus('offline');
  , []);
  
  if (connectionStatus === 'online') return <OnlineComponent/>
  if (connectionStatus === 'offline') return <FallbackComponent/>
  return null;

您可以将您的钩子提取到单独的模块中。在我的示例中,它既是状态又是效果。

function useConnectionStatus() 
  let [connectionStatus, setConnectionStatus] = useState("unknown");
  useEffect(function checkConnection() 
    if (typeof window === "undefined" || window.navigator.onLine)
      setConnectionStatus("online");
    else setConnectionStatus("offline");
  , []);
  return connectionStatus;

通过这种方式,您可以将逻辑与表示分离,并且可以模拟单个模块。 Storybook 有guide to mock individual modules 并按故事配置它们。最好查阅文档,因为软件正在发生变化,并且可能会及时以其他方式完成。

假设您将文件命名为useConnectionStatus.js。要模拟它,你必须创建__mock__ 文件夹,在那里创建你的模拟模块。例如,它会是这样的:

// __mock__/useConnectionStatus.js
let connectionStatus = 'online';
export default function useConnectionStatus()
  return connectionStatus;


export function decorator(story,  parameters ) 
  if (parameters && parameters.offline) 
    connectionStatus = 'offline';
  
  return story();  

下一步是修改 webpack 配置以使用模拟的钩子而不是实际的钩子。文档提供了一种从您的.storybook/main.js 执行此操作的方法。在撰写本文时,它是这样完成的:

// .storybook/main.js

module.exports = 
  // Your Storybook configuration

  webpackFinal: (config) => 
    config.resolve.alias['path/to/original/useConnectionStatus'] = require.resolve('../__mocks__/useConnectionStatus.js');
    return config;
  ,
;

现在,使用我们的新装饰器装饰您的预览,您将能够分别为每个特定故事设置配置。

// inside your story
Story.parameters = 
  offline: true

【讨论】:

以上是关于如何在单个故事中模拟窗口变量的主要内容,如果未能解决你的问题,请参考以下文章

SQL 数据库存储不同类型的值(在单个字段中或模拟为单个字段)

如何在 xcode 6.3 界面构建器故事板中默认模拟指标?

如何在 MyScene(SKScene 的子类)中打开 xib 或故事板窗口?

如何在java中模拟单个方法

如何在单个测试的基础上更改模拟实现 [Jestjs]

在通用故事板中使用 present 作为弹出框