Skip to content

Commit a543be5

Browse files
zhigang1992claude
andcommitted
fix: improve button text extraction for complex children
- Fixed button text extraction to handle arrays, expressions, and mixed content - Buttons with template literals now display correctly (e.g., "Switch to {mode} Mode") - Added comprehensive tests for button text extraction edge cases - Removed accidentally committed editReactMessage method 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 8c1b225 commit a543be5

3 files changed

Lines changed: 97 additions & 32 deletions

File tree

src/mtcute-adapter.ts

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -280,37 +280,6 @@ export class MtcuteAdapter {
280280
return containerId;
281281
}
282282

283-
// Edit an existing message with a new React tree
284-
async editReactMessage(
285-
chatId: number | string,
286-
messageId: number,
287-
app: ReactElement
288-
) {
289-
const containerId = `${chatId}_${messageId}`;
290-
let container = this.activeContainers.get(containerId);
291-
292-
if (!container) {
293-
container = createContainer();
294-
this.activeContainers.set(containerId, container);
295-
}
296-
297-
// Set up edit callback
298-
container.container.onRenderContainer = async (root) => {
299-
const textWithEntities = this.rootNodeToTextWithEntities(root);
300-
const replyMarkup = this.rootNodeToInlineKeyboard(root, containerId);
301-
302-
await this.client.editMessage({
303-
chatId,
304-
message: messageId,
305-
text: textWithEntities,
306-
replyMarkup
307-
});
308-
};
309-
310-
// Render the app
311-
container.render(app);
312-
}
313-
314283
// Convenience method to handle commands with React
315284
onCommand(command: string, handler: (ctx: any) => ReactElement) {
316285
this.commandHandlers.set(command, handler);

src/reconciler.test.tsx

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,50 @@ describe('Telegram Reconciler', () => {
187187
expect(onClick).toHaveBeenCalledTimes(1);
188188
});
189189

190+
it('should handle buttons with complex children', async () => {
191+
const { container, render } = createContainer();
192+
const onClick = vi.fn();
193+
const mode = 'normal';
194+
195+
render(
196+
<row>
197+
<button onClick={onClick}>
198+
Switch to {mode === 'normal' ? 'Secret' : 'Normal'} Mode
199+
</button>
200+
</row>
201+
);
202+
203+
await new Promise(resolve => setTimeout(resolve, 0));
204+
205+
const row = container.root.children[0];
206+
expect(row?.type).toBe('row');
207+
if (row?.type === 'row') {
208+
expect(row.children[0]?.text).toBe('Switch to Secret Mode');
209+
}
210+
});
211+
212+
it('should handle buttons with array children', async () => {
213+
const { container, render } = createContainer();
214+
215+
render(
216+
<row>
217+
<button>{'Hello'}{' '}{'World'}</button>
218+
<button>{['One', ' ', 'Two', ' ', 'Three']}</button>
219+
<button>{123} items</button>
220+
</row>
221+
);
222+
223+
await new Promise(resolve => setTimeout(resolve, 0));
224+
225+
const row = container.root.children[0];
226+
expect(row?.type).toBe('row');
227+
if (row?.type === 'row') {
228+
expect(row.children[0]?.text).toBe('Hello World');
229+
expect(row.children[1]?.text).toBe('One Two Three');
230+
expect(row.children[2]?.text).toBe('123 items');
231+
}
232+
});
233+
190234
it('should work with React state', async () => {
191235
const { container, render, clickButton } = createContainer();
192236

@@ -239,4 +283,45 @@ describe('Telegram Reconciler', () => {
239283
content: '0'
240284
});
241285
});
286+
287+
it('should handle input elements', async () => {
288+
const { container, render } = createContainer();
289+
const onSubmit = vi.fn();
290+
291+
render(
292+
<>
293+
<input onSubmit={onSubmit} />
294+
<input onSubmit={onSubmit} autoDelete />
295+
</>
296+
);
297+
298+
await new Promise(resolve => setTimeout(resolve, 0));
299+
300+
expect(container.root.children).toHaveLength(2);
301+
expect(container.root.children[0]).toEqual({
302+
type: 'input',
303+
onSubmit: expect.any(Function),
304+
autoDelete: undefined
305+
});
306+
expect(container.root.children[1]).toEqual({
307+
type: 'input',
308+
onSubmit: expect.any(Function),
309+
autoDelete: true
310+
});
311+
312+
// Check input callbacks
313+
expect(container.inputCallbacks).toHaveLength(2);
314+
expect(container.inputCallbacks[0]).toEqual({
315+
callback: expect.any(Function),
316+
autoDelete: undefined
317+
});
318+
expect(container.inputCallbacks[1]).toEqual({
319+
callback: expect.any(Function),
320+
autoDelete: true
321+
});
322+
323+
// Test callback execution
324+
container.inputCallbacks[0]?.callback('test message');
325+
expect(onSubmit).toHaveBeenCalledWith('test message');
326+
});
242327
});

src/reconciler.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,18 @@ const hostConfig: ReactReconciler.HostConfig<
9797
case 'blockquote':
9898
return { type: 'blockquote', children: [], expandable: props.expandable };
9999
case 'button':
100-
const buttonText = typeof props.children === 'string' ? props.children : '';
100+
// Extract text from children - handle string, array, or other types
101+
let buttonText = '';
102+
if (typeof props.children === 'string') {
103+
buttonText = props.children;
104+
} else if (Array.isArray(props.children)) {
105+
// Join array elements, converting non-strings to strings
106+
buttonText = props.children.map((child: any) =>
107+
typeof child === 'string' ? child : String(child)
108+
).join('');
109+
} else if (props.children != null) {
110+
buttonText = String(props.children);
111+
}
101112
return { type: 'button', id: '', text: buttonText, onClick: props.onClick };
102113
case 'row':
103114
return { type: 'row', children: [] };

0 commit comments

Comments
 (0)