如何在单个故事中模拟 window 变量
How to mock a window variable in a single story
我在我的 React 应用程序中使用 Navigator online 来确定客户端是否在线。当客户端离线时,我正在显示一个离线后备组件。现在我已经制作了纯组件——所以我可以通过将在线状态传递为 属性 来在 Storybook 中显示它。但这并不总是合适的解决方案。
所以我想知道如何在 Storybook 中模拟单个故事的全局 (window) 变量?唯一的 - 非常肮脏的解决方案 - 我发现如下所示:
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 />
},
]
有人有更直接的解决方案吗?
在这个答案中,我假设所有副作用(与 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
}
我在我的 React 应用程序中使用 Navigator online 来确定客户端是否在线。当客户端离线时,我正在显示一个离线后备组件。现在我已经制作了纯组件——所以我可以通过将在线状态传递为 属性 来在 Storybook 中显示它。但这并不总是合适的解决方案。
所以我想知道如何在 Storybook 中模拟单个故事的全局 (window) 变量?唯一的 - 非常肮脏的解决方案 - 我发现如下所示:
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 />
},
]
有人有更直接的解决方案吗?
在这个答案中,我假设所有副作用(与 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
}