精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

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

前端蟲洞狀態

2025-03-03 00:00:03

2015-07-22 16:08:46

OpenStack開源貢獻代碼

2018-03-27 23:25:40

Paddle

2024-02-01 09:37:42

Kubernetes服務網格? 命令
點贊
收藏

51CTO技術棧公眾號

成人一级生活片| 国产精品久久电影观看| 精品视频站长推荐| 88xx成人网| 综合久久给合久久狠狠狠97色| 久久免费精品视频| 50一60岁老妇女毛片| 国产经典一区| 亚洲综合偷拍欧美一区色| 久精品国产欧美| 一级片免费网站| 1000部精品久久久久久久久| 亚洲系列中文字幕| 亚洲av午夜精品一区二区三区| aa级大片免费在线观看| 国产欧美日韩在线视频| 99视频在线播放| 一级黄色在线观看| 亚洲精选久久| 最近2019好看的中文字幕免费 | 伊人成人在线| 在线观看精品国产视频| 免费看毛片的网站| 久久久国产精品入口麻豆 | 国产精品乱码一区二区视频| 91精品电影| 亚洲视频综合网| 午夜免费福利影院| 外国成人毛片| 91久久精品一区二区三| 国精产品一区一区三区视频| 高清全集视频免费在线| 日本一区二区三区四区在线视频| 成人免费视频视频在| 在线观看国产小视频| 久久久国产亚洲精品| 久久免费观看视频| 久久久久久激情| 亚洲精品一区二区在线看| 亚洲天堂成人在线| 国产呦小j女精品视频| 成人av激情人伦小说| 欧美一级艳片视频免费观看| 日韩av在线中文| 国精产品一区二区三区有限公司| 亚洲国产婷婷综合在线精品| 99re6这里有精品热视频| 免费在线看黄网站| 欧美国产成人在线| 四虎影院一区二区三区 | 国产亚洲精品成人av久久ww| 国产网站无遮挡| 欧美调教视频| 日韩电视剧免费观看网站| 丰满岳乱妇一区二区| 国产精品视频3p| 亚洲精品720p| 加勒比精品视频| 日韩激情网站| 亚洲欧美在线一区二区| 久久美女免费视频| 欧洲杯半决赛直播| 日韩有码视频在线| 婷婷久久综合网| 国内自拍一区| 国语自产精品视频在线看抢先版图片| 国产农村妇女精品一区| 日韩精品看片| 久久久精品一区二区| 国产女片a归国片aa| 欧美精品九九| 久久久亚洲天堂| 日韩精品在线免费视频| 老司机久久99久久精品播放免费| 81精品国产乱码久久久久久| www.国产色| 日韩成人精品视频| 国产日韩精品在线观看| 国产黄色一区二区| 99视频精品免费视频| 久久久久久艹| 91精彩在线视频| 一区二区三区色| 香港三级韩国三级日本三级| 欧美黄色三级| 欧美一卡2卡三卡4卡5免费| 国产精品久久久久久亚洲色| 奇米狠狠一区二区三区| 久久精品99国产精品酒店日本| 日本福利片在线观看| 国产一区二区三区久久| 国产精品视频永久免费播放 | 欧美一区二区三区人| 亚洲成年人av| 日本午夜一区| 欧美精品videofree1080p| 无码人妻精品一区二| 国产一区二区视频在线播放| 久久免费99精品久久久久久| 色综合久久久久综合一本到桃花网| 国产精品传媒入口麻豆| 成人在线观看你懂的| 亚洲国产伊人| 亚洲毛片在线观看.| 在线免费日韩av| 麻豆九一精品爱看视频在线观看免费| 国产成人精品免费视频| 精品国产999久久久免费| 久久久国产精华| 日韩欧美猛交xxxxx无码| 日韩av超清在线观看| 精品美女在线观看| 天堂网中文在线观看| 一区二区日韩免费看| 2014亚洲精品| 91女主播在线观看| 色婷婷av一区二区三区之一色屋| 99中文字幕在线| 国产精品一区二区三区av麻| 国产+人+亚洲| 国产青青草视频| 国产欧美日韩在线| 9久久9毛片又大又硬又粗| 国产精品一区三区在线观看| 一区二区三欧美| 毛片视频网站在线观看| 国产jizzjizz一区二区| 在线播放 亚洲| 日本综合久久| 亚洲欧洲美洲在线综合| 国产女同在线观看| 成人午夜电影久久影院| 欧美日韩中文字幕在线播放| 国产精品诱惑| 一区二区三区四区精品| 亚洲色成人www永久网站| www日韩大片| 97国产在线播放| 乱亲女h秽乱长久久久| 欧美猛男性生活免费| 国产免费叼嘿网站免费| 中文字幕亚洲电影| 国产精品自在自线| 国产精品久久久久久久久久10秀| 国产成人精品综合| 免费一级在线观看播放网址| 欧美视频二区36p| 亚洲第一黄色网址| 亚洲一区日本| 鲁丝一区二区三区免费| av高清不卡| 亚洲性xxxx| 中文永久免费观看| 国产精品视频yy9299一区| 一区二区三区网址| 成人影院在线| 成人免费高清完整版在线观看| 国家队第一季免费高清在线观看| 欧美色另类天堂2015| www.久久国产| 青草国产精品久久久久久| 视频一区二区在线观看| 欧美成a人片免费观看久久五月天| 亚洲欧美激情在线视频| 国产又粗又猛又爽又| 中文字幕乱码日本亚洲一区二区| 欧美一级黄色片视频| 精品一区二区三区在线 | www..com久久爱| 国产亚洲精品网站| 精品国产乱码| 成人免费网视频| 污污视频在线看| 亚洲精品电影久久久| 丰满少妇xoxoxo视频| 国产精品情趣视频| 韩国三级与黑人| 日韩视频久久| 日韩精品久久一区| 免费看日产一区二区三区| 国自产精品手机在线观看视频| 色婷婷av一区二区三区之e本道| 午夜欧美在线一二页| 亚洲黄色小说视频| 国产精品自在在线| 国产97在线 | 亚洲| 91欧美在线| 国产伦精品一区二区三区照片91| 麻豆免费在线| 日韩在线激情视频| 天天摸天天碰天天爽天天弄| 欧美性猛片xxxx免费看久爱| 一区二区视频免费看| 99久久精品久久久久久清纯| 国产九九在线观看| 91久久综合| 亚洲一区二区三区免费看| 91夜夜蜜桃臀一区二区三区| 日本人成精品视频在线| av在线官网| 亚洲欧洲在线播放| 不卡av中文字幕| 欧美在线观看你懂的| 久久久久香蕉视频| 国产精品视频一二三区| 国产麻豆剧传媒精品国产av| 久久国产精品区| 国产97在线 | 亚洲| 欧美99在线视频观看| 性欧美大战久久久久久久免费观看 | 激情在线小视频| 亚洲国产精品系列| 91精品视频免费在线观看| 红桃av永久久久| 日韩三级在线观看视频| 91一区二区三区在线观看| www.偷拍.com| 激情久久久久久久久久久久久久久久| 日本一级淫片演员| 欧美色蜜桃97| 欧美极品一区二区| 欧美午夜寂寞| 国产精品传媒毛片三区| www.久久久久爱免| 国产美女搞久久| 韩国三级一区| 欧美影院久久久| 高清在线视频不卡| 欧美激情图片区| 日本片在线观看| 久久韩剧网电视剧| 日本a在线播放| 中文字幕日韩高清| 国产二区在线播放| 亚洲一区二区久久| 久久这里精品| 国产一区二区三区视频免费| 你懂的视频在线免费| 亚洲激情自拍图| 五月天久久久久久| 亚洲国产日韩欧美综合久久| 韩国av免费在线| 日韩精品中文字幕一区二区三区| 亚洲国产无线乱码在线观看| 色婷婷久久一区二区三区麻豆| 国产无遮无挡120秒| 亚洲一区二区五区| 国产精品suv一区二区69| 亚洲精选一二三| 黄色一级视频免费观看| 亚洲欧美日韩久久| 国产乱国产乱老熟300| 亚洲精品视频在线| 久草网在线观看| 亚洲成人中文在线| 国产成人在线免费观看视频| 欧美日韩精品二区| 91在线视频在线观看| 在线视频一区二区三| 成人黄色三级视频| 欧美美女激情18p| 成人黄色免费视频| 日韩激情av在线播放| 粉嫩av一区| 精品国产一区二区三区久久久狼| 天天在线视频色| 欧美美女15p| 日本不卡1234视频| 国产精品网红福利| 日韩成人精品| 久久99精品久久久久久水蜜桃| 秋霞影视一区二区三区| 日韩欧美视频一区二区| 国产精品99一区二区三| 人人妻人人澡人人爽欧美一区| 国产精品av久久久久久麻豆网| 成人在线免费观看视频网站| 亚洲乱码久久| 熟女人妇 成熟妇女系列视频| 欧美资源在线| 视频免费1区二区三区| 成人免费观看视频| 男人舔女人下部高潮全视频| 亚洲欧美日韩国产综合在线| 国产大片中文字幕在线观看| 91福利视频网站| www.久久久久久| 亚洲女人被黑人巨大进入al| 黄色成人在线观看| 91精品国产成人| 亚洲国产精选| 欧美精品二区三区四区免费看视频| 精品一区电影| 亚洲色欲久久久综合网东京热| 一本一本久久| 国产成人美女视频| 91小视频在线| 日韩a级片在线观看| 91国模大尺度私拍在线视频| 精品人妻午夜一区二区三区四区 | 国产精品www| 清纯唯美激情亚洲| 日韩国产美国| 亚洲免费大片| 亚洲一区二区在线视频观看| 99视频国产精品| 免费在线黄色网| 在线亚洲+欧美+日本专区| 日本激情视频网站| 毛片精品免费在线观看| 日本精品网站| 欧美精品123| 国产手机视频一区二区| 又黄又爽又色的视频| 国产精品免费丝袜| caoporn国产| 亚洲国产精品999| 日本一本在线免费福利| 国产欧美一区二区三区在线| 亚洲免费成人av在线| 成人一级生活片| 国产剧情av麻豆香蕉精品| 懂色av粉嫩av浪潮av| 欧美性猛交xxxx乱大交3| 亚洲免费国产视频| 久久国产加勒比精品无码| 国产欧美自拍| 先锋影音亚洲资源| 日韩av午夜在线观看| 玖玖爱在线观看| 狠狠躁夜夜躁人人爽天天天天97| 国产理论视频在线观看| 色悠悠久久久久| 在线免费日韩片| 蜜桃av色综合| 亚洲欧美清纯在线制服| 亚洲黄色免费在线观看| 亚洲成av人片在线观看| 成人毛片在线精品国产| 欧美大片免费观看| 91综合精品国产丝袜长腿久久| 干日本少妇视频| 国产乱码一区二区三区| 朝桐光av在线| 日韩三级高清在线| 午夜伦理在线视频| 999热视频| 激情国产一区| 日韩少妇一区二区| 欧美日韩在线影院| 免费动漫网站在线观看| 国产成人久久久精品一区| 国产一区二区三区四区五区传媒 | 丝袜脚交一区二区| 亚洲一级黄色录像| 欧美中文字幕亚洲一区二区va在线 | 亚洲国产成人在线观看| 欧美xxxx做受欧美.88| 国产精品3区| 影音先锋成人资源网站| 国产一区二区三区av电影| 黄色一级视频在线观看| 亚洲成人亚洲激情| 激情黄产视频在线免费观看| 久久一区免费| 日产国产欧美视频一区精品| 国产调教在线观看| 欧美欧美欧美欧美首页| bestiality新另类大全| 国产精品视频入口| 香蕉亚洲视频| www中文在线| 日韩欧美三级在线| 亚洲美女久久精品| 亚洲精品美女久久7777777| 极品销魂美女一区二区三区| 欧美被狂躁喷白浆精品| 亚洲精品久久久久久下一站| 性欧美超级视频| 在线观看免费黄色片| caoporm超碰国产精品| www.日韩一区| 欧美精品一二区| 一本久久青青| 欧美一级免费在线| 欧美日韩免费观看中文| 99免在线观看免费视频高清| 91精品国产高清久久久久久91裸体| 欧美精品aa| 国产全是老熟女太爽了| 欧美一区二区三区在线电影 | 欧美极品美女电影一区| 日韩大片在线免费观看| 午夜免费福利视频在线观看| 性久久久久久久久久久久| 成人欧美一区| 国产精品美女xx| 久久精品国产99国产| 日韩 欧美 综合| 久久伊人精品天天|