termcast 1.3.30 → 1.3.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apis/cache.d.ts.map +1 -1
- package/dist/apis/cache.js +4 -39
- package/dist/apis/cache.js.map +1 -1
- package/dist/apis/hud.d.ts.map +1 -1
- package/dist/apis/hud.js +13 -31
- package/dist/apis/hud.js.map +1 -1
- package/dist/apis/localstorage.d.ts.map +1 -1
- package/dist/apis/localstorage.js +3 -27
- package/dist/apis/localstorage.js.map +1 -1
- package/dist/apis/toast.d.ts +16 -43
- package/dist/apis/toast.d.ts.map +1 -1
- package/dist/apis/toast.js +78 -177
- package/dist/apis/toast.js.map +1 -1
- package/dist/build.d.ts +3 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +52 -2
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +206 -25
- package/dist/cli.js.map +1 -1
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +1 -0
- package/dist/colors.js.map +1 -1
- package/dist/compile.d.ts +0 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +18 -23
- package/dist/compile.js.map +1 -1
- package/dist/components/actions.d.ts.map +1 -1
- package/dist/components/actions.js +30 -15
- package/dist/components/actions.js.map +1 -1
- package/dist/components/animation-tick.d.ts +12 -0
- package/dist/components/animation-tick.d.ts.map +1 -0
- package/dist/components/animation-tick.js +63 -0
- package/dist/components/animation-tick.js.map +1 -0
- package/dist/components/detail.d.ts.map +1 -1
- package/dist/components/detail.js +10 -13
- package/dist/components/detail.js.map +1 -1
- package/dist/components/dropdown.d.ts +1 -0
- package/dist/components/dropdown.d.ts.map +1 -1
- package/dist/components/dropdown.js +27 -26
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/extension-preferences.d.ts.map +1 -1
- package/dist/components/extension-preferences.js +15 -10
- package/dist/components/extension-preferences.js.map +1 -1
- package/dist/components/footer.d.ts +13 -0
- package/dist/components/footer.d.ts.map +1 -0
- package/dist/components/footer.js +106 -0
- package/dist/components/footer.js.map +1 -0
- package/dist/components/form/file-autocomplete.d.ts +19 -4
- package/dist/components/form/file-autocomplete.d.ts.map +1 -1
- package/dist/components/form/file-autocomplete.js +56 -55
- package/dist/components/form/file-autocomplete.js.map +1 -1
- package/dist/components/form/file-picker.d.ts.map +1 -1
- package/dist/components/form/file-picker.js +26 -15
- package/dist/components/form/file-picker.js.map +1 -1
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +17 -15
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/form/with-left-border.d.ts.map +1 -1
- package/dist/components/form/with-left-border.js +4 -12
- package/dist/components/form/with-left-border.js.map +1 -1
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +126 -86
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-bar.d.ts.map +1 -1
- package/dist/components/loading-bar.js +5 -22
- package/dist/components/loading-bar.js.map +1 -1
- package/dist/components/loading-text.d.ts.map +1 -1
- package/dist/components/loading-text.js +3 -22
- package/dist/components/loading-text.js.map +1 -1
- package/dist/components/theme-picker.d.ts +2 -0
- package/dist/components/theme-picker.d.ts.map +1 -0
- package/dist/components/theme-picker.js +37 -0
- package/dist/components/theme-picker.js.map +1 -0
- package/dist/descendants.d.ts +6 -0
- package/dist/descendants.d.ts.map +1 -1
- package/dist/descendants.js +74 -8
- package/dist/descendants.js.map +1 -1
- package/dist/examples/internal/descendants-rerender.d.ts +14 -0
- package/dist/examples/internal/descendants-rerender.d.ts.map +1 -0
- package/dist/examples/internal/descendants-rerender.js +145 -0
- package/dist/examples/internal/descendants-rerender.js.map +1 -0
- package/dist/examples/internal/simple-dialog.js +4 -1
- package/dist/examples/internal/simple-dialog.js.map +1 -1
- package/dist/examples/internal/simple-scrollbox.js +1 -1
- package/dist/examples/internal/simple-scrollbox.js.map +1 -1
- package/dist/examples/list-with-dropdown.js +1 -1
- package/dist/examples/list-with-dropdown.js.map +1 -1
- package/dist/examples/miscellaneous.js +1 -1
- package/dist/examples/miscellaneous.js.map +1 -1
- package/dist/examples/toast-action.d.ts +2 -0
- package/dist/examples/toast-action.d.ts.map +1 -0
- package/dist/examples/toast-action.js +76 -0
- package/dist/examples/toast-action.js.map +1 -0
- package/dist/examples/toast-variations.js +38 -36
- package/dist/examples/toast-variations.js.map +1 -1
- package/dist/extensions/dev.d.ts +1 -1
- package/dist/extensions/dev.d.ts.map +1 -1
- package/dist/extensions/dev.js +62 -30
- package/dist/extensions/dev.js.map +1 -1
- package/dist/extensions/home.d.ts.map +1 -1
- package/dist/extensions/home.js +4 -3
- package/dist/extensions/home.js.map +1 -1
- package/dist/extensions/react-refresh-init.d.ts +5 -0
- package/dist/extensions/react-refresh-init.d.ts.map +1 -0
- package/dist/extensions/react-refresh-init.js +52 -0
- package/dist/extensions/react-refresh-init.js.map +1 -0
- package/dist/internal/date-picker-widget.js +1 -1
- package/dist/internal/date-picker-widget.js.map +1 -1
- package/dist/internal/dialog.d.ts +8 -3
- package/dist/internal/dialog.d.ts.map +1 -1
- package/dist/internal/dialog.js +37 -53
- package/dist/internal/dialog.js.map +1 -1
- package/dist/internal/navigation.d.ts +1 -0
- package/dist/internal/navigation.d.ts.map +1 -1
- package/dist/internal/navigation.js +25 -1
- package/dist/internal/navigation.js.map +1 -1
- package/dist/internal/providers.d.ts.map +1 -1
- package/dist/internal/providers.js +9 -197
- package/dist/internal/providers.js.map +1 -1
- package/dist/internal/scrollbox.d.ts.map +1 -1
- package/dist/internal/scrollbox.js +1 -0
- package/dist/internal/scrollbox.js.map +1 -1
- package/dist/release.d.ts +1 -0
- package/dist/release.d.ts.map +1 -1
- package/dist/release.js +16 -9
- package/dist/release.js.map +1 -1
- package/dist/state.d.ts +27 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +6 -0
- package/dist/state.js.map +1 -1
- package/dist/theme.d.ts +6 -19
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +76 -45
- package/dist/theme.js.map +1 -1
- package/dist/themes/aura.json +69 -0
- package/dist/themes/ayu.json +80 -0
- package/dist/themes/catppuccin-frappe.json +233 -0
- package/dist/themes/catppuccin-macchiato.json +233 -0
- package/dist/themes/catppuccin.json +112 -0
- package/dist/themes/cobalt2.json +228 -0
- package/dist/themes/cursor.json +249 -0
- package/dist/themes/dracula.json +219 -0
- package/dist/themes/everforest.json +241 -0
- package/dist/themes/flexoki.json +237 -0
- package/dist/themes/github-light.json +56 -0
- package/dist/themes/github.json +241 -0
- package/dist/themes/gruvbox.json +95 -0
- package/dist/themes/kanagawa.json +77 -0
- package/dist/themes/lucent-orng.json +227 -0
- package/dist/themes/material.json +235 -0
- package/dist/themes/matrix.json +77 -0
- package/dist/themes/mercury.json +245 -0
- package/dist/themes/monokai.json +221 -0
- package/dist/themes/nightowl.json +221 -0
- package/dist/themes/nord.json +223 -0
- package/dist/themes/one-dark.json +84 -0
- package/dist/themes/opencode-light.json +62 -0
- package/dist/themes/opencode.json +245 -0
- package/dist/themes/orng.json +245 -0
- package/dist/themes/palenight.json +222 -0
- package/dist/themes/rosepine.json +234 -0
- package/dist/themes/solarized.json +223 -0
- package/dist/themes/synthwave84.json +226 -0
- package/dist/themes/termcast.json +226 -0
- package/dist/themes/tokyonight.json +243 -0
- package/dist/themes/vercel.json +255 -0
- package/dist/themes/vesper.json +218 -0
- package/dist/themes/zenburn.json +223 -0
- package/dist/themes.d.ts +57 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +181 -0
- package/dist/themes.js.map +1 -0
- package/dist/utils/run-command.d.ts +2 -1
- package/dist/utils/run-command.d.ts.map +1 -1
- package/dist/utils/run-command.js +20 -10
- package/dist/utils/run-command.js.map +1 -1
- package/dist/utils.d.ts +2 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +90 -17
- package/dist/utils.js.map +1 -1
- package/dist/watcher.d.ts +3 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +16 -0
- package/dist/watcher.js.map +1 -0
- package/package.json +16 -10
- package/src/apis/cache.tsx +5 -44
- package/src/apis/hud.tsx +17 -62
- package/src/apis/localstorage.tsx +3 -32
- package/src/apis/toast.tsx +91 -275
- package/src/build.test.tsx +10 -0
- package/src/build.tsx +61 -1
- package/src/cli.tsx +365 -103
- package/src/colors.tsx +1 -0
- package/src/compile.tsx +21 -29
- package/src/compile.vitest.tsx +300 -0
- package/src/components/actions.tsx +64 -45
- package/src/components/animation-tick.tsx +85 -0
- package/src/components/detail.tsx +31 -35
- package/src/components/dropdown.tsx +32 -21
- package/src/components/extension-preferences.tsx +14 -10
- package/src/components/footer.tsx +241 -0
- package/src/components/form/file-autocomplete.tsx +80 -60
- package/src/components/form/file-picker.tsx +37 -25
- package/src/components/form/index.tsx +45 -41
- package/src/components/form/with-left-border.tsx +4 -14
- package/src/components/list.tsx +181 -121
- package/src/components/loading-bar.tsx +5 -25
- package/src/components/loading-text.tsx +4 -23
- package/src/components/theme-picker.tsx +57 -0
- package/src/descendants.tsx +98 -9
- package/src/examples/actions-dialog-layout.vitest.tsx +112 -0
- package/src/examples/file-autocomplete.vitest.tsx +131 -122
- package/src/examples/form-basic.vitest.tsx +463 -644
- package/src/examples/form-dropdown.vitest.tsx +553 -571
- package/src/examples/form-scroll.vitest.tsx +112 -102
- package/src/examples/form-tagpicker.vitest.tsx +364 -338
- package/src/examples/internal/descendants-rerender.tsx +273 -0
- package/src/examples/internal/descendants-rerender.vitest.tsx +194 -0
- package/src/examples/internal/simple-dialog.tsx +4 -4
- package/src/examples/internal/simple-scrollbox.tsx +2 -2
- package/src/examples/internal/simple-scrollbox.vitest.tsx +43 -31
- package/src/examples/list-detail-metadata.vitest.tsx +34 -30
- package/src/examples/list-dropdown-default.vitest.tsx +84 -72
- package/src/examples/list-empty-view.vitest.tsx +93 -0
- package/src/examples/list-fetch-data.vitest.tsx +36 -30
- package/src/examples/list-scrollbox.vitest.tsx +59 -39
- package/src/examples/list-with-detail.vitest.tsx +339 -314
- package/src/examples/list-with-dropdown.tsx +1 -0
- package/src/examples/list-with-dropdown.vitest.tsx +176 -150
- package/src/examples/list-with-sections.vitest.tsx +289 -270
- package/src/examples/list-with-toast.vitest.tsx +44 -44
- package/src/examples/miscellaneous.tsx +10 -0
- package/src/examples/simple-file-picker.vitest.tsx +90 -86
- package/src/examples/simple-grid.vitest.tsx +275 -249
- package/src/examples/simple-navigation.vitest.tsx +192 -168
- package/src/examples/store.vitest.tsx +6 -4
- package/src/examples/swift-extension.vitest.tsx +31 -19
- package/src/examples/synonyms.vitest.tsx +93 -83
- package/src/examples/toast-action.tsx +160 -0
- package/src/examples/toast-action.vitest.tsx +404 -0
- package/src/examples/toast-variations.tsx +58 -57
- package/src/examples/toast-variations.vitest.tsx +186 -166
- package/src/extensions/dev.tsx +74 -33
- package/src/extensions/dev.vitest.tsx +162 -69
- package/src/extensions/home.tsx +5 -6
- package/src/extensions/react-refresh-init.tsx +59 -0
- package/src/internal/date-picker-widget.tsx +1 -1
- package/src/internal/dialog.tsx +59 -83
- package/src/internal/navigation.tsx +37 -4
- package/src/internal/providers.tsx +27 -315
- package/src/internal/scrollbox.tsx +1 -0
- package/src/release.tsx +16 -10
- package/src/state.tsx +36 -3
- package/src/theme.tsx +82 -51
- package/src/themes/aura.json +69 -0
- package/src/themes/ayu.json +80 -0
- package/src/themes/catppuccin-frappe.json +233 -0
- package/src/themes/catppuccin-macchiato.json +233 -0
- package/src/themes/catppuccin.json +112 -0
- package/src/themes/cobalt2.json +228 -0
- package/src/themes/cursor.json +249 -0
- package/src/themes/dracula.json +219 -0
- package/src/themes/everforest.json +241 -0
- package/src/themes/flexoki.json +237 -0
- package/src/themes/github-light.json +56 -0
- package/src/themes/github.json +241 -0
- package/src/themes/gruvbox.json +95 -0
- package/src/themes/kanagawa.json +77 -0
- package/src/themes/lucent-orng.json +227 -0
- package/src/themes/material.json +235 -0
- package/src/themes/matrix.json +77 -0
- package/src/themes/mercury.json +252 -0
- package/src/themes/monokai.json +221 -0
- package/src/themes/nightowl.json +221 -0
- package/src/themes/nord.json +223 -0
- package/src/themes/one-dark.json +84 -0
- package/src/themes/opencode-light.json +62 -0
- package/src/themes/opencode.json +245 -0
- package/src/themes/orng.json +245 -0
- package/src/themes/palenight.json +222 -0
- package/src/themes/rosepine.json +234 -0
- package/src/themes/solarized.json +223 -0
- package/src/themes/synthwave84.json +226 -0
- package/src/themes/termcast.json +227 -0
- package/src/themes/tokyonight.json +243 -0
- package/src/themes/vercel.json +255 -0
- package/src/themes/vesper.json +218 -0
- package/src/themes/zenburn.json +223 -0
- package/src/themes.ts +291 -0
- package/src/utils/run-command.tsx +23 -12
- package/src/utils.tsx +115 -18
- package/src/watcher.tsx +19 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// Test useDescendantsRerender() behavior in different component positions
|
|
2
|
+
// This example tracks render counts to verify:
|
|
3
|
+
// 1. Hook in parent of descendants
|
|
4
|
+
// 2. Hook in independent child component
|
|
5
|
+
// 3. Hook in a descendant item component
|
|
6
|
+
//
|
|
7
|
+
import { createDescendants } from 'termcast/src/descendants'
|
|
8
|
+
import { createContext, useContext, useState, useRef } from 'react'
|
|
9
|
+
import { renderWithProviders } from '../../utils'
|
|
10
|
+
import { useKeyboard } from '@opentui/react'
|
|
11
|
+
import { useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
12
|
+
|
|
13
|
+
// Create descendants with the new useDescendantsRerender hook
|
|
14
|
+
const {
|
|
15
|
+
DescendantsProvider,
|
|
16
|
+
useDescendants,
|
|
17
|
+
useDescendant,
|
|
18
|
+
useDescendantsRerender,
|
|
19
|
+
} = createDescendants<{
|
|
20
|
+
title: string
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
// Global render counters for testing
|
|
24
|
+
const renderCounts = {
|
|
25
|
+
parent: 0,
|
|
26
|
+
parentWithHook: 0,
|
|
27
|
+
independentChild: 0,
|
|
28
|
+
independentChildWithHook: 0,
|
|
29
|
+
descendantItem: 0,
|
|
30
|
+
descendantItemWithHook: 0,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Reset counters - call between test scenarios
|
|
34
|
+
export function resetRenderCounts() {
|
|
35
|
+
renderCounts.parent = 0
|
|
36
|
+
renderCounts.parentWithHook = 0
|
|
37
|
+
renderCounts.independentChild = 0
|
|
38
|
+
renderCounts.independentChildWithHook = 0
|
|
39
|
+
renderCounts.descendantItem = 0
|
|
40
|
+
renderCounts.descendantItemWithHook = 0
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Get current render counts
|
|
44
|
+
export function getRenderCounts() {
|
|
45
|
+
return { ...renderCounts }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Context to share item count for display
|
|
49
|
+
const ItemCountContext = createContext<number>(0)
|
|
50
|
+
|
|
51
|
+
// ============================================
|
|
52
|
+
// SCENARIO 1: Hook in parent of descendants
|
|
53
|
+
// The hook must be called INSIDE the provider, so we need a wrapper
|
|
54
|
+
// ============================================
|
|
55
|
+
function ParentWithHookInner({ children }: { children: React.ReactNode }) {
|
|
56
|
+
renderCounts.parentWithHook++
|
|
57
|
+
|
|
58
|
+
// Using the hook inside provider - will this cause infinite loop?
|
|
59
|
+
const descendants = useDescendantsRerender()
|
|
60
|
+
const itemCount = Object.keys(descendants).length
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<ItemCountContext.Provider value={itemCount}>
|
|
64
|
+
<box flexDirection="column">
|
|
65
|
+
<text>Parent with hook - renders: {renderCounts.parentWithHook}</text>
|
|
66
|
+
<text>Item count (from hook): {itemCount}</text>
|
|
67
|
+
{children}
|
|
68
|
+
</box>
|
|
69
|
+
</ItemCountContext.Provider>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function ParentWithHook({ children }: { children: React.ReactNode }) {
|
|
74
|
+
const descendantsContext = useDescendants()
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<DescendantsProvider value={descendantsContext}>
|
|
78
|
+
<ParentWithHookInner>{children}</ParentWithHookInner>
|
|
79
|
+
</DescendantsProvider>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ============================================
|
|
84
|
+
// SCENARIO 2: Parent without hook (baseline)
|
|
85
|
+
// ============================================
|
|
86
|
+
function ParentWithoutHook({ children }: { children: React.ReactNode }) {
|
|
87
|
+
renderCounts.parent++
|
|
88
|
+
const descendantsContext = useDescendants()
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<DescendantsProvider value={descendantsContext}>
|
|
92
|
+
<box flexDirection="column">
|
|
93
|
+
<text>Parent without hook - renders: {renderCounts.parent}</text>
|
|
94
|
+
{children}
|
|
95
|
+
</box>
|
|
96
|
+
</DescendantsProvider>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ============================================
|
|
101
|
+
// SCENARIO 3: Independent child with hook
|
|
102
|
+
// ============================================
|
|
103
|
+
function IndependentChildWithHook() {
|
|
104
|
+
renderCounts.independentChildWithHook++
|
|
105
|
+
const descendants = useDescendantsRerender()
|
|
106
|
+
const itemCount = Object.keys(descendants).length
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<box flexShrink={0}>
|
|
110
|
+
<text>
|
|
111
|
+
Independent child with hook - renders:{' '}
|
|
112
|
+
{renderCounts.independentChildWithHook}, items: {itemCount}
|
|
113
|
+
</text>
|
|
114
|
+
</box>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ============================================
|
|
119
|
+
// SCENARIO 4: Independent child without hook
|
|
120
|
+
// ============================================
|
|
121
|
+
function IndependentChildWithoutHook() {
|
|
122
|
+
renderCounts.independentChild++
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<box flexShrink={0}>
|
|
126
|
+
<text>
|
|
127
|
+
Independent child without hook - renders:{' '}
|
|
128
|
+
{renderCounts.independentChild}
|
|
129
|
+
</text>
|
|
130
|
+
</box>
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ============================================
|
|
135
|
+
// SCENARIO 5: Descendant item with hook
|
|
136
|
+
// ============================================
|
|
137
|
+
function DescendantItemWithHook({ title }: { title: string }) {
|
|
138
|
+
renderCounts.descendantItemWithHook++
|
|
139
|
+
const { index } = useDescendant({ title })
|
|
140
|
+
const descendants = useDescendantsRerender()
|
|
141
|
+
const totalItems = Object.keys(descendants).length
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<text>
|
|
145
|
+
[{index}] {title} (with hook, renders: {renderCounts.descendantItemWithHook}, total: {totalItems})
|
|
146
|
+
</text>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ============================================
|
|
151
|
+
// SCENARIO 6: Descendant item without hook
|
|
152
|
+
// ============================================
|
|
153
|
+
function DescendantItemWithoutHook({ title }: { title: string }) {
|
|
154
|
+
renderCounts.descendantItem++
|
|
155
|
+
const { index } = useDescendant({ title })
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<text>
|
|
159
|
+
[{index}] {title} (without hook, renders: {renderCounts.descendantItem})
|
|
160
|
+
</text>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ============================================
|
|
165
|
+
// Main example component
|
|
166
|
+
// ============================================
|
|
167
|
+
type Scenario =
|
|
168
|
+
| 'parent-with-hook'
|
|
169
|
+
| 'parent-without-hook'
|
|
170
|
+
| 'independent-child-with-hook'
|
|
171
|
+
| 'descendant-item-with-hook'
|
|
172
|
+
|
|
173
|
+
function Example({ scenario }: { scenario: Scenario }) {
|
|
174
|
+
const [items, setItems] = useState(['Apple', 'Banana', 'Cherry'])
|
|
175
|
+
const inFocus = useIsInFocus()
|
|
176
|
+
|
|
177
|
+
useKeyboard((evt) => {
|
|
178
|
+
if (!inFocus) return
|
|
179
|
+
|
|
180
|
+
if (evt.name === 'a') {
|
|
181
|
+
// Add item with stable key
|
|
182
|
+
setItems((prev) => [...prev, `Item ${prev.length + 1}`])
|
|
183
|
+
} else if (evt.name === 'd' && items.length > 0) {
|
|
184
|
+
// Delete last item
|
|
185
|
+
setItems((prev) => prev.slice(0, -1))
|
|
186
|
+
} else if (evt.name === 'r') {
|
|
187
|
+
// Reset counts
|
|
188
|
+
resetRenderCounts()
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
const renderScenario = () => {
|
|
193
|
+
switch (scenario) {
|
|
194
|
+
case 'parent-with-hook':
|
|
195
|
+
return (
|
|
196
|
+
<ParentWithHook>
|
|
197
|
+
{items.map((item) => (
|
|
198
|
+
<DescendantItemWithoutHook key={item} title={item} />
|
|
199
|
+
))}
|
|
200
|
+
</ParentWithHook>
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
case 'parent-without-hook':
|
|
204
|
+
return (
|
|
205
|
+
<ParentWithoutHook>
|
|
206
|
+
{items.map((item) => (
|
|
207
|
+
<DescendantItemWithoutHook key={item} title={item} />
|
|
208
|
+
))}
|
|
209
|
+
</ParentWithoutHook>
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
case 'independent-child-with-hook':
|
|
213
|
+
return (
|
|
214
|
+
<ParentWithoutHook>
|
|
215
|
+
<IndependentChildWithHook />
|
|
216
|
+
<IndependentChildWithoutHook />
|
|
217
|
+
{items.map((item) => (
|
|
218
|
+
<DescendantItemWithoutHook key={item} title={item} />
|
|
219
|
+
))}
|
|
220
|
+
</ParentWithoutHook>
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
case 'descendant-item-with-hook':
|
|
224
|
+
return (
|
|
225
|
+
<ParentWithoutHook>
|
|
226
|
+
{items.map((item) => (
|
|
227
|
+
<DescendantItemWithHook key={item} title={item} />
|
|
228
|
+
))}
|
|
229
|
+
</ParentWithoutHook>
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<box flexDirection="column">
|
|
236
|
+
<text>Scenario: {scenario}</text>
|
|
237
|
+
<text>Items: {items.length} | [a] add | [d] delete | [r] reset counts</text>
|
|
238
|
+
<box marginTop={1}>{renderScenario()}</box>
|
|
239
|
+
<box marginTop={1} borderStyle="single" padding={1}>
|
|
240
|
+
<box flexDirection="column">
|
|
241
|
+
<text>Render Counts:</text>
|
|
242
|
+
<text> parent: {renderCounts.parent}</text>
|
|
243
|
+
<text> parentWithHook: {renderCounts.parentWithHook}</text>
|
|
244
|
+
<text> independentChild: {renderCounts.independentChild}</text>
|
|
245
|
+
<text> independentChildWithHook: {renderCounts.independentChildWithHook}</text>
|
|
246
|
+
<text> descendantItem: {renderCounts.descendantItem}</text>
|
|
247
|
+
<text> descendantItemWithHook: {renderCounts.descendantItemWithHook}</text>
|
|
248
|
+
</box>
|
|
249
|
+
</box>
|
|
250
|
+
</box>
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Export for different scenarios
|
|
255
|
+
export function ParentWithHookExample() {
|
|
256
|
+
return <Example scenario="parent-with-hook" />
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function ParentWithoutHookExample() {
|
|
260
|
+
return <Example scenario="parent-without-hook" />
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function IndependentChildWithHookExample() {
|
|
264
|
+
return <Example scenario="independent-child-with-hook" />
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function DescendantItemWithHookExample() {
|
|
268
|
+
return <Example scenario="descendant-item-with-hook" />
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Default export runs parent-with-hook scenario
|
|
272
|
+
const scenario = (process.argv[2] as Scenario) || 'parent-with-hook'
|
|
273
|
+
await renderWithProviders(<Example scenario={scenario} />)
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { expect, test } from 'vitest'
|
|
2
|
+
import { launchTerminal } from 'tuistory/src'
|
|
3
|
+
|
|
4
|
+
test(
|
|
5
|
+
'useDescendantsRerender - compare render counts with and without hook',
|
|
6
|
+
async () => {
|
|
7
|
+
// First test WITHOUT hook (baseline)
|
|
8
|
+
const sessionWithout = await launchTerminal({
|
|
9
|
+
command: 'bun',
|
|
10
|
+
args: [
|
|
11
|
+
'src/examples/internal/descendants-rerender.tsx',
|
|
12
|
+
'parent-without-hook',
|
|
13
|
+
],
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const withoutHook = await sessionWithout.text({
|
|
17
|
+
waitFor: (text) =>
|
|
18
|
+
text.includes('Parent without hook') && text.includes('Render Counts'),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
expect(withoutHook).toMatchInlineSnapshot(`
|
|
22
|
+
"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
Scenario: parent-without-hook
|
|
26
|
+
Items: 3 | [a] add | [d] delete | [r] reset counts
|
|
27
|
+
|
|
28
|
+
Parent without hook - renders: 2
|
|
29
|
+
[0] Apple (without hook, renders: 7)
|
|
30
|
+
[1] Banana (without hook, renders: 8)
|
|
31
|
+
[2] Cherry (without hook, renders: 9)
|
|
32
|
+
|
|
33
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
34
|
+
│ │
|
|
35
|
+
│ Render Counts: │
|
|
36
|
+
│ parent: 1 │
|
|
37
|
+
│ parentWithHook: 0 │
|
|
38
|
+
│ independentChild: 0 │
|
|
39
|
+
│ independentChildWithHook: 0 │
|
|
40
|
+
│ descendantItem: 3 │
|
|
41
|
+
│ descendantItemWithHook: 0 │
|
|
42
|
+
│ │
|
|
43
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
"
|
|
47
|
+
`)
|
|
48
|
+
|
|
49
|
+
sessionWithout.close()
|
|
50
|
+
|
|
51
|
+
// Then test WITH hook
|
|
52
|
+
const sessionWith = await launchTerminal({
|
|
53
|
+
command: 'bun',
|
|
54
|
+
args: [
|
|
55
|
+
'src/examples/internal/descendants-rerender.tsx',
|
|
56
|
+
'parent-with-hook',
|
|
57
|
+
],
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const withHook = await sessionWith.text({
|
|
61
|
+
waitFor: (text) =>
|
|
62
|
+
text.includes('Parent with hook') && text.includes('Render Counts'),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
expect(withHook).toMatchInlineSnapshot(`
|
|
66
|
+
"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
Scenario: parent-with-hook
|
|
70
|
+
Items: 3 | [a] add | [d] delete | [r] reset counts
|
|
71
|
+
|
|
72
|
+
Parent with hook - renders: 3
|
|
73
|
+
Item count (from hook): 3
|
|
74
|
+
[0] Apple (without hook, renders: 7)
|
|
75
|
+
[1] Banana (without hook, renders: 8)
|
|
76
|
+
[2] Cherry (without hook, renders: 9)
|
|
77
|
+
|
|
78
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
79
|
+
│ │
|
|
80
|
+
│ Render Counts: │
|
|
81
|
+
│ parent: 0 │
|
|
82
|
+
│ parentWithHook: 1 │
|
|
83
|
+
│ independentChild: 0 │
|
|
84
|
+
│ independentChildWithHook: 0 │
|
|
85
|
+
│ descendantItem: 3 │
|
|
86
|
+
│ descendantItemWithHook: 0 │
|
|
87
|
+
│ │
|
|
88
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
89
|
+
|
|
90
|
+
"
|
|
91
|
+
`)
|
|
92
|
+
|
|
93
|
+
sessionWith.close()
|
|
94
|
+
},
|
|
95
|
+
60000,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
test(
|
|
99
|
+
'useDescendantsRerender - independent child with hook',
|
|
100
|
+
async () => {
|
|
101
|
+
const session = await launchTerminal({
|
|
102
|
+
command: 'bun',
|
|
103
|
+
args: [
|
|
104
|
+
'src/examples/internal/descendants-rerender.tsx',
|
|
105
|
+
'independent-child-with-hook',
|
|
106
|
+
],
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const text = await session.text({
|
|
110
|
+
// waitFor: (text) =>
|
|
111
|
+
// text.includes('Independent child with hook')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
expect(text).toMatchInlineSnapshot(`
|
|
115
|
+
"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
Items:i3:|i[a]padde|t[d]ideleteh|h[r] reset counts
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
Parent without hook - renders: 2
|
|
122
|
+
Independent child with hook - renders: 3, items: 3
|
|
123
|
+
[0]eApplen(withoutwhook,trenders:r7)ders: 2
|
|
124
|
+
[1] Banana (without hook, renders: 8)
|
|
125
|
+
[2] Cherry (without hook, renders: 9)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
129
|
+
│ │
|
|
130
|
+
│ Reparent:u1ts: │
|
|
131
|
+
│ parentWithHook: 0 │
|
|
132
|
+
│ independentChild: 1 │
|
|
133
|
+
│ independentChildWithHook: 1 │
|
|
134
|
+
│ descendantItem: 3 │
|
|
135
|
+
│ descendantItemWithHook: 0 │
|
|
136
|
+
│ │
|
|
137
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
138
|
+
|
|
139
|
+
"
|
|
140
|
+
`)
|
|
141
|
+
|
|
142
|
+
session.close()
|
|
143
|
+
},
|
|
144
|
+
30000,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
test(
|
|
148
|
+
'useDescendantsRerender - descendant item with hook',
|
|
149
|
+
async () => {
|
|
150
|
+
const session = await launchTerminal({
|
|
151
|
+
command: 'bun',
|
|
152
|
+
args: [
|
|
153
|
+
'src/examples/internal/descendants-rerender.tsx',
|
|
154
|
+
'descendant-item-with-hook',
|
|
155
|
+
],
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const text = await session.text({
|
|
159
|
+
waitFor: (text) =>
|
|
160
|
+
text.includes('with hook') && text.includes('Render Counts'),
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
expect(text).toMatchInlineSnapshot(`
|
|
164
|
+
"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
Scenario: descendant-item-with-hook
|
|
168
|
+
Items: 3 | [a] add | [d] delete | [r] reset counts
|
|
169
|
+
|
|
170
|
+
Parent without hook - renders: 2
|
|
171
|
+
[0] Apple (with hook, renders: 7, total: 3)
|
|
172
|
+
[1] Banana (with hook, renders: 8, total: 3)
|
|
173
|
+
[2] Cherry (with hook, renders: 9, total: 3)
|
|
174
|
+
|
|
175
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
176
|
+
│ │
|
|
177
|
+
│ Render Counts: │
|
|
178
|
+
│ parent: 1 │
|
|
179
|
+
│ parentWithHook: 0 │
|
|
180
|
+
│ independentChild: 0 │
|
|
181
|
+
│ independentChildWithHook: 0 │
|
|
182
|
+
│ descendantItem: 0 │
|
|
183
|
+
│ descendantItemWithHook: 3 │
|
|
184
|
+
│ │
|
|
185
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
"
|
|
189
|
+
`)
|
|
190
|
+
|
|
191
|
+
session.close()
|
|
192
|
+
},
|
|
193
|
+
30000,
|
|
194
|
+
)
|
|
@@ -101,10 +101,10 @@ function App(): any {
|
|
|
101
101
|
<Action
|
|
102
102
|
title={`Open ${item.title} Dialog`}
|
|
103
103
|
onAction={() => {
|
|
104
|
-
dialog.push(
|
|
105
|
-
<DialogContent position={item.position} />,
|
|
106
|
-
item.position,
|
|
107
|
-
)
|
|
104
|
+
dialog.push({
|
|
105
|
+
element: <DialogContent position={item.position} />,
|
|
106
|
+
position: item.position,
|
|
107
|
+
})
|
|
108
108
|
}}
|
|
109
109
|
/>
|
|
110
110
|
<Action
|
|
@@ -30,7 +30,7 @@ function SimpleScrollBoxDemo(): any {
|
|
|
30
30
|
</text>
|
|
31
31
|
</box>
|
|
32
32
|
|
|
33
|
-
<ScrollBox focused
|
|
33
|
+
<ScrollBox focused flexShrink={1}>
|
|
34
34
|
{Array.from({ length: 30 }, (_, i) => (
|
|
35
35
|
<box
|
|
36
36
|
key={i}
|
|
@@ -55,4 +55,4 @@ function SimpleScrollBoxDemo(): any {
|
|
|
55
55
|
|
|
56
56
|
if (import.meta.main) {
|
|
57
57
|
await renderWithProviders(<SimpleScrollBoxDemo />)
|
|
58
|
-
}
|
|
58
|
+
}
|
|
@@ -17,87 +17,99 @@ test('simple scrollbox navigation and scrolling', async () => {
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
Simple ScrollBox Demo
|
|
21
|
+
▀
|
|
22
|
+
Item 1 - This is content for item number 1. Lorem ipsum dolor sit amet,
|
|
23
|
+
consectetur adipiscing elit.
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
Item 2 - This is content for item number 2. Lorem ipsum dolor sit amet,
|
|
28
|
+
consectetur adipiscing elit.
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
Item 3 - This is content for item number 3. Lorem ipsum dolor sit amet,
|
|
33
|
+
consectetur adipiscing elit.
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Use mouse scroll or arrow keys | Press [q] to quit
|
|
39
|
+
|
|
40
|
+
"
|
|
39
41
|
`)
|
|
40
42
|
|
|
41
43
|
// Scroll down to see more items
|
|
42
44
|
await session.scrollDown(3)
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
// Wait for Item 4 to appear (proves scroll happened)
|
|
47
|
+
const afterScrollDownSnapshot = await session.text({
|
|
48
|
+
waitFor: (text) => text.includes('Item 4'),
|
|
49
|
+
timeout: 5000,
|
|
50
|
+
})
|
|
46
51
|
expect(afterScrollDownSnapshot).toMatchInlineSnapshot(`
|
|
47
52
|
"
|
|
48
53
|
|
|
49
54
|
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
Simple ScrollBox Demo
|
|
57
|
+
Item 1 - This is content for item number 1. Lorem ipsum dolor sit amet, ▀
|
|
58
|
+
consectetur adipiscing elit.
|
|
59
|
+
|
|
54
60
|
|
|
55
61
|
|
|
62
|
+
Item 2 - This is content for item number 2. Lorem ipsum dolor sit amet,
|
|
63
|
+
consectetur adipiscing elit.
|
|
56
64
|
|
|
57
|
-
Item 2 - This is content for item number 2. Lorem ipsum dolor sit amet,
|
|
58
|
-
consectetur adipiscing elit.
|
|
59
65
|
|
|
60
66
|
|
|
67
|
+
Item 3 - This is content for item number 3. Lorem ipsum dolor sit amet,
|
|
68
|
+
consectetur adipiscing elit.
|
|
61
69
|
|
|
62
|
-
Item 3 - This is content for item number 3. Lorem ipsum dolor sit amet,
|
|
63
|
-
consectetur adipiscing elit.
|
|
64
70
|
|
|
65
71
|
|
|
72
|
+
Item 4 - This is content for item number 4. Lorem ipsum dolor sit amet,
|
|
66
73
|
|
|
67
|
-
|
|
74
|
+
Use mouse scroll or arrow keys | Press [q] to quit
|
|
68
75
|
|
|
69
|
-
|
|
76
|
+
"
|
|
70
77
|
`)
|
|
71
78
|
|
|
72
79
|
// Scroll back up
|
|
73
80
|
await session.scrollUp(2)
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
// Wait for scrollbar to move back up (proves scroll happened)
|
|
83
|
+
const afterScrollUpSnapshot = await session.text({
|
|
84
|
+
waitFor: (text) => text !== afterScrollDownSnapshot,
|
|
85
|
+
timeout: 5000,
|
|
86
|
+
})
|
|
77
87
|
expect(afterScrollUpSnapshot).toMatchInlineSnapshot(`
|
|
78
88
|
"
|
|
79
89
|
|
|
80
90
|
|
|
81
91
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
Simple ScrollBox Demo
|
|
93
|
+
▀
|
|
94
|
+
Item 1 - This is content for item number 1. Lorem ipsum dolor sit amet,
|
|
95
|
+
consectetur adipiscing elit.
|
|
86
96
|
|
|
87
97
|
|
|
88
98
|
|
|
89
|
-
|
|
90
|
-
|
|
99
|
+
Item 2 - This is content for item number 2. Lorem ipsum dolor sit amet,
|
|
100
|
+
consectetur adipiscing elit.
|
|
91
101
|
|
|
92
102
|
|
|
93
103
|
|
|
94
|
-
|
|
95
|
-
|
|
104
|
+
Item 3 - This is content for item number 3. Lorem ipsum dolor sit amet,
|
|
105
|
+
consectetur adipiscing elit.
|
|
96
106
|
|
|
97
107
|
|
|
98
108
|
|
|
99
109
|
|
|
100
|
-
|
|
110
|
+
Use mouse scroll or arrow keys | Press [q] to quit
|
|
111
|
+
|
|
112
|
+
"
|
|
101
113
|
`)
|
|
102
114
|
|
|
103
115
|
await session.press('esc')
|