From 9a1540af9e8bd30ad1b34d174bd61c0b34d49231 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Fri, 8 May 2026 12:00:17 +0200 Subject: [PATCH 1/3] ref(lint): no-non-null-assertion in scraps --- eslint.config.ts | 8 +++ .../app/components/core/avatar/avatarList.tsx | 2 + .../app/components/core/avatar/useAvatar.ts | 2 + .../core/avatarButton/avatarButton.tsx | 24 +++++--- .../components/core/compactSelect/control.tsx | 4 +- .../core/compactSelect/gridList/index.tsx | 3 +- .../components/core/compactSelect/list.tsx | 1 + .../core/compactSelect/listBox/index.tsx | 3 +- .../components/core/compactSelect/utils.tsx | 25 +++++---- .../components/core/form/field/baseField.tsx | 2 +- .../core/hotkey/useHotkeys.spec.tsx | 56 +++++++++---------- .../components/core/layout/styles.spec.tsx | 13 +++-- static/app/components/core/layout/styles.tsx | 19 ++----- static/app/components/core/select/select.tsx | 2 +- static/app/components/core/slider/slider.tsx | 1 + static/app/components/core/tabs/tabs.spec.tsx | 20 +++---- 16 files changed, 106 insertions(+), 79 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index c755a4ff58adaf..58193d571ac589 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -1393,4 +1393,12 @@ export default typescript.config([ 'boundaries/dependencies': 'off', }, }, + { + name: 'files/scraps', + files: ['static/app/components/core/**/*.{js,mjs,ts,jsx,tsx}'], + ignores: ['**/*.spec.{js,mjs,ts,jsx,tsx}'], + rules: { + '@typescript-eslint/no-non-null-assertion': 'error', + }, + }, ]); diff --git a/static/app/components/core/avatar/avatarList.tsx b/static/app/components/core/avatar/avatarList.tsx index 3a2ae9f3cf7f9e..9abed110ec20d5 100644 --- a/static/app/components/core/avatar/avatarList.tsx +++ b/static/app/components/core/avatar/avatarList.tsx @@ -66,8 +66,10 @@ export function AvatarList({ if (numCollapsedAvatars === 1) { if (visibleTeamAvatars.length < teams.length) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion visibleTeamAvatars.unshift(teams[teams.length - 1]!); } else if (visibleUserAvatars.length < users.length) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion visibleUserAvatars.unshift(users[users.length - 1]!); } numCollapsedAvatars = 0; diff --git a/static/app/components/core/avatar/useAvatar.ts b/static/app/components/core/avatar/useAvatar.ts index 73ead5bb63213a..cdc2af0d6c8d6f 100644 --- a/static/app/components/core/avatar/useAvatar.ts +++ b/static/app/components/core/avatar/useAvatar.ts @@ -174,8 +174,10 @@ function getInitials(name: string | undefined): Tagged { // Use Array.from as slicing and substring() work on ucs2 segments which // results in only getting half of any 4+ byte character. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion let initials = Array.from(words[0]!)[0]!; if (words.length > 1) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion initials += Array.from(words[words.length - 1]!)[0]!; } return initials.toUpperCase() as Tagged; diff --git a/static/app/components/core/avatarButton/avatarButton.tsx b/static/app/components/core/avatarButton/avatarButton.tsx index b3afa77707c237..beb53604439f25 100644 --- a/static/app/components/core/avatarButton/avatarButton.tsx +++ b/static/app/components/core/avatarButton/avatarButton.tsx @@ -1,6 +1,6 @@ import {useTheme, type Theme} from '@emotion/react'; import styled from '@emotion/styled'; -import {useQuery} from '@tanstack/react-query'; +import {skipToken, useQuery} from '@tanstack/react-query'; import color from 'color'; import type {BaseAvatarProps} from '@sentry/scraps/avatar'; @@ -32,8 +32,10 @@ export function AvatarButton({avatar, size: explicitSize, ...props}: AvatarButto const {data: imageResult} = useQuery({ queryKey: ['avatar-button-chonk', imageUrl, theme.type], - queryFn: () => resolveImageAvatarColors(imageUrl!, theme.type), - enabled: !!imageUrl && avatarDefinition.type === 'image', + queryFn: + imageUrl && avatarDefinition.type === 'image' + ? () => resolveImageAvatarColors(imageUrl, theme.type) + : skipToken, staleTime: Infinity, }); @@ -133,6 +135,7 @@ const StyledAvatarButton = styled(Button)<{chonk: string | undefined}>` // Returns 'fill' when the image covers the full frame edge-to-edge, 'padded' otherwise. // Each edge check returns 'padded' when every pixel on that edge is transparent (alpha < 128). // Pixel (col, row) has its alpha channel at (row * 12 + col) * 4 + 3 in a 12×12 RGBA canvas. +/* eslint-disable @typescript-eslint/no-non-null-assertion */ function shouldPadImage(data: Uint8ClampedArray): 'fill' | 'padded' { // oxfmt-ignore if (!(data[3]!>=128 || data[51]!>=128 || data[99]!>=128 || @@ -159,6 +162,7 @@ function shouldPadImage(data: Uint8ClampedArray): 'fill' | 'padded' { return 'fill'; } +/* eslint-enable @typescript-eslint/no-non-null-assertion */ function readPixels(img: HTMLImageElement): Uint8ClampedArray | null { const SAMPLE_SIZE = 12; @@ -200,18 +204,24 @@ function sampleAvatarColor( const style = shouldPadImage(data); // Accumulate two sets: chromatic pixels (saturation ≥ 0.15) and all opaque pixels. - // oxfmt-ignore - let cr = 0, cg = 0, cb = 0, ccount = 0; - // oxfmt-ignore - let ar = 0, ag = 0, ab = 0, acount = 0; + let cr = 0, + cg = 0, + cb = 0, + ccount = 0; + let ar = 0, + ag = 0, + ab = 0, + acount = 0; for (let i = 0; i < data.length; i += 4) { + /* eslint-disable @typescript-eslint/no-non-null-assertion */ if (data[i + 3]! < 128) continue; const r = data[i]!, g = data[i + 1]!, b = data[i + 2]!; + /* eslint-enable @typescript-eslint/no-non-null-assertion */ // accumulate all pixels ar += r; ag += g; diff --git a/static/app/components/core/compactSelect/control.tsx b/static/app/components/core/compactSelect/control.tsx index c6079406a311e4..0091352c2eea7d 100644 --- a/static/app/components/core/compactSelect/control.tsx +++ b/static/app/components/core/compactSelect/control.tsx @@ -504,13 +504,13 @@ export function Control({ ref={menuRef} width={menuWidth ?? menuFullWidth} height={menuHeight} - minWidth={menuMinWidth ?? overlayProps.style!.minWidth} + minWidth={menuMinWidth ?? overlayProps.style?.minWidth} maxWidth={ overlayProps.style?.maxWidth ? `calc(${withUnits(overlayProps.style.maxWidth)} * 0.9)` : undefined } - maxHeight={overlayProps.style!.maxHeight} + maxHeight={overlayProps.style?.maxHeight} maxHeightProp={maxMenuHeight} data-menu-has-header={!!menuTitle || clearable} data-menu-has-search={searchEnabled} diff --git a/static/app/components/core/compactSelect/gridList/index.tsx b/static/app/components/core/compactSelect/gridList/index.tsx index c7569eca663818..506fdc55088aea 100644 --- a/static/app/components/core/compactSelect/gridList/index.tsx +++ b/static/app/components/core/compactSelect/gridList/index.tsx @@ -143,7 +143,8 @@ function GridList({ ref={ref} > {virtualizer.items.map(row => { - const item = listItems[row.index]!; + const item = listItems[row.index]; + if (!item) return null; if (item.type === 'section') { return ( ({ disallowEmptySelection: !clearable, allowDuplicateSelectionEvents: true, onSelectionChange: selection => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const selectedOption = getSelectedOptions(items, selection)[0]!; onChange?.(selectedOption); diff --git a/static/app/components/core/compactSelect/listBox/index.tsx b/static/app/components/core/compactSelect/listBox/index.tsx index e9213c4e753306..280f0220755629 100644 --- a/static/app/components/core/compactSelect/listBox/index.tsx +++ b/static/app/components/core/compactSelect/listBox/index.tsx @@ -251,7 +251,8 @@ export function ListBox({ > {overlayIsOpen && virtualizer.items.map(row => { - const item = listItems[row.index]!; + const item = listItems[row.index]; + if (!item) return null; if (item.type === 'section') { return ( ( // // Then, limit the number of remaining options to `limit` // - let threshold = [Infinity, Infinity]; + let threshold: [number, number] = [Infinity, Infinity]; let accumulator = 0; let currentIndex = 0; while (currentIndex < orderedRemainingItems.length) { - const item = orderedRemainingItems[currentIndex]!; - const delta = 'options' in item ? item.options.length : 1; + const item = orderedRemainingItems[currentIndex]; + const delta = item && 'options' in item ? item.options.length : 1; if (accumulator + delta > limit) { threshold = [currentIndex, limit - accumulator]; @@ -233,15 +233,18 @@ export function getHiddenOptions( currentIndex += 1; } - for (let i = threshold[0]!; i < orderedRemainingItems.length; i++) { - const item = orderedRemainingItems[i]!; - if ('options' in item) { - const startingIndex = i === threshold[0] ? threshold[1]! : 0; - for (let j = startingIndex; j < item.options.length; j++) { - hiddenOptionsSet.add(item.options[j]!.key); + for (let i = threshold[0]; i < orderedRemainingItems.length; i++) { + const item = orderedRemainingItems[i]; + if (item) { + if ('options' in item) { + const startingIndex = i === threshold[0] ? threshold[1] : 0; + for (let j = startingIndex; j < item.options.length; j++) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + hiddenOptionsSet.add(item.options[j]!.key); + } + } else { + hiddenOptionsSet.add(item.key); } - } else { - hiddenOptionsSet.add(item.key); } } diff --git a/static/app/components/core/form/field/baseField.tsx b/static/app/components/core/form/field/baseField.tsx index bf2f9a2728a51b..da2d5e18b6d239 100644 --- a/static/app/components/core/form/field/baseField.tsx +++ b/static/app/components/core/form/field/baseField.tsx @@ -93,7 +93,7 @@ function useFocusRestore(ref: React.RefObject) { } function onBlur() { - if (el!.hasAttribute('disabled')) { + if (el?.hasAttribute('disabled')) { hadFocusRef.current = true; } } diff --git a/static/app/components/core/hotkey/useHotkeys.spec.tsx b/static/app/components/core/hotkey/useHotkeys.spec.tsx index 45473d372a5a3a..19d26e38ddf4bd 100644 --- a/static/app/components/core/hotkey/useHotkeys.spec.tsx +++ b/static/app/components/core/hotkey/useHotkeys.spec.tsx @@ -57,7 +57,7 @@ describe('useHotkeys', () => { expect(callback).not.toHaveBeenCalled(); const evt = makeKeyEventFixture('s', {ctrlKey: true}); - events.keydown!(evt); + events.keydown?.(evt); expect(evt.preventDefault).toHaveBeenCalled(); expect(callback).toHaveBeenCalled(); @@ -73,12 +73,12 @@ describe('useHotkeys', () => { expect(events.keydown).toBeDefined(); expect(callback).not.toHaveBeenCalled(); - events.keydown!(makeKeyEventFixture('s', {ctrlKey: true})); + events.keydown?.(makeKeyEventFixture('s', {ctrlKey: true})); expect(callback).toHaveBeenCalled(); callback.mockClear(); - events.keydown!(makeKeyEventFixture('m', {metaKey: true})); + events.keydown?.(makeKeyEventFixture('m', {metaKey: true})); expect(callback).toHaveBeenCalled(); }); @@ -93,7 +93,7 @@ describe('useHotkeys', () => { expect(events.keydown).toBeDefined(); expect(callback).not.toHaveBeenCalled(); - events.keydown!( + events.keydown?.( makeKeyEventFixture('x', { altKey: true, metaKey: true, @@ -115,7 +115,7 @@ describe('useHotkeys', () => { expect(events.keydown).toBeDefined(); expect(callback).not.toHaveBeenCalled(); - events.keydown!( + events.keydown?.( makeKeyEventFixture('x', { altKey: true, metaKey: true, @@ -137,17 +137,17 @@ describe('useHotkeys', () => { expect(events.keydown).toBeDefined(); expect(callback).not.toHaveBeenCalled(); - events.keydown!(makeKeyEventFixture('s', {ctrlKey: true})); + events.keydown?.(makeKeyEventFixture('s', {ctrlKey: true})); expect(callback).toHaveBeenCalled(); callback.mockClear(); rerender({match: 'command+m'}); - events.keydown!(makeKeyEventFixture('s', {ctrlKey: true})); + events.keydown?.(makeKeyEventFixture('s', {ctrlKey: true})); expect(callback).not.toHaveBeenCalled(); - events.keydown!(makeKeyEventFixture('m', {metaKey: true})); + events.keydown?.(makeKeyEventFixture('m', {metaKey: true})); expect(callback).toHaveBeenCalled(); }); @@ -158,7 +158,7 @@ describe('useHotkeys', () => { initialProps: [{match: ['/'], callback}], }); - events.keydown!(makeKeyEventFixture('/', {target: document.createElement('input')})); + events.keydown?.(makeKeyEventFixture('/', {target: document.createElement('input')})); expect(callback).not.toHaveBeenCalled(); }); @@ -170,7 +170,7 @@ describe('useHotkeys', () => { initialProps: [{match: ['/'], callback, includeInputs: true}], }); - events.keydown!(makeKeyEventFixture('/', {target: document.createElement('input')})); + events.keydown?.(makeKeyEventFixture('/', {target: document.createElement('input')})); expect(callback).toHaveBeenCalled(); }); @@ -183,7 +183,7 @@ describe('useHotkeys', () => { }); const evt = makeKeyEventFixture('s', {ctrlKey: true}); - events.keydown!(evt); + events.keydown?.(evt); expect(evt.preventDefault).not.toHaveBeenCalled(); expect(callback).toHaveBeenCalled(); @@ -199,7 +199,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'command+shift+1', callback}], }); - events.keydown!({ + events.keydown?.({ key: '!', code: 'Digit1', metaKey: true, @@ -219,7 +219,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'command+k', callback}], }); - events.keydown!({ + events.keydown?.({ key: 'k', code: 'KeyK', metaKey: true, @@ -238,7 +238,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'command+k', callback}], }); - events.keydown!({ + events.keydown?.({ key: 'к', code: 'KeyK', metaKey: true, @@ -255,7 +255,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'Escape', callback}], }); - events.keydown!({key: 'Escape', code: 'Escape', preventDefault: jest.fn()}); + events.keydown?.({key: 'Escape', code: 'Escape', preventDefault: jest.fn()}); expect(callback).toHaveBeenCalled(); }); @@ -267,7 +267,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'left', callback}], }); - events.keydown!({key: 'ArrowLeft', code: 'ArrowLeft', preventDefault: jest.fn()}); + events.keydown?.({key: 'ArrowLeft', code: 'ArrowLeft', preventDefault: jest.fn()}); expect(callback).toHaveBeenCalled(); }); @@ -279,10 +279,10 @@ describe('useHotkeys', () => { initialProps: [{match: ['left', 'h'], callback}], }); - events.keydown!({key: 'ArrowLeft', code: 'ArrowLeft', preventDefault: jest.fn()}); + events.keydown?.({key: 'ArrowLeft', code: 'ArrowLeft', preventDefault: jest.fn()}); expect(callback).toHaveBeenCalledTimes(1); - events.keydown!({key: 'h', code: 'KeyH', preventDefault: jest.fn()}); + events.keydown?.({key: 'h', code: 'KeyH', preventDefault: jest.fn()}); expect(callback).toHaveBeenCalledTimes(2); }); @@ -294,7 +294,7 @@ describe('useHotkeys', () => { }); const evt = makeKeyEventFixture('Escape'); - events.keydown!(evt); + events.keydown?.(evt); expect(callback).not.toHaveBeenCalled(); expect(evt.preventDefault).not.toHaveBeenCalled(); @@ -307,12 +307,12 @@ describe('useHotkeys', () => { initialProps: [{match: 'Escape', enabled: false, callback}], }); - events.keydown!(makeKeyEventFixture('Escape')); + events.keydown?.(makeKeyEventFixture('Escape')); expect(callback).not.toHaveBeenCalled(); rerender([{match: 'Escape', enabled: true, callback}]); - events.keydown!(makeKeyEventFixture('Escape')); + events.keydown?.(makeKeyEventFixture('Escape')); expect(callback).toHaveBeenCalledTimes(1); }); @@ -325,7 +325,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'command+shift+k', callback}], }); - events.keydown!({ + events.keydown?.({ key: 'K', code: 'KeyK', metaKey: true, @@ -354,7 +354,7 @@ describe('useHotkeys', () => { metaKey: true, preventDefault: jest.fn(), }; - events.keydown!(evt); + events.keydown?.(evt); expect(callback).not.toHaveBeenCalled(); expect(evt.preventDefault).not.toHaveBeenCalled(); @@ -375,10 +375,10 @@ describe('useHotkeys', () => { initialProps: [{match: 'mod+k', callback}], }); - events.keydown!(makeKeyEventFixture('k', {metaKey: true})); + events.keydown?.(makeKeyEventFixture('k', {metaKey: true})); expect(callback).toHaveBeenCalledTimes(1); - events.keydown!(makeKeyEventFixture('k', {ctrlKey: true})); + events.keydown?.(makeKeyEventFixture('k', {ctrlKey: true})); expect(callback).toHaveBeenCalledTimes(1); }); @@ -390,10 +390,10 @@ describe('useHotkeys', () => { initialProps: [{match: 'mod+k', callback}], }); - events.keydown!(makeKeyEventFixture('k', {ctrlKey: true})); + events.keydown?.(makeKeyEventFixture('k', {ctrlKey: true})); expect(callback).toHaveBeenCalledTimes(1); - events.keydown!(makeKeyEventFixture('k', {metaKey: true})); + events.keydown?.(makeKeyEventFixture('k', {metaKey: true})); expect(callback).toHaveBeenCalledTimes(1); }); @@ -407,7 +407,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'mod+k', callback}], }); - events.keydown!(makeKeyEventFixture('k', {metaKey: true, ctrlKey: true})); + events.keydown?.(makeKeyEventFixture('k', {metaKey: true, ctrlKey: true})); expect(callback).not.toHaveBeenCalled(); }); }); diff --git a/static/app/components/core/layout/styles.spec.tsx b/static/app/components/core/layout/styles.spec.tsx index 6e00850fd9cf36..8136571cb744ee 100644 --- a/static/app/components/core/layout/styles.spec.tsx +++ b/static/app/components/core/layout/styles.spec.tsx @@ -3,6 +3,7 @@ import {ThemeFixture} from 'sentry-fixture/theme'; import {act, renderHookWithProviders} from 'sentry-test/reactTestingLibrary'; +import {assert} from 'sentry/types/utils'; import type {BreakpointSize} from 'sentry/utils/theme'; // eslint-disable-next-line boundaries/dependencies @@ -58,7 +59,8 @@ const setupMediaQueries = ( describe('rc', () => { it('returns a simple CSS declaration for a plain string value', () => { - const output = rc('color', 'red', theme)!; + const output = rc('color', 'red', theme); + assert(output); expect( normalizeCss( css` @@ -73,7 +75,8 @@ describe('rc', () => { }); it('applies a resolver to a plain value', () => { - const output = rc('color', 'primary', theme, value => `resolved-${value}`)!; + const output = rc('color', 'primary', theme, value => `resolved-${value}`); + assert(output); expect( normalizeCss( css` @@ -89,7 +92,8 @@ describe('rc', () => { it('generates media queries for responsive values', () => { // First defined breakpoint gets both min-width and max-width; subsequent get min-width only. - const output = rc('color', {xs: 'blue', md: 'green'}, theme)!; + const output = rc('color', {xs: 'blue', md: 'green'}, theme); + assert(output); expect( normalizeCss( css` @@ -101,7 +105,8 @@ describe('rc', () => { it('skips undefined intermediate breakpoints', () => { // xs and md are defined; 2xs, sm, lg, xl, 2xl are absent from the output. - const output = rc('font-size', {xs: 'md', md: 'lg'}, theme)!; + const output = rc('font-size', {xs: 'md', md: 'lg'}, theme); + assert(output); expect( normalizeCss( css` diff --git a/static/app/components/core/layout/styles.tsx b/static/app/components/core/layout/styles.tsx index 951fca0508b02c..00bfaae2db1a89 100644 --- a/static/app/components/core/layout/styles.tsx +++ b/static/app/components/core/layout/styles.tsx @@ -110,18 +110,10 @@ function resolveRadius(sizeComponent: RadiusSize | undefined, theme: Theme) { } function resolveSpacing(sizeComponent: SpaceSize, theme: Theme) { - if (sizeComponent === undefined) { - return; - } - return theme.space[sizeComponent] ?? theme.space['0']; } function resolveMargin(sizeComponent: Margin, theme: Theme) { - if (sizeComponent === undefined) { - return; - } - if (sizeComponent === 'auto') { return 'auto'; } @@ -209,7 +201,7 @@ export function getMargin( if (margin.length < 3) { // This can only be a single margin value, so we can resolve it directly. - return resolveMargin(margin as Margin, theme)!; + return resolveMargin(margin as Margin, theme); } return margin @@ -249,8 +241,8 @@ export function useResponsivePropValue>( // If we don't have an exact match, find the next smallest breakpoint for (let i = activeIndex - 1; i >= 0; i--) { - const smallerBreakpoint = BREAKPOINT_ORDER[i]!; - if (prop[smallerBreakpoint] !== undefined) { + const smallerBreakpoint = BREAKPOINT_ORDER[i]; + if (smallerBreakpoint && prop[smallerBreakpoint] !== undefined) { value = prop[smallerBreakpoint]; break; } @@ -259,14 +251,15 @@ export function useResponsivePropValue>( // If no smaller breakpoint found, then window < smallest breakpoint, so we need to find the first larger breakpoint if (value === undefined) { for (let i = activeIndex + 1; i < BREAKPOINT_ORDER.length; i++) { - const largerBreakpoint = BREAKPOINT_ORDER[i]!; - if (prop[largerBreakpoint] !== undefined) { + const largerBreakpoint = BREAKPOINT_ORDER[i]; + if (largerBreakpoint && prop[largerBreakpoint] !== undefined) { value = prop[largerBreakpoint]; break; } } } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return value!; } diff --git a/static/app/components/core/select/select.tsx b/static/app/components/core/select/select.tsx index 6d5aec48280d77..9a8b2640e28ea8 100644 --- a/static/app/components/core/select/select.tsx +++ b/static/app/components/core/select/select.tsx @@ -347,7 +347,7 @@ function isGroupedOptions( if (!maybe || maybe.length === 0) { return false; } - return (maybe as GroupedOptionsType)[0]!.options !== undefined; + return (maybe as GroupedOptionsType)[0]?.options !== undefined; } function MultiValueRemove( diff --git a/static/app/components/core/slider/slider.tsx b/static/app/components/core/slider/slider.tsx index 1ef36fa1d21853..db80293c847285 100644 --- a/static/app/components/core/slider/slider.tsx +++ b/static/app/components/core/slider/slider.tsx @@ -103,6 +103,7 @@ export function Slider({ state ); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion useImperativeHandle(ref, () => inputRef.current!, []); const thumbPercent = state.getThumbPercent(0); diff --git a/static/app/components/core/tabs/tabs.spec.tsx b/static/app/components/core/tabs/tabs.spec.tsx index 4fd64abbda6d34..962d17cec23dae 100644 --- a/static/app/components/core/tabs/tabs.spec.tsx +++ b/static/app/components/core/tabs/tabs.spec.tsx @@ -20,7 +20,7 @@ const TABS = [ label: 'Attachments', content: 'Called though excuse length ye needed it he having.', }, -]; +] as const; describe('Tabs', () => { it('renders tabs list', () => { @@ -47,11 +47,11 @@ describe('Tabs', () => { }); // The first tab item is selected and its content visible - expect(screen.getByRole('tab', {name: TABS[0]!.label})).toHaveAttribute( + expect(screen.getByRole('tab', {name: TABS[0].label})).toHaveAttribute( 'aria-selected', 'true' ); - expect(screen.getByText(TABS[0]!.content)).toBeInTheDocument(); + expect(screen.getByText(TABS[0].content)).toBeInTheDocument(); }); it('renders tabs list when disabled', () => { @@ -71,11 +71,11 @@ describe('Tabs', () => { ); // The first tab item is selected and its content visible - expect(screen.getByRole('tab', {name: TABS[0]!.label})).toHaveAttribute( + expect(screen.getByRole('tab', {name: TABS[0].label})).toHaveAttribute( 'aria-selected', 'true' ); - expect(screen.getByText(TABS[0]!.content)).toBeInTheDocument(); + expect(screen.getByText(TABS[0].content)).toBeInTheDocument(); // All tabs are marked as disabled TABS.forEach(tab => { @@ -110,7 +110,7 @@ describe('Tabs', () => { 'aria-selected', 'true' ); - expect(screen.getByText(TABS[1]!.content)).toBeInTheDocument(); + expect(screen.getByText(TABS[1].content)).toBeInTheDocument(); }); it('changes tabs using keyboard navigation', async () => { @@ -141,7 +141,7 @@ describe('Tabs', () => { 'aria-selected', 'true' ); - expect(screen.getByText(TABS[1]!.content)).toBeInTheDocument(); + expect(screen.getByText(TABS[1].content)).toBeInTheDocument(); }); it('changes tabs on key press in vertical orientation', async () => { @@ -172,7 +172,7 @@ describe('Tabs', () => { 'aria-selected', 'true' ); - expect(screen.getByText(TABS[1]!.content)).toBeInTheDocument(); + expect(screen.getByText(TABS[1].content)).toBeInTheDocument(); }); it('renders disabled tabs', () => { @@ -230,7 +230,7 @@ describe('Tabs', () => { // Command/ctrl/shift-clicking on a tab link doesn't change the tab selection. // The expected behavior is that clicking on a tab link will open a new browser // tab/window. The current view shouldn't update. - const secondTabEl = screen.getByRole('tab', {name: TABS[1]!.label}); + const secondTabEl = screen.getByRole('tab', {name: TABS[1].label}); const secondTabLink = within(secondTabEl).getByRole('link', {hidden: true}); const user = userEvent.setup(); @@ -247,7 +247,7 @@ describe('Tabs', () => { await user.click(secondTabLink); await user.keyboard('[/ShiftLeft]'); - expect(screen.getByRole('tab', {name: TABS[0]!.label})).toHaveAttribute( + expect(screen.getByRole('tab', {name: TABS[0].label})).toHaveAttribute( 'aria-selected', 'true' ); From 0f93a5f5cdad3fe37b5e4f9dc8b2e09d5ac5ac53 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Fri, 8 May 2026 13:42:16 +0200 Subject: [PATCH 2/3] ref: revert tests --- .../core/hotkey/useHotkeys.spec.tsx | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/static/app/components/core/hotkey/useHotkeys.spec.tsx b/static/app/components/core/hotkey/useHotkeys.spec.tsx index 19d26e38ddf4bd..45473d372a5a3a 100644 --- a/static/app/components/core/hotkey/useHotkeys.spec.tsx +++ b/static/app/components/core/hotkey/useHotkeys.spec.tsx @@ -57,7 +57,7 @@ describe('useHotkeys', () => { expect(callback).not.toHaveBeenCalled(); const evt = makeKeyEventFixture('s', {ctrlKey: true}); - events.keydown?.(evt); + events.keydown!(evt); expect(evt.preventDefault).toHaveBeenCalled(); expect(callback).toHaveBeenCalled(); @@ -73,12 +73,12 @@ describe('useHotkeys', () => { expect(events.keydown).toBeDefined(); expect(callback).not.toHaveBeenCalled(); - events.keydown?.(makeKeyEventFixture('s', {ctrlKey: true})); + events.keydown!(makeKeyEventFixture('s', {ctrlKey: true})); expect(callback).toHaveBeenCalled(); callback.mockClear(); - events.keydown?.(makeKeyEventFixture('m', {metaKey: true})); + events.keydown!(makeKeyEventFixture('m', {metaKey: true})); expect(callback).toHaveBeenCalled(); }); @@ -93,7 +93,7 @@ describe('useHotkeys', () => { expect(events.keydown).toBeDefined(); expect(callback).not.toHaveBeenCalled(); - events.keydown?.( + events.keydown!( makeKeyEventFixture('x', { altKey: true, metaKey: true, @@ -115,7 +115,7 @@ describe('useHotkeys', () => { expect(events.keydown).toBeDefined(); expect(callback).not.toHaveBeenCalled(); - events.keydown?.( + events.keydown!( makeKeyEventFixture('x', { altKey: true, metaKey: true, @@ -137,17 +137,17 @@ describe('useHotkeys', () => { expect(events.keydown).toBeDefined(); expect(callback).not.toHaveBeenCalled(); - events.keydown?.(makeKeyEventFixture('s', {ctrlKey: true})); + events.keydown!(makeKeyEventFixture('s', {ctrlKey: true})); expect(callback).toHaveBeenCalled(); callback.mockClear(); rerender({match: 'command+m'}); - events.keydown?.(makeKeyEventFixture('s', {ctrlKey: true})); + events.keydown!(makeKeyEventFixture('s', {ctrlKey: true})); expect(callback).not.toHaveBeenCalled(); - events.keydown?.(makeKeyEventFixture('m', {metaKey: true})); + events.keydown!(makeKeyEventFixture('m', {metaKey: true})); expect(callback).toHaveBeenCalled(); }); @@ -158,7 +158,7 @@ describe('useHotkeys', () => { initialProps: [{match: ['/'], callback}], }); - events.keydown?.(makeKeyEventFixture('/', {target: document.createElement('input')})); + events.keydown!(makeKeyEventFixture('/', {target: document.createElement('input')})); expect(callback).not.toHaveBeenCalled(); }); @@ -170,7 +170,7 @@ describe('useHotkeys', () => { initialProps: [{match: ['/'], callback, includeInputs: true}], }); - events.keydown?.(makeKeyEventFixture('/', {target: document.createElement('input')})); + events.keydown!(makeKeyEventFixture('/', {target: document.createElement('input')})); expect(callback).toHaveBeenCalled(); }); @@ -183,7 +183,7 @@ describe('useHotkeys', () => { }); const evt = makeKeyEventFixture('s', {ctrlKey: true}); - events.keydown?.(evt); + events.keydown!(evt); expect(evt.preventDefault).not.toHaveBeenCalled(); expect(callback).toHaveBeenCalled(); @@ -199,7 +199,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'command+shift+1', callback}], }); - events.keydown?.({ + events.keydown!({ key: '!', code: 'Digit1', metaKey: true, @@ -219,7 +219,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'command+k', callback}], }); - events.keydown?.({ + events.keydown!({ key: 'k', code: 'KeyK', metaKey: true, @@ -238,7 +238,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'command+k', callback}], }); - events.keydown?.({ + events.keydown!({ key: 'к', code: 'KeyK', metaKey: true, @@ -255,7 +255,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'Escape', callback}], }); - events.keydown?.({key: 'Escape', code: 'Escape', preventDefault: jest.fn()}); + events.keydown!({key: 'Escape', code: 'Escape', preventDefault: jest.fn()}); expect(callback).toHaveBeenCalled(); }); @@ -267,7 +267,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'left', callback}], }); - events.keydown?.({key: 'ArrowLeft', code: 'ArrowLeft', preventDefault: jest.fn()}); + events.keydown!({key: 'ArrowLeft', code: 'ArrowLeft', preventDefault: jest.fn()}); expect(callback).toHaveBeenCalled(); }); @@ -279,10 +279,10 @@ describe('useHotkeys', () => { initialProps: [{match: ['left', 'h'], callback}], }); - events.keydown?.({key: 'ArrowLeft', code: 'ArrowLeft', preventDefault: jest.fn()}); + events.keydown!({key: 'ArrowLeft', code: 'ArrowLeft', preventDefault: jest.fn()}); expect(callback).toHaveBeenCalledTimes(1); - events.keydown?.({key: 'h', code: 'KeyH', preventDefault: jest.fn()}); + events.keydown!({key: 'h', code: 'KeyH', preventDefault: jest.fn()}); expect(callback).toHaveBeenCalledTimes(2); }); @@ -294,7 +294,7 @@ describe('useHotkeys', () => { }); const evt = makeKeyEventFixture('Escape'); - events.keydown?.(evt); + events.keydown!(evt); expect(callback).not.toHaveBeenCalled(); expect(evt.preventDefault).not.toHaveBeenCalled(); @@ -307,12 +307,12 @@ describe('useHotkeys', () => { initialProps: [{match: 'Escape', enabled: false, callback}], }); - events.keydown?.(makeKeyEventFixture('Escape')); + events.keydown!(makeKeyEventFixture('Escape')); expect(callback).not.toHaveBeenCalled(); rerender([{match: 'Escape', enabled: true, callback}]); - events.keydown?.(makeKeyEventFixture('Escape')); + events.keydown!(makeKeyEventFixture('Escape')); expect(callback).toHaveBeenCalledTimes(1); }); @@ -325,7 +325,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'command+shift+k', callback}], }); - events.keydown?.({ + events.keydown!({ key: 'K', code: 'KeyK', metaKey: true, @@ -354,7 +354,7 @@ describe('useHotkeys', () => { metaKey: true, preventDefault: jest.fn(), }; - events.keydown?.(evt); + events.keydown!(evt); expect(callback).not.toHaveBeenCalled(); expect(evt.preventDefault).not.toHaveBeenCalled(); @@ -375,10 +375,10 @@ describe('useHotkeys', () => { initialProps: [{match: 'mod+k', callback}], }); - events.keydown?.(makeKeyEventFixture('k', {metaKey: true})); + events.keydown!(makeKeyEventFixture('k', {metaKey: true})); expect(callback).toHaveBeenCalledTimes(1); - events.keydown?.(makeKeyEventFixture('k', {ctrlKey: true})); + events.keydown!(makeKeyEventFixture('k', {ctrlKey: true})); expect(callback).toHaveBeenCalledTimes(1); }); @@ -390,10 +390,10 @@ describe('useHotkeys', () => { initialProps: [{match: 'mod+k', callback}], }); - events.keydown?.(makeKeyEventFixture('k', {ctrlKey: true})); + events.keydown!(makeKeyEventFixture('k', {ctrlKey: true})); expect(callback).toHaveBeenCalledTimes(1); - events.keydown?.(makeKeyEventFixture('k', {metaKey: true})); + events.keydown!(makeKeyEventFixture('k', {metaKey: true})); expect(callback).toHaveBeenCalledTimes(1); }); @@ -407,7 +407,7 @@ describe('useHotkeys', () => { initialProps: [{match: 'mod+k', callback}], }); - events.keydown?.(makeKeyEventFixture('k', {metaKey: true, ctrlKey: true})); + events.keydown!(makeKeyEventFixture('k', {metaKey: true, ctrlKey: true})); expect(callback).not.toHaveBeenCalled(); }); }); From c327a88f5559da35f92787dfbbb70852d91b22e9 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Mon, 11 May 2026 16:36:22 +0200 Subject: [PATCH 3/3] fix: code-review findings --- static/app/components/core/avatar/useAvatar.ts | 7 +++---- static/app/components/core/compactSelect/utils.tsx | 5 ++--- static/app/components/core/layout/styles.tsx | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/static/app/components/core/avatar/useAvatar.ts b/static/app/components/core/avatar/useAvatar.ts index cdc2af0d6c8d6f..0e895f99762c6a 100644 --- a/static/app/components/core/avatar/useAvatar.ts +++ b/static/app/components/core/avatar/useAvatar.ts @@ -174,11 +174,10 @@ function getInitials(name: string | undefined): Tagged { // Use Array.from as slicing and substring() work on ucs2 segments which // results in only getting half of any 4+ byte character. - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - let initials = Array.from(words[0]!)[0]!; + + let initials = Array.from(words[0] ?? '')[0] ?? ''; if (words.length > 1) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - initials += Array.from(words[words.length - 1]!)[0]!; + initials += Array.from(words[words.length - 1] ?? '')[0] ?? ''; } return initials.toUpperCase() as Tagged; } diff --git a/static/app/components/core/compactSelect/utils.tsx b/static/app/components/core/compactSelect/utils.tsx index ba282d7f503e59..e8eb7c9be715dd 100644 --- a/static/app/components/core/compactSelect/utils.tsx +++ b/static/app/components/core/compactSelect/utils.tsx @@ -238,9 +238,8 @@ export function getHiddenOptions( if (item) { if ('options' in item) { const startingIndex = i === threshold[0] ? threshold[1] : 0; - for (let j = startingIndex; j < item.options.length; j++) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - hiddenOptionsSet.add(item.options[j]!.key); + for (const option of item.options.slice(startingIndex)) { + hiddenOptionsSet.add(option.key); } } else { hiddenOptionsSet.add(item.key); diff --git a/static/app/components/core/layout/styles.tsx b/static/app/components/core/layout/styles.tsx index 00bfaae2db1a89..28993ae21bbae6 100644 --- a/static/app/components/core/layout/styles.tsx +++ b/static/app/components/core/layout/styles.tsx @@ -218,12 +218,12 @@ export function getMargin( type ResponsiveValue = T extends Responsive ? U : never; export function useResponsivePropValue>( prop: T -): ResponsiveValue { +): T | ResponsiveValue { const activeBreakpoint = useActiveBreakpoint(); // Only resolve the active breakpoint if the prop is responsive, else ignore it. if (!isResponsive(prop)) { - return prop as unknown as ResponsiveValue; + return prop; } if (Object.keys(prop).length === 0) {