adding enzyme-like .debug() feature to Node instances#1088
Conversation
e4c8d7d to
2cffa04
Compare
packages/react-testing/src/types.ts
Outdated
| ): MaybeFunctionReturnType<NonNullable<Props[K]>>; | ||
| triggerKeypath<T = unknown>(keypath: string, ...args: unknown[]): T; | ||
|
|
||
| debug(maxDepth?: number): string; |
There was a problem hiding this comment.
could also be handy to have an omitChildren flag.
There was a problem hiding this comment.
also maxDepth is a poor choice for a name, it really represents prop object expansion depth. perhaps a better approach is {propsDepth?: number; childDepth?: number}?
There was a problem hiding this comment.
I'm not sure when I'd want those two to be different values, or if i did that I'd remember that as an option. A single value for both feels more intuitive.
There was a problem hiding this comment.
i can see some legit use cases.
I want to see max detail for all props on a single component with no children
wrapper.findWhere(({type}) => type && type.name === 'Header').debug({verbosity: 9, depth: 0})
Expand to see a rather large expansion of props structure
<Header
breadcrumbs={[{content: 'Bar', url: '/foo/bar'}]}
title="Foo"
separator
polaris={{
appBridge: undefined,
intl: {
translate: ['Function anonymous'],
translation: {
Polaris: {
ActionMenu: {RollupActions: {rollupButton: 'Actions'}},
Autocomplete: {spinnerAccessibilityLabel: 'Loading'},
Avatar: {
label: 'Avatar',
labelWithInitials: 'Avatar with initials {initials}',
},
Badge: {
PROGRESS_LABELS: {
complete: 'Complete',
incomplete: 'Incomplete',
partiallyComplete: 'Partially complete',
},
STATUS_LABELS: {
attention: 'Attention',
info: 'Info',
new: 'New',
success: 'Success',
warning: 'Warning',
},
},
Button: {spinnerAccessibilityLabel: 'Loading'},
Common: {
cancel: 'Cancel',
checkbox: 'checkbox',
clear: 'Clear',
close: 'Close',
more: 'More',
newWindowAccessibilityHint: '(opens a new window)',
submit: 'Submit',
undo: 'Undo',
},
ContextualSaveBar: {discard: 'Discard', save: 'Save'},
DataTable: {
navAccessibilityLabel:
'Scroll table {direction} one column',
sortAccessibilityLabel: 'sort {direction} by',
totalsRowHeading: 'Totals',
},
DatePicker: {
daysAbbreviated: {
friday: 'Fr',
monday: 'Mo',
saturday: 'Sa',
sunday: 'Su',
thursday: 'Th',
tuesday: 'Tu',
wednesday: 'We',
},
months: {
april: 'April',
august: 'August',
december: 'December',
february: 'February',
january: 'January',
july: 'July',
june: 'June',
march: 'March',
may: 'May',
november: 'November',
october: 'October',
september: 'September',
},
nextMonth: 'Show next month, {nextMonth} {nextYear}',
previousMonth:
'Show previous month, {previousMonthName} {showPreviousYear}',
today: 'Today ',
},
DiscardConfirmationModal: {
message:
'If you discard changes, you’ll delete any edits you made since you last saved.',
primaryAction: 'Discard changes',
secondaryAction: 'Continue editing',
title: 'Discard all unsaved changes',
},
DropZone: {
FileUpload: {
actionHintFile: 'or drop files to upload',
actionHintImage: 'or drop images to upload',
actionTitleFile: 'Add file',
actionTitleImage: 'Add image',
label: 'Upload file',
},
errorOverlayTextFile: 'File type is not valid',
errorOverlayTextImage: 'Image type is not valid',
overlayTextFile: 'Drop file to upload',
overlayTextImage: 'Drop image to upload',
},
EmptySearchResult: {altText: 'Empty search results'},
Filters: {
cancel: 'Cancel',
clear: 'Clear',
clearAllFilters: 'Clear all filters',
clearLabel: 'Clear {filterName}',
done: 'Done',
filter: 'Filter {resourceName}',
moreFilters: 'More filters',
noFiltersApplied: 'No filters applied',
},
Frame: {
Navigation: {
closeMobileNavigationLabel: 'Close navigation',
},
skipToContent: 'Skip to content',
},
Icon: {
backdropWarning:
'The {color} icon doesn’t accept backdrops. The icon colors that have backdrops are: {colorsWithBackDrops}',
},
Modal: {
iFrameTitle: 'body markup',
modalWarning:
'These required properties are missing from Modal: {missingProps}',
},
Pagination: {
next: 'Next',
pagination: 'Pagination',
previous: 'Previous',
},
ProgressBar: {
exceedWarningMessage:
'Values passed to the progress prop shouldn’t exceed 100. Setting {progress} to 100.',
negativeWarningMessage:
'Values passed to the progress prop shouldn’t be negative. Resetting {progress} to 0.',
},
ResourceList: {
BulkActions: {
actionsActivatorLabel: 'Actions',
moreActionsActivatorLabel: 'More actions',
warningMessage:
'To provide a better user experience. There should only be a maximum of {maxPromotedActions} promoted actions.',
},
DateSelector: {
FilterLabelForValue: {
coming_month: 'next month',
coming_quarter: 'in the next 3 months',
coming_week: 'next week',
coming_year: 'in the next year',
on_or_after: 'after {date}',
on_or_before: 'before {date}',
past_month: 'in the last month',
past_quarter: 'in the last 3 months',
past_week: 'in the last week',
past_year: 'in the last year',
},
SelectOptions: {
ComingMonth: 'next month',
ComingQuarter: 'in the next 3 months',
ComingWeek: 'next week',
ComingYear: 'in the next year',
OnOrAfter: 'on or after',
OnOrBefore: 'on or before',
PastMonth: 'in the last month',
PastQuarter: 'in the last 3 months',
PastWeek: 'in the last week',
PastYear: 'in the last year',
},
dateFilterLabel: 'Select a value',
dateValueError: 'Match YYYY-MM-DD format',
dateValueLabel: 'Date',
dateValuePlaceholder: 'YYYY-MM-DD',
},
FilterControl: {
textFieldLabel: 'Search {resourceNamePlural}',
},
FilterCreator: {
addFilterButtonLabel: 'Add filter',
filterButtonLabel: 'Filter',
selectFilterKeyPlaceholder: 'Select a filter…',
showAllWhere: 'Show all {resourceNamePlural} where:',
},
FilterValueSelector: {
selectFilterValuePlaceholder: 'Select a filter…',
},
Item: {
actionsDropdown: 'Actions dropdown',
actionsDropdownLabel:
'Actions for {accessibilityLabel}',
viewItem: 'View details for {itemName}',
},
a11yCheckboxDeselectAllMultiple:
'Deselect all {itemsLength} {resourceNamePlural}',
a11yCheckboxDeselectAllSingle:
'Deselect {resourceNameSingular}',
a11yCheckboxSelectAllMultiple:
'Select all {itemsLength} {resourceNamePlural}',
a11yCheckboxSelectAllSingle:
'Select {resourceNameSingular}',
allItemsSelected:
'All {itemsLength}+ {resourceNamePlural} in your store are selected.',
ariaLivePlural: '{itemsLength} items',
ariaLiveSingular: '{itemsLength} item',
defaultItemPlural: 'items',
defaultItemSingular: 'item',
emptySearchResultDescription:
'Try changing the filters or search term',
emptySearchResultTitle: 'No {resourceNamePlural} found',
loading: 'Loading {resource}',
selectAllItems:
'Select all {itemsLength}+ {resourceNamePlural} in your store',
selectButtonText: 'Select',
selected: '{selectedItemsCount} selected',
showing: 'Showing {itemsCount} {resource}',
sortingLabel: 'Sort by',
},
SkeletonPage: {loadingLabel: 'Page loading'},
Spinner: {
warningMessage:
'The color {color} is not meant to be used on {size} spinners. The colors available on large spinners are: {colors}',
},
Tabs: {toggleTabsLabel: 'More tabs'},
Tag: {ariaLabel: 'Remove {children}'},
TextField: {
characterCount: '{count} characters',
characterCountWithMaxLength:
'{count} of {limit} characters used',
},
TopBar: {
SearchField: {
clearButtonLabel: 'Clear',
search: 'Search',
},
toggleMenuLabel: 'Toggle menu',
},
},
},
},
link: undefined,
scrollLockManager: {locked: false, scrollLocks: 0},
stickyManager: {
handleResize: ['Function debounced'],
handleScroll: ['Function debounced'],
stickyItems: [],
stuckItems: [],
topBarOffset: 0,
},
theme: {logo: null},
}}
>
{/* <1 child... /> */}
</Header>There was a problem hiding this comment.
Ah that makes more sense, thanks! I think i'd go for a 2 args approach with some sensible-ish defaults rather than an object. It feels like seeing more children while not going that deep into the props might be a decent base.debug(childrenMaxDepth = 5, propsMaxDepth = 1).
It's at this point that I mutter something something Polaris being on a warpath to kill our WithAppProvider component (the only HoC remaining in v4), and we're down to <15 usages, but that won't help the reset of web being very HoC happy.
6293ed0 to
3cc3021
Compare
lemonmade
left a comment
There was a problem hiding this comment.
Definitely looks like a great feature 👍
| } | ||
| } | ||
|
|
||
| function printElement(element: Element<any>, options: DebugOptions, level = 0) { |
There was a problem hiding this comment.
Is there no existing serializer for a react element we could use?
There was a problem hiding this comment.
if there is i didn't encounter one
There was a problem hiding this comment.
looks like are a few options:
react-element-to-jsx-stringpretty-formatfromjestReact.renderToStaticMarkup(...)
any of these could work, but really there isn't much code here so i'm not sure how interested i am in pulling in another dependency. the renderToStaticMarkup is probably a bit basic, pretty-format could be reasonable though if it's already present in the package dependences?
There was a problem hiding this comment.
i tried out pretty-format and it was a no go, we would need to change how we are rendering nodes which isn't really in the scope of this feature. The other options i don't feel make sense to attempt.
cartogram
left a comment
There was a problem hiding this comment.
I love that you added this. I had the same idea but you beat me to it! 🙂
6a9ba4c to
b1768b8
Compare
lemonmade
left a comment
There was a problem hiding this comment.
If you feel the print won’t be an ongoing maintenance burden then 👍
da22127 to
60477a5
Compare
60477a5 to
d932ed9
Compare
Description
Fixes (issue #) do we have an issue for this???
Similar to how enzyme handles
.debug(), we can now emit mounted structure (and sub-structure) usingwrapper.debug()orwrapper.find(Foo)!.debug().increase (or decrease) verbosity of the props object expansion by changing the
verbosityoption (default is1,wrapper.debug(3)). limit how deep to walk the graph by changing thedepthoption (default is undefined, walk the entire graph). Use either position parameters (wrapper.debug(verbosity, depth)) or a single options object (wrapper.debug({depth, verbosity}))Test it out in your project using the debug console,
wrapper.findWhere((type) => type && type.name === 'Breadcrumbs')!.debug(5)I'm open to changing the name from
.debug(), but maybe we want to keep it the same (or at least an alias to it) for posterity?Type of change
Checklist