Sentry 开发者贡献指南 - 前端(ReactJS生态)

开发 前端
本指南涵盖了我们如何在 Sentry 编写前端代码, 并特别关注 Sentry 和 Getsentry 代码库。它假设您使用的是 eslint-config-sentry 概述的 eslint 规则;因此,这里不会讨论由这些 linting 规则强制执行的代码风格。

[[440666]]

本文转载自微信公众号「黑客下午茶」,作者为少 。转载本文请联系黑客下午茶公众号。

前端手册

本指南涵盖了我们如何在 Sentry 编写前端代码, 并特别关注 Sentry 和 Getsentry 代码库。它假设您使用的是 eslint-config-sentry 概述的 eslint 规则;因此,这里不会讨论由这些 linting 规则强制执行的代码风格。

  • https://github.com/getsentry/eslint-config-sentry

目录结构

前端代码库当前位于 sentry 中的 src/sentry/static/sentry/app 和 getentry 中的 static/getsentry 下。(我们打算在未来与 static/sentry 保持一致。)

文件夹和文件结构

文件命名

  • 根据模块的功能或类的使用方式或使用它们的应用程序部分,有意义地命名文件。
  • 除非必要,否则不要使用前缀或后缀(即 dataScrubbingEditModal、dataScrubbingAddModal),而是使用像 dataScrubbing/editModal 这样的名称。

使用 index.(j|t)?(sx)

在文件夹中有一个 index 文件提供了一种隐式导入主文件而不指定它的方法

index 文件的使用应遵循以下规则:

  • 如果创建文件夹来对一起使用的组件进行分组,并且有一个入口点组件,它使用分组内的组件(examples、avatar、idBadge)。入口点组件应该是 index 文件。
  • 不要使用 index.(j|t)?(sx) 文件,如果文件夹包含在应用程序的其他部分使用的组件,与入口点文件无关。(即,actionCreators,panels)
  • 不要仅仅为了重新导出而使用 index 文件。更倾向于导入单个组件。

React

定义 React 组件

