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>
This commit is contained in:
Kyle Fang
2025-07-01 09:20:29 +08:00
parent 8c1b225e3a
commit a543be55f0
3 changed files with 97 additions and 32 deletions

View File

@@ -280,37 +280,6 @@ export class MtcuteAdapter {
return containerId;
}
// Edit an existing message with a new React tree
async editReactMessage(
chatId: number | string,
messageId: number,
app: ReactElement
) {
const containerId = `${chatId}_${messageId}`;
let container = this.activeContainers.get(containerId);
if (!container) {
container = createContainer();
this.activeContainers.set(containerId, container);
}
// Set up edit callback
container.container.onRenderContainer = async (root) => {
const textWithEntities = this.rootNodeToTextWithEntities(root);
const replyMarkup = this.rootNodeToInlineKeyboard(root, containerId);
await this.client.editMessage({
chatId,
message: messageId,
text: textWithEntities,
replyMarkup
});
};
// Render the app
container.render(app);
}
// Convenience method to handle commands with React
onCommand(command: string, handler: (ctx: any) => ReactElement) {
this.commandHandlers.set(command, handler);

View File

@@ -187,6 +187,50 @@ describe('Telegram Reconciler', () => {
expect(onClick).toHaveBeenCalledTimes(1);
});
it('should handle buttons with complex children', async () => {
const { container, render } = createContainer();
const onClick = vi.fn();
const mode = 'normal';
render(
<row>
<button onClick={onClick}>
Switch to {mode === 'normal' ? 'Secret' : 'Normal'} Mode
</button>
</row>
);
await new Promise(resolve => setTimeout(resolve, 0));
const row = container.root.children[0];
expect(row?.type).toBe('row');
if (row?.type === 'row') {
expect(row.children[0]?.text).toBe('Switch to Secret Mode');
}
});
it('should handle buttons with array children', async () => {
const { container, render } = createContainer();
render(
<row>
<button>{'Hello'}{' '}{'World'}</button>
<button>{['One', ' ', 'Two', ' ', 'Three']}</button>
<button>{123} items</button>
</row>
);
await new Promise(resolve => setTimeout(resolve, 0));
const row = container.root.children[0];
expect(row?.type).toBe('row');
if (row?.type === 'row') {
expect(row.children[0]?.text).toBe('Hello World');
expect(row.children[1]?.text).toBe('One Two Three');
expect(row.children[2]?.text).toBe('123 items');
}
});
it('should work with React state', async () => {
const { container, render, clickButton } = createContainer();
@@ -239,4 +283,45 @@ describe('Telegram Reconciler', () => {
content: '0'
});
});
it('should handle input elements', async () => {
const { container, render } = createContainer();
const onSubmit = vi.fn();
render(
<>
<input onSubmit={onSubmit} />
<input onSubmit={onSubmit} autoDelete />
</>
);
await new Promise(resolve => setTimeout(resolve, 0));
expect(container.root.children).toHaveLength(2);
expect(container.root.children[0]).toEqual({
type: 'input',
onSubmit: expect.any(Function),
autoDelete: undefined
});
expect(container.root.children[1]).toEqual({
type: 'input',
onSubmit: expect.any(Function),
autoDelete: true
});
// Check input callbacks
expect(container.inputCallbacks).toHaveLength(2);
expect(container.inputCallbacks[0]).toEqual({
callback: expect.any(Function),
autoDelete: undefined
});
expect(container.inputCallbacks[1]).toEqual({
callback: expect.any(Function),
autoDelete: true
});
// Test callback execution
container.inputCallbacks[0]?.callback('test message');
expect(onSubmit).toHaveBeenCalledWith('test message');
});
});

View File

@@ -97,7 +97,18 @@ const hostConfig: ReactReconciler.HostConfig<
case 'blockquote':
return { type: 'blockquote', children: [], expandable: props.expandable };
case 'button':
const buttonText = typeof props.children === 'string' ? props.children : '';
// Extract text from children - handle string, array, or other types
let buttonText = '';
if (typeof props.children === 'string') {
buttonText = props.children;
} else if (Array.isArray(props.children)) {
// Join array elements, converting non-strings to strings
buttonText = props.children.map((child: any) =>
typeof child === 'string' ? child : String(child)
).join('');
} else if (props.children != null) {
buttonText = String(props.children);
}
return { type: 'button', id: '', text: buttonText, onClick: props.onClick };
case 'row':
return { type: 'row', children: [] };