如何在Storybook中冻结DateTime.now()避免Chromatic误判变更
解决方案
核心思路是在Storybook运行环境中固定DateTime.now()的返回值,让时间差计算结果恒定,彻底消除Chromatic截图时的非预期UI差异。以下是按推荐优先级排序的可落地方法:
方案1:全局固定luxon时间(零业务代码侵入,最稳定)
luxon原生提供了全局时间配置项,不需要修改组件业务代码,只需要调整Storybook预览配置即可让所有故事中的DateTime.now()返回预设静态值:
- 打开项目下的Storybook配置文件
.storybook/preview.js(TS项目对应.storybook/preview.ts) - 引入luxon的全局设置,配置固定的时间返回函数,参考代码:
// .storybook/preview.js import { Settings } from 'luxon'; // 配置固定的"当前时间",建议使用和测试用固定对比日期匹配的静态UTC时间 const STATIC_FIXED_TIME = '2024-01-01T12:00:00.000Z'; Settings.now = () => new Date(STATIC_FIXED_TIME).getTime(); // 保留原有preview配置即可 const preview = { parameters: { // 你原有的配置项,比如actions、controls配置 }, }; export default preview;
这个配置仅在Storybook运行环境生效,完全不会影响生产环境代码逻辑。
方案2:单故事独立配置时间(适配不同故事需要不同时间基准的场景)
如果不需要全局生效,只想给特定时间相关组件单独设置固定时间,可以在对应故事文件中通过装饰器注入mock,避免影响其他组件故事:
// 组件故事文件,例:ElapsedTime.stories.jsx import { Settings } from 'luxon'; import ElapsedTime from './ElapsedTime'; export default { title: 'Common/ElapsedTime', component: ElapsedTime, decorators: [ (Story) => { // 保存原始时间方法,故事渲染后还原,避免污染其他故事 const originalNowMethod = Settings.now; // 为当前故事设置固定时间 Settings.now = () => new Date('2024-01-01T12:00:00.000Z').getTime(); Story(); // 还原配置 Settings.now = originalNowMethod; } ] }; export const ThirtyMinutesElapsed = { args: { // 传入固定对比时间,此时组件计算出的已流逝时间永远为30分钟,不会随真实时间变动 targetTime: '2024-01-01T11:30:00.000Z' } };
方案3:Chromatic层面全局时间冻结(双重保险)
如果项目中除了luxon之外还有其他地方调用原生时间API(比如Date.now()、new Date()),可以配合Chromatic自带的时间模拟能力加一层保障:
- 进入Chromatic对应项目的构建设置页
- 找到时间模拟配置项,填入和Storybook中配置一致的固定UTC时间
- 配置生效后,Chromatic在构建截图时会冻结整个容器的系统时间,所有时间相关API都会返回固定值,彻底杜绝时间差导致的UI diff
避坑提示:不要在Storybook环境使用单测场景的时间mock库(比如jest-date-mock、sinon假时钟),这类工具针对Node单测环境设计,在浏览器运行的Storybook环境中容易出现mock失效、全局状态污染的问题,优先使用luxon原生配置是最稳定的选择。
内容的提问来源于stack exchange,提问作者David Armendariz
相关产品推荐
相关产品推荐