新组件在需要访问 this 时使用 class 语法,以及类字段+箭头函数方法定义。

  1. class Note extends React.Component { 
  2.   static propTypes = { 
  3.     author: PropTypes.object.isRequired, 
  4.     onEdit: PropTypes.func.isRequired, 
  5.   }; 
  6.  
  7.   // 请注意,方法是使用箭头函数类字段定义的(绑定“this”) 
  8.   handleChange = value => { 
  9.     let user = ConfigStore.get('user'); 
  10.  
  11.     if (user.isSuperuser) { 
  12.       this.props.onEdit(value); 
  13.     } 
  14.   }; 
  15.  
  16.   render() { 
  17.     let {content} = this.props; // 对 props 使用解构赋值 
  18.  
  19.     return <div onChange={this.handleChange}>{content}</div>; 
  20.   } 
  21.  
  22. export default Note; 

一些较旧的组件使用 createReactClass 和 mixins,但这已被弃用。

组件与视图

app/components/ 和 app/views 文件夹都包含 React 组件。

使用通常不会在代码库的其他部分重用的 UI 视图。

使用设计为高度可重用的 UI 组件。

组件应该有一个关联的 .stories.js 文件来记录它应该如何使用。

使用 yarn storybook 在本地运行 Storybook 或在 https://storybook.getsentry.net/ 上查看托管版本

PropTypes

使用它们,要明确,尽可能使用共享的自定义属性。

更倾向 Proptypes.arrayOf 而不是 PropTypes.array 和 PropTypes.shape 而不是 PropTypes.object

如果你使用一组重要的、定义良好的 key(你的组件依赖)传递对象,那么使用 PropTypes.shape 显式定义它们:

  1. PropTypes.shape({ 
  2.   username: PropTypes.string.isRequired, 
  3.   email: PropTypes.string 
  4. }) 

如果您要重复使用自定义 prop-type 或传递常见的共享 shape(如 organization、project 或 user), 请确保从我们有用的自定义集合中导入 proptype!

  • https://github.com/getsentry/sentry/blob/master/static/app/sentryTypes.tsx

事件处理程序

我们使用不同的前缀来更好地区分事件处理程序和事件回调属性。

对事件处理程序使用 handle 前缀,例如:

  1. <Button onClick={this.handleDelete}/> 

 

对于传递给组件的事件回调属性,请使用 on 前缀,例如:

  1. <Button onClick={this.props.onDelete}> 

CSS 和 Emotion

  • 使用 Emotion,使用 theme 对象。
  • 最好的样式是您不编写的样式 - 尽可能使用现有组件。
  • 新代码应该使用 css-in-js 库 e m o t i o n - 它允许您将样式绑定到元素而无需全局选择器的间接性。你甚至不需要打开另一个文件!
  • 从 props.theme 获取常量(z-indexes, paddings, colors)
    • https://emotion.sh/
    • https://github.com/getsentry/sentry/blob/master/static/app/utils/theme.tsx
  1. import styled from 'react-emotion'
  2.  
  3. const SomeComponent = styled('div')` 
  4.   border-radius: 1.45em; 
  5.   font-weight: bold; 
  6.   z-index: ${p => p.theme.zIndex.modal}; 
  7.   padding: ${p => p.theme.grid}px ${p => p.theme.grid * 2}px; 
  8.   border: 1px solid ${p => p.theme.borderLight}; 
  9.   color: ${p => p.theme.purple}; 
  10.   box-shadow: ${p => p.theme.dropShadowHeavy}; 
  11. `; 
  12.  
  13. export default SomeComponent; 

请注意,reflexbox(例如Flex 和Box)已被弃用,请避免在新代码中使用。

stylelint 错误

"No duplicate selectors"

当您使用样式组件(styled component)作为选择器时会发生这种情况,我们需要通过使用注释来辅助 linter 来告诉 stylelint 我们正在插入的是一个选择器。例如

  1. const ButtonBar = styled("div")` 
  2.   ${/* sc-selector */Button) { 
  3.      border-radius: 0; 
  4.   } 
  5. `; 

有关其他标签和更多信息,请参阅。

  • https://styled-components.com/docs/tooling#interpolation-tagging

状态管理

我们目前使用 Reflux 来管理全局状态。

Reflux 实现了 Flux 概述的单向数据流模式。 Store 注册在 app/stores 下,用于存储应用程序使用的各种数据。 Action 需要在 app/actions 下注册。我们使用 action creator 函数(在 app/actionCreators 下)来分派 action。 Reflux store 监听 action 并相应地更新自己。

我们目前正在探索 Reflux 库的替代方案以供将来使用。

  • https://github.com/reflux/refluxjs
  • https://facebook.github.io/flux/docs/overview.html

测试

我们正在远离 Enzyme,转而使用 React Testing Library。有关 RTL 提示,请查看此页面。

注意:你的文件名必须是 .spec.jsx 否则 jest 不会运行它!

我们在 setup.js 中定义了有用的 fixtures,使用这些!如果您以重复的方式定义模拟数据,则可能值得添加此文件。routerContext 是一种特别有用的方法,用于提供大多数视图所依赖的上下文对象。

  • https://github.com/getsentry/sentry/blob/master/tests/js/setup.ts

Client.addMockResponse 是模拟 API 请求的最佳方式。这是我们的代码, 所以如果它让您感到困惑,只需将 console.log() 语句放入其逻辑中即可!

  • https://github.com/getsentry/sentry/blob/master/static/app/__mocks__/api.tsx

我们测试环境中的一个重要问题是,enzyme 修改了 react 生命周期的许多方面以同步评估(即使它们通常是异步的)。当您触发某些逻辑并且没有立即在您的断言逻辑中反映出来时,这可能会使您陷入一种虚假的安全感。

标记您的测试方法 async 并使用 await tick(); 实用程序可以让事件循环刷新运行事件并修复此问题:

  1. wrapper.find('ExpandButton').simulate('click'); 
  2. await tick(); 
  3. expect(wrapper.find('CommitRow')).toHaveLength(2); 

选择器

如果您正在编写 jest 测试,您可以使用 Component(和 Styled Component)名称作为选择器。此外,如果您需要使用 DOM 查询选择器,请使用 data-test-id 而不是类名。我们目前没有,但我们可以在构建过程中使用 babel 去除它。

测试中未定义的 theme 属性

而不是使用来自 enzyme 的 mount() ...使用这个:import {mountWithTheme} from 'sentry-test/enzyme' 以便被测组件用

  • https://emotion.sh/docs/theming

Babel 语法插件

我们决定只使用处于 stage 3(或更高版本)的 ECMAScript 提案(参见 TC39 提案)。此外,因为我们正在迁移到 typescript,我们将与他们的编译器支持的内容保持一致。唯一的例外是装饰器。

  • https://github.com/tc39/proposals

新语法

可选链

可选链 帮助我们访问 [嵌套] 对象, 而无需在每个属性/方法访问之前检查是否存在。如果我们尝试访问 undefined 或 null 对象的属性,它将停止并返回 undefined。

https://github.com/tc39/proposal-optional-chaining

语法

可选链操作符拼写为 ?.。它可能出现在三个位置:

  1. obj?.prop       // 可选的静态属性访问 
  2. obj?.[expr]     // 可选的动态属性访问 
  3. func?.(...args) // 可选的函数或方法调用 

来自 https://github.com/tc39/proposal-optional-chaining

空值合并

这是一种设置“默认”值的方法。例如:以前你会做类似的事情

  1. let x = volume || 0.5; 

这是一个问题,因为 0 是 volume 的有效值,但因为它的计算结果为 false -y,我们不会使表达式短路,并且 x 的值为 0.5

如果我们使用空值合并

  • https://github.com/tc39/proposal-nullish-coalescing
  1. let x = volume ?? 0.5 

如果 volume 为 null 或 undefined,它只会默认为 0.5。

语法

基本情况。如果表达式在 ?? 的左侧运算符计算为 undefined 或 null,则返回其右侧。

  1. const response = { 
  2.   settings: { 
  3.     nullValue: null
  4.     height: 400, 
  5.     animationDuration: 0, 
  6.     headerText: ''
  7.     showSplashScreen: false 
  8.   } 
  9. }; 
  10.  
  11. const undefinedValue = response.settings.undefinedValue ?? 'some other default'; // result: 'some other default' 
  12. const nullValue = response.settings.nullValue ?? 'some other default'; // result: 'some other default' 
  13. const headerText = response.settings.headerText ?? 'Hello, world!'; // result: '' 
  14. const animationDuration = response.settings.animationDuration ?? 300; // result: 0 
  15. const showSplashScreen = response.settings.showSplashScreen ?? true; // result: false 

Lodash

确保不要使用默认的 lodash 包导入 lodash 实用程序。有一个 eslint 规则来确保这不会发生。而是直接导入实用程序,例如 import isEqual from 'lodash/isEqual';。

以前我们使用了 lodash-webpack-plugin 和 babel-plugin-lodash 的组合, 但是在尝试使用新的 lodash 实用程序(例如这个 PR)时很容易忽略这些插件和配置。通过 webpack tree shaking 和 eslint 强制执行,我们应该能够保持合理的包大小。

  • https://www.npmjs.com/package/lodash-webpack-plugin
  • https://github.com/lodash/babel-plugin-lodash
  • https://github.com/getsentry/sentry/pull/13834

有关更多信息,请参阅此 PR。

  • https://github.com/getsentry/sentry/pull/15521

我们更喜欢使用可选链和空值合并而不是来自 lodash/get 的 get。

Typescript

  • Typing DefaultProps

迁移指南

  • Grid-Emotion

Storybook Styleguide

引用其文档,“Storybook 是用于 UI 组件的 UI 开发环境。有了它,您可以可视化 UI 组件的不同状态并以交互方式开发它们。”

更多细节在这里:

  • https://storybook.js.org/

我们使用它吗?

是的!我们将 Storybook 用于 getsentry/sentry 项目。 Storybook 的配置可以在 https://github.com/getsentry/sentry/tree/master/.storybook 中找到。

要在本地运行 Storybook,请在 getsentry/sentry 存储库的根目录中运行 npm run storybook。

它部署在某个地方吗?

Sentry 的 Storybook 是使用 Vercel 构建和部署的。每个 Pull Request 都有自己的部署,每次推送到主分支都会部署到 https://storybook.sentry.dev。

  • https://storybook.sentry.dev

Typing DefaultProps

由于 Typescript 3.0 默认 props 可以更简单地输入。有几种不同的方法适合不同的场景。

类(Class)组件

  1. import React from 'react'
  2.  
  3. type DefaultProps = { 
  4.   size'Small' | 'Medium' | 'Large'; // 这些不应标记为可选 
  5. }; 
  6.  
  7. // 没有 Partial<DefaultProps> 
  8. type Props = DefaultProps & { 
  9.   name: string; 
  10.   codename?: string; 
  11. }; 
  12.  
  13. class Planet extends React.Component<Props> { 
  14.   // 没有 Partial<Props> 因为它会将所有内容标记为可选 
  15.   static defaultProps: DefaultProps = { 
  16.     size'Medium'
  17.   }; 
  18.  
  19.   render() { 
  20.     const {namesize, codename} = this.props; 
  21.  
  22.     return ( 
  23.       <p> 
  24.         {nameis a {size.toLowerCase()} planet. 
  25.         {codename && ` Its codename is ${codename}`} 
  26.       </p> 
  27.     ); 
  28.   } 
  29.  
  30. const planet = <Planet name="Mars" />; 

或在 typeof 的帮助下:

  1. import React from 'react'
  2.  
  3. const defaultProps = { 
  4.   size'Medium' as 'Small' | 'Medium' | 'Large'
  5. }; 
  6.  
  7. type Props = { 
  8.   name: string; 
  9.   codename?: string; 
  10. } & typeof defaultProps; 
  11. // 没有 Partial<typeof defaultProps> 因为它会将所有内容标记为可选 
  12.  
  13. class Planet extends React.Component<Props> { 
  14.   static defaultProps = defaultProps; 
  15.  
  16.   render() { 
  17.     const {namesize, codename} = this.props; 
  18.  
  19.     return ( 
  20.       <p> 
  21.         {nameis a {size.toLowerCase()} planet. Its color is{' '
  22.         {codename && ` Its codename is ${codename}`} 
  23.       </p> 
  24.     ); 
  25.   } 
  26.  
  27. const planet = <Planet name="Mars" />; 

函数式(Function)组件

  1. import React from 'react'
  2.  
  3. // 函数组件上的 defaultProps 将在未来停止使用 
  4. // https://twitter.com/dan_abramov/status/1133878326358171650 
  5. // https://github.com/reactjs/rfcs/pull/107 
  6. // 我们应该使用默认参数 
  7.  
  8. type Props = { 
  9.   name: string; 
  10.   size?: 'Small' | 'Medium' | 'Large'; // 具有 es6 默认参数的属性应标记为可选 
  11.   codename?: string; 
  12. }; 
  13.  
  14. // 共识是输入解构的 Props 比使用 React.FC<Props> 稍微好一点 
  15. // https://github.com/typescript-cheatsheets/react-typescript-cheatsheet#function-components 
  16. const Planet = ({namesize = 'Medium', codename}: Props) => { 
  17.   return ( 
  18.     <p> 
  19.       {nameis a {size.toLowerCase()} planet. 
  20.       {codename && ` Its codename is ${codename}`} 
  21.     </p> 
  22.   ); 
  23. }; 
  24.  
  25. const planet = <Planet name="Mars" />; 

参考

  • Typescript 3.0 Release notes
    • https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#support-for-defaultprops-in-jsx
  • Stack Overflow question on typing default props
    • https://stackoverflow.com/questions/37282159/default-property-value-in-react-component-using-typescript/37282264#37282264

使用 Hooks

为了使组件更易于重用和更易于理解,React 和 React 生态系统一直趋向于函数式组件和 hooks。 Hooks 是一种向功能组件添加状态和副作用的便捷方式。它们还为库提供了一种公开行为的便捷方式。

虽然我们通常支持 hooks,但我们有一些关于 hooks 应该如何与 Sentry 前端一起使用的建议。

使用库中的 hooks

如果一个库提供了 hooks,你应该使用它们。通常,这将是使用库的唯一方法。例如,dnd-kit 通过钩子公开了它的所有原语(primitives),我们应该按照预期的方式使用该库。

我们不喜欢使用不用 hooks 的库。相反,与具有更大、更复杂的 API 或更大的包大小的库相比, 更喜欢具有更清晰、更简单的 API 和更小的包大小的库。

使用 react 的内置 hooks

useState, useMemo, useCallback, useContext 和 useRef hooks 在任何函数式组件中都是受欢迎的。在需要少量状态或访问 react 原语(如引用和上下文)的展示组件中,它们通常是一个不错的选择。例如,具有滑出(slide-out)或可展开状态(expandable state)的组件。

useEffect hook 更复杂,您需要小心地跟踪您的依赖项并确保通过清理回调取消订阅。应避免 useEffect 的复杂链式应用程序,此时 'controller' 组件应保持基于类(class)。

同样,useReducer 钩子与目前尚未确定的状态管理重叠。我们希望避免 又一个 状态管理模式,因此此时避免使用useReducer。

使用 context

当我们计划远离 Reflux 的路径时,useContext hook 提供了一个更简单的实现选项来共享状态和行为。当您需要创建新的共享状态源时,请考虑使用 context 和 useContext 而不是 Reflux。此外,可以利用虫洞状态管理模式来公开共享状态和突变函数。

  • https://swizec.com/blog/wormhole-state-management

使用自定义 hooks

可以创建自定义 hooks 来共享应用程序中的可重用逻辑。创建自定义 hook 时,函数名称必须遵循约定,以 “use” 开头(例如 useTheme), 并且可以在自定义 hooks 内调用其他 hooks。

注意 hooks 的规则和注意事项

React hooks 有一些规则。请注意 hooks 创建的规则和限制。我们使用 ESLint 规则来防止大多数 hook 规则被非法侵入。

  • https://reactjs.org/docs/hooks-rules.html

此外,我们建议您尽量少使用 useEffect。使用多个 useEffect 回调表示您有一个高度有状态的组件, 您应该使用类(class)组件来代替。

我们的基础视图组件仍然是基于类的

我们的基础视图组件(AsyncView 和 AsyncComponent)是基于类的,并且会持续很长时间。在构建视图时请记住这一点。您将需要额外的 wrapper 组件来访问 hooks 或将 hook state 转换为您的 AsyncComponent 的 props。

不要为 hooks 重写

虽然 hooks 可以在新代码中符合人体工程学,但我们应该避免重写现有代码以利用 hooks。重写需要时间,使我们面临风险,并且为最终用户提供的价值很小。

如果您需要重新设计一个组件以使用库中的 hooks,那么还可以考虑从一个类转换为一个函数组件。

使用 React Testing Library

我们正在将我们的测试从 Enzyme 转换为 React Testing Library。在本指南中,您将找到遵循最佳实践和避免常见陷阱的技巧。

我们有两个 ESLint 规则来帮助解决这个问题:

  • eslint-plugin-jest-dom
    • https://github.com/testing-library/eslint-plugin-jest-dom
  • eslint-plugin-testing-library
    • https://github.com/testing-library/eslint-plugin-testing-library

我们努力以一种与应用程序使用方式非常相似的方式编写测试。

我们不是处理渲染组件的实例,而是以与用户相同的方式查询 DOM。我们通过 label 文本找到表单元素(就像用户一样),我们从他们的文本中找到链接和按钮(就像用户一样)。

作为此目标的一部分,我们避免测试实现细节,因此重构(更改实现但不是功能)不会破坏测试。

我们通常赞成用例覆盖而不是代码覆盖。

查询

  • 尽可能使用 getBy...
  • 仅在检查不存在时使用 queryBy...
  • 仅当期望元素在可能不会立即发生的 DOM 更改后出现时才使用 await findBy...

为确保测试类似于用户与我们的代码交互的方式,我们建议使用以下优先级进行查询:

1.getByRole - 这应该是几乎所有东西的首选选择器。

作为这个选择器的一个很好的奖励,我们确保我们的应用程序是可访问的。它很可能与 name 选项 getByRole('button', {name: /save/i}) 一起使用。 name 通常是表单元素的 label 或 button 的文本内容,或 aria-label 属性的值。如果不确定,请使用 logRoles 功能 或查阅可用角色列表。

2.https://testing-library.com/docs/dom-testing-library/api-accessibility/#logroles

3.https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques#roles

4.getByLabelText/getByPlaceholderText - 用户使用 label 文本查找表单元素,因此在测试表单时首选此选项。

getByText - 在表单之外,文本内容是用户查找元素的主要方式。此方法可用于查找非交互式元素(如 div、span 和 paragraph)。

getByTestId - 因为这不反映用户如何与应用交互,所以只推荐用于不能使用任何其他选择器的情况

如果您仍然无法决定使用哪个查询, 请查看 testing-playground.com 以及 screen.logTestingPlaygroundURL() 及其浏览器扩展。

  • https://testing-playground.com/

不要忘记,你可以在测试中的任何地方放置 screen.debug() 来查看当前的 DOM。

在官方文档中阅读有关查询的更多信息。

  • https://testing-library.com/docs/queries/about/

技巧

避免从 render 方法中解构查询函数,而是使用 screen(examples)。当您添加/删除您需要的查询时,您不必使 render 调用解构保持最新。您只需要输入 screen 并让您的编辑器的自动完成功能处理其余的工作。

  • https://github.com/getsentry/sentry/pull/29312
  1. import { mountWithTheme, screen } from "sentry-test/reactTestingLibrary"
  2.  
  3. // ❌ 
  4. const { getByRole } = mountWithTheme(<Example />); 
  5. const errorMessageNode = getByRole("alert"); 
  6.  
  7. // ✅ 
  8. mountWithTheme(<Example />); 
  9. const errorMessageNode = screen.getByRole("alert"); 

除了检查不存在(examples)之外,避免将 queryBy... 用于任何事情。如果没有找到元素,getBy... 和 findBy... 变量将抛出更有用的错误消息。

  • https://github.com/getsentry/sentry/pull/29517
  1. import { mountWithTheme, screen } from "sentry-test/reactTestingLibrary"
  2.  
  3. // ❌ 
  4. mountWithTheme(<Example />); 
  5. expect(screen.queryByRole("alert")).toBeInTheDocument(); 
  6.  
  7. // ✅ 
  8. mountWithTheme(<Example />); 
  9. expect(screen.getByRole("alert")).toBeInTheDocument(); 
  10. expect(screen.queryByRole("button")).not.toBeInTheDocument(); 

避免使用 waitFor 等待出现,而是使用 findBy...(examples)。这两个基本上是等价的(findBy... 甚至在其里面使用了 waitFor),但是 findBy... 更简单,我们得到的错误信息也会更好。

  • https://github.com/getsentry/sentry/pull/29544
  1. import { 
  2.   mountWithTheme, 
  3.   screen, 
  4.   waitFor, 
  5. from "sentry-test/reactTestingLibrary"
  6.  
  7. // ❌ 
  8. mountWithTheme(<Example />); 
  9. await waitFor(() => { 
  10.   expect(screen.getByRole("alert")).toBeInTheDocument(); 
  11. }); 
  12.  
  13. // ✅ 
  14. mountWithTheme(<Example />); 
  15. expect(await screen.findByRole("alert")).toBeInTheDocument(); 

避免使用 waitFor 等待消失,使用 waitForElementToBeRemoved 代替(examples)。

  • https://github.com/getsentry/sentry/pull/29547

后者使用 MutationObserver,这比使用 waitFor 定期轮询 DOM 更有效。

  1. import { 
  2.   mountWithTheme, 
  3.   screen, 
  4.   waitFor, 
  5.   waitForElementToBeRemoved, 
  6. from "sentry-test/reactTestingLibrary"
  7.  
  8. // ❌ 
  9. mountWithTheme(<Example />); 
  10. await waitFor(() => 
  11.   expect(screen.queryByRole("alert")).not.toBeInTheDocument() 
  12. ); 
  13.  
  14. // ✅ 
  15. mountWithTheme(<Example />); 
  16. await waitForElementToBeRemoved(() => screen.getByRole("alert")); 

更喜欢使用 jest-dom 断言(examples)。使用这些推荐的断言的优点是更好的错误消息、整体语义、一致性和统一性。

  • https://github.com/getsentry/sentry/pull/29508
  1. import { mountWithTheme, screen } from "sentry-test/reactTestingLibrary"
  2.  
  3. // ❌ 
  4. mountWithTheme(<Example />); 
  5. expect(screen.getByRole("alert")).toBeTruthy(); 
  6. expect(screen.getByRole("alert").textContent).toEqual("abc"); 
  7. expect(screen.queryByRole("button")).toBeFalsy(); 
  8. expect(screen.queryByRole("button")).toBeNull(); 
  9.  
  10. // ✅ 
  11. mountWithTheme(<Example />); 
  12. expect(screen.getByRole("alert")).toBeInTheDocument(); 
  13. expect(screen.getByRole("alert")).toHaveTextContent("abc"); 
  14. expect(screen.queryByRole("button")).not.toBeInTheDocument(); 

按文本搜索时,最好使用不区分大小写的正则表达式。它将使测试更能适应变化。

  1. import { mountWithTheme, screen } from "sentry-test/reactTestingLibrary"
  2.  
  3. // ❌ 
  4. mountWithTheme(<Example />); 
  5. expect(screen.getByText("Hello World")).toBeInTheDocument(); 
  6.  
  7. // ✅ 
  8. mountWithTheme(<Example />); 
  9. expect(screen.getByText(/hello world/i)).toBeInTheDocument(); 

尽可能在 fireEvent 上使用 userEvent。 userEvent 来自 @testing-library/user-event 包,它构建在 fireEvent 之上,但它提供了几种更类似于用户交互的方法。

  1. // ❌ 
  2. import { 
  3.   mountWithTheme, 
  4.   screen, 
  5.   fireEvent, 
  6. from "sentry-test/reactTestingLibrary"
  7. mountWithTheme(<Example />); 
  8. fireEvent.change(screen.getByLabelText("Search by name"), { 
  9.   target: { value: "sentry" }, 
  10. }); 
  11.  
  12. // ✅ 
  13. import { 
  14.   mountWithTheme, 
  15.   screen, 
  16.   userEvent, 
  17. from "sentry-test/reactTestingLibrary"
  18. mountWithTheme(<Example />); 
  19. userEvent.type(screen.getByLabelText("Search by name"), "sentry"); 

迁移 - grid-emotion

grid-emotion 已经被弃用一年多了,新项目是 reflexbox。为了升级到最新版本的 emotion,我们需要迁移出 grid-emotion。

要迁移,请使用 emotion 将导入的 组件替换为带样式的组件。

组件

用下面的替换组件,然后删除必要的 props 并移动到 styled component。

<Flex>

  1. const Flex = styled('div')` 
  2.   display: flex; 
  3. `; 

<Box>

  1. const Box = styled('div')` 
  2. `; 

属性

如果您正在修改导出的组件,请确保通过该组件的代码库进行 grep 以确保它没有被渲染为特定于 grid-emotion 的附加属性。示例是 组件。

margin 和 padding

旧 (grid-emotion) 新 (css/emotion/styled)
m={2} margin: ${space(2);
mx={2} margin-left: ${space(2); margin-right: ${space(2)};
my={2} margin-top: ${space(2); margin-bottom: ${space(2)};
ml={2} margin-left: ${space(2);
mr={2} margin-right: ${space(2);
mt={2} margin-top: ${space(2);
mb={2} margin-bottom: ${space(2);

flexbox

这些是 flexbox 属性

旧 (grid-emotion) 新 (css/emotion/styled)
align="center" align-items: center;
justify="center" justify-content: center;
direction="column" flex-direction: column;
wrap="wrap" flex-wrap: wrap;

现在只需忽略 grid-emotion 的导入语句,例如 // eslint-disable-line no-restricted-imports

 

责任编辑:武晓燕 来源: 黑客下午茶
相关推荐

2022-01-11 20:42:54

开发Sentry标志

2022-01-17 19:34:43

SentryWeb APISentry API

2022-01-15 23:33:47

SentryPyCharm配置

2022-01-18 23:26:45

开发

2022-01-02 23:26:08

开发SDK Sentry

2022-01-21 21:33:03

开发JavaScript应用

2021-12-25 22:31:55

Sentry 监控SDK 开发 性能监控

2022-01-16 22:16:59

数据库Sentry开发者

2022-01-13 20:13:31

元宇宙搜索引擎

2022-01-03 22:59:30

开发SDK数据

2021-12-31 18:35:40

监控Sentry开发

2022-01-02 06:59:43

SentrySDK 开发客户端报告

2022-01-19 19:49:53

Sentry浏览器SDK

2021-12-16 20:12:37

后端开发Sentry

2022-01-20 19:49:10

Sentry开发Scope

2021-12-17 19:15:51

前端虫洞状态

2015-07-22 16:08:46

OpenStack开源贡献代码

2018-03-27 23:25:40

Paddle

2019-08-16 10:55:37

开发者技能AI

2024-05-07 08:45:16

OpenAILlamaIndex大语言模型
点赞
收藏

51CTO技术栈公众号