Files
wehub-resource-sync b7f52be4c9
CI / Run CI (push) Has been cancelled
CI / check-backend (push) Has been cancelled
CI / check-frontend (push) Has been cancelled
CI / tests (push) Has been cancelled
CI / e2e-tests (push) Has been cancelled
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:48:47 +08:00

458 lines
16 KiB
TypeScript

import 'cypress-plugin-steps';
describe('Command', () => {
// Taller viewport reduces header overlap in headless + absolute-positioned menus
beforeEach(() => {
cy.viewport(1280, 900);
});
it('should correctly display commands', () => {
cy.step('Type command shortcut and select Search');
cy.get(`#chat-input`).type('/sear');
cy.get('.command-item').should('have.length', 1);
cy.get('.command-item').eq(0).click();
cy.step('Send message with Search command');
cy.get(`#chat-input`).type('Hello{enter}');
cy.step('Verify Search command was applied');
cy.get('.step').should('have.length', 2);
cy.get('.step').eq(0).find('.command-span').should('have.text', 'Search');
cy.get('#command-button').should('exist');
cy.get('.step')
.eq(1)
.invoke('text')
.then((text) => {
expect(text.trim()).to.equal('Command: Search');
});
cy.step('Select Picture command');
cy.get(`#chat-input`).type('/pic');
cy.get('.command-item').should('have.length', 1);
cy.get('.command-item').eq(0).click();
cy.step('Send message with Picture command');
cy.get(`#chat-input`).type('Hello{enter}');
cy.step('Verify Picture command clears all commands');
cy.get('.step').should('have.length', 4);
cy.get('.step').eq(2).find('.command-span').should('have.text', 'Picture');
// After selecting Picture, backend clears commands -> tools button disappears
cy.get('#command-button').should('not.exist');
});
it('should correctly display and interact with commands', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Verify initial command buttons are visible');
// Search and StickyButton are button commands
cy.get('#command-Search').should('exist').and('be.visible');
cy.get('#command-StickyButton').should('exist').and('be.visible');
cy.get('#command-Picture').should('not.exist');
cy.get('#command-Canvas').should('not.exist');
cy.get('#command-button').should('exist').and('be.visible');
cy.step('Open command menu and filter to Picture');
cy.get('#chat-input').click();
cy.get('#chat-input').clear();
cy.get('#chat-input').type('/');
cy.get('[data-index]').should('have.length.at.least', 3);
cy.get('#chat-input').type('pic');
cy.get('[data-index]').should('have.length', 1);
cy.get('[data-index="0"]').should('contain', 'Picture');
cy.get('[data-index="0"]').click();
cy.step('Submit message with Picture command');
cy.get('#chat-input').type('Generate an image{enter}');
cy.step('Verify command was sent and UI updated');
cy.get('.step').should('have.length', 2);
cy.get('.step').eq(1).should('contain', 'Command: Picture');
// Backend cleared commands after Picture -> no tools button and no button pills
cy.get('#command-Search').should('not.exist');
cy.get('#command-StickyButton').should('not.exist');
cy.get('#command-button').should('not.exist');
});
it('should handle keyboard navigation in command menu', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Open command menu');
cy.get('#chat-input').click();
cy.get('#chat-input').clear();
cy.get('#chat-input').type('/');
cy.get('[data-index]').should('have.length.at.least', 3);
// Check first item is selected by default using data attribute
cy.get('[data-index="0"]').should('satisfy', ($el) => {
// Component adds bg-accent class when selected, but we check for functional state
return $el.hasClass('bg-accent') || $el.attr('aria-selected') === 'true';
});
cy.step('Navigate down with keyboard');
cy.get('#chat-input').type('{downArrow}');
cy.get('[data-index="1"]').should('satisfy', ($el) => {
return $el.hasClass('bg-accent') || $el.attr('aria-selected') === 'true';
});
cy.step('Navigate up with keyboard');
cy.get('#chat-input').type('{upArrow}');
cy.get('[data-index="0"]').should('satisfy', ($el) => {
return $el.hasClass('bg-accent') || $el.attr('aria-selected') === 'true';
});
cy.step('Select with Enter key');
cy.get('#chat-input').type('{enter}');
cy.get('[data-index]').should('not.exist');
cy.get('[id^="command-"]').should('have.length.at.least', 1);
});
it('should handle Tools dropdown menu', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Open Tools dropdown');
cy.get('#command-button').should('exist').click();
cy.get('[data-popover-content]').should('be.visible');
// Non-button commands are listed: Picture, Canvas, Sticky
cy.get('[data-popover-content] [data-index]').should(
'have.length.at.least',
3
);
cy.step('Verify non-button commands are in dropdown');
cy.get('[data-popover-content]').should('contain', 'Picture');
cy.get('[data-popover-content]').should('contain', 'Canvas');
cy.get('[data-popover-content]').should('contain', 'Sticky');
cy.step('Select Canvas from dropdown');
cy.get('[data-popover-content] [data-index]').contains('Canvas').click();
cy.get('#command-Canvas').should('exist').and('be.visible');
cy.get('#command-button').should('exist');
cy.step('Send message with Canvas command');
cy.get('#chat-input').type('Collaborate on code{enter}');
cy.get('.step').should('contain', 'Command: Canvas');
});
it('should handle button command clicks', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Click Search button command');
cy.get('#command-Search').should('exist');
cy.get('#command-Search').click();
// Check that the button shows selected state (via text color class or aria attribute)
cy.get('#command-Search').should('satisfy', ($el) => {
// The component adds text-command class when selected
return (
$el.hasClass('text-command') ||
$el.find('.text-command').length > 0 ||
$el.attr('aria-pressed') === 'true' ||
$el.attr('data-selected') === 'true'
);
});
cy.step('Send message with Search command selected');
cy.get('#chat-input').type('Search for chainlit{enter}');
cy.get('.step').should('contain', 'Command: Search');
cy.get('#command-Search').should('exist');
cy.step('Deselect Search command');
cy.get('#command-Search').click();
cy.step('Send message without command');
cy.get('#chat-input').type('No command message{enter}');
cy.get('.step').last().should('contain', 'Command:');
});
it('should handle escape key to close command menu', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Open command menu');
cy.get('#chat-input').click();
cy.get('#chat-input').clear();
cy.get('#chat-input').type('/');
cy.get('[data-index]').should('exist');
cy.get('[data-index]').should('have.length.at.least', 3);
cy.step('Close menu with Escape key');
cy.get('#chat-input').type('{esc}');
cy.get('[data-index]').should('not.exist');
cy.get('#chat-input').should('have.value', '/');
});
it('should handle command selection via click', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Type partial command');
cy.get('#chat-input').click();
cy.get('#chat-input').clear();
cy.get('#chat-input').type('/can');
cy.step('Select Canvas from filtered results');
cy.get('[data-index]').should('have.length', 1);
cy.get('[data-index="0"]').should('contain', 'Canvas');
cy.get('[data-index="0"]').click();
cy.step('Verify Canvas command is selected');
cy.get('#command-Canvas').should('exist').and('be.visible');
cy.get('[data-index]').should('not.exist');
cy.get('#chat-input').invoke('val').should('not.contain', '/can');
});
it('should properly handle selected non-button command removal', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Select Canvas from Tools dropdown');
cy.get('#command-button').should('exist').click();
cy.get('[data-popover-content]').should('be.visible');
cy.get('[data-popover-content] [data-index]').contains('Canvas').click();
cy.step('Verify Canvas is selected');
cy.get('#command-Canvas').should('exist').and('be.visible');
cy.step('Deselect Canvas');
cy.get('#command-Canvas').click();
cy.get('#command-Canvas').should('not.exist');
cy.get('#command-button').should('exist');
});
it('should handle mouse hover in command menus', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Open command menu');
cy.get('#chat-input').click();
cy.get('#chat-input').clear();
cy.get('#chat-input').type('/');
// Make sure the menu itself is visible
cy.get('[data-index]').should('have.length.at.least', 3);
cy.step('Hover over second item');
// First item should be selected initially
cy.get('[data-index="0"]').should('satisfy', ($el) => {
return $el.hasClass('bg-accent') || $el.attr('aria-selected') === 'true';
});
cy.get('[data-index="1"]').scrollIntoView();
cy.get('[data-index="1"]').trigger('mousemove', { force: true });
cy.get('[data-index="1"]').should('satisfy', ($el) => {
return $el.hasClass('bg-accent') || $el.attr('aria-selected') === 'true';
});
cy.step('Verify selection persists after mouse leave');
cy.get('[data-index="1"]').trigger('mouseleave', { force: true });
cy.get('[data-index="1"]').should('satisfy', ($el) => {
return $el.hasClass('bg-accent') || $el.attr('aria-selected') === 'true';
});
});
it('should filter commands correctly', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Filter for Picture command');
cy.get('#chat-input').click();
cy.get('#chat-input').clear();
cy.get('#chat-input').type('/pic');
cy.get('[data-index]').should('have.length', 1);
cy.get('[data-index="0"]').should('contain', 'Picture');
cy.step('Filter for Canvas command');
cy.get('#chat-input').clear();
cy.get('#chat-input').type('/can');
cy.get('[data-index]').should('have.length', 1);
cy.get('[data-index="0"]').should('contain', 'Canvas');
cy.step('Filter with non-matching text');
cy.get('#chat-input').clear();
cy.get('#chat-input').type('/xyz');
cy.get('[data-index]').should('not.exist');
});
it('should handle Tools dropdown keyboard navigation', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Open Tools dropdown');
cy.get('#command-button').should('exist').click();
cy.get('[data-popover-content]').should('be.visible');
// First item should be selected by default
cy.get('[data-popover-content] [data-index="0"]').should(
'satisfy',
($el) => {
return (
$el.hasClass('bg-accent') || $el.attr('aria-selected') === 'true'
);
}
);
cy.step('Navigate down in dropdown');
cy.get('[data-popover-content]').type('{downArrow}');
cy.get('[data-popover-content] [data-index="1"]').should(
'satisfy',
($el) => {
return (
$el.hasClass('bg-accent') || $el.attr('aria-selected') === 'true'
);
}
);
cy.step('Navigate up in dropdown');
cy.get('[data-popover-content]').type('{upArrow}');
cy.get('[data-popover-content] [data-index="0"]').should(
'satisfy',
($el) => {
return (
$el.hasClass('bg-accent') || $el.attr('aria-selected') === 'true'
);
}
);
cy.step('Select with Enter key');
cy.get('[data-popover-content]').type('{enter}');
cy.get('[data-popover-content]').should('not.exist');
cy.get(
'[id^="command-"][id$="icture"], [id^="command-"][id$="anvas"], [id^="command-"][id$="ticky"]'
).should('exist');
});
it('should handle command persistence correctly (non-button)', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Select persistent Sticky command');
cy.get('#chat-input').click();
cy.get('#chat-input').clear();
cy.get('#chat-input').type('/sti');
cy.get('[data-index]').should('have.length.at.least', 1);
cy.get('[data-index]').contains('Sticky').click();
// Selected command should appear as a pill
cy.get('#command-Sticky').should('exist').and('be.visible');
cy.step('Send first message with persistent command');
cy.get('#chat-input').type('First sticky message{enter}');
cy.get('.step').last().should('contain', 'Command: Sticky');
cy.step('Send second message - command should persist');
cy.get('#chat-input').type('Second sticky message{enter}');
cy.get('.step').last().should('contain', 'Command: Sticky');
cy.step('Deselect persistent command');
cy.get('#command-Sticky').click();
cy.step('Send message without command');
cy.get('#chat-input').type('No command now{enter}');
cy.get('.step').last().should('contain', 'Command:');
});
it('should handle command persistence correctly (button)', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Select persistent StickyButton command');
cy.get('#command-StickyButton').should('exist').click();
cy.step('Send first message with persistent button');
cy.get('#chat-input').type('StickyButton #1{enter}');
cy.get('.step').last().should('contain', 'Command: StickyButton');
cy.step('Send second message - button should remain selected');
cy.get('#chat-input').type('StickyButton #2{enter}');
cy.get('.step').last().should('contain', 'Command: StickyButton');
cy.step('Deselect persistent button');
cy.get('#command-StickyButton').click();
cy.step('Send message without command');
cy.get('#chat-input').type('After deselect{enter}');
cy.get('.step').last().should('contain', 'Command:');
});
it('should show commands in correct places', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Verify button commands are visible as buttons');
// Buttons: Search & StickyButton (button=true) visible; non-button are not
cy.get('#command-Search').should('exist').and('be.visible');
cy.get('#command-StickyButton').should('exist').and('be.visible');
cy.get('#command-Picture').should('not.exist');
cy.get('#command-Canvas').should('not.exist');
cy.get('#command-Sticky').should('not.exist');
cy.step('Verify Tools menu contains non-button commands');
cy.get('#command-button').click();
cy.get('[data-popover-content]').within(() => {
cy.contains('Picture').should('exist');
cy.contains('Canvas').should('exist');
cy.contains('Sticky').should('exist');
cy.contains('Search').should('not.exist');
cy.contains('StickyButton').should('not.exist');
});
cy.get('body').click(0, 0);
cy.get('[data-popover-content]').should('not.exist');
cy.step('Verify inline menu contains all commands');
cy.get('#chat-input').type('/');
cy.get('[data-index]').should('have.length', 5); // Total: Picture, Search, Canvas, Sticky, StickyButton
cy.get('[data-index]')
.parent()
.parent()
.within(() => {
cy.contains('Picture').should('exist');
cy.contains('Canvas').should('exist');
cy.contains('Search').should('exist');
cy.contains('Sticky').should('exist');
cy.contains('StickyButton').should('exist');
});
});
it('should test command clearing behavior with Picture command', () => {
cy.visit('/');
cy.get('#chat-input').should('exist');
cy.step('Verify initial commands are available');
cy.get('#command-Search').should('exist');
cy.get('#command-StickyButton').should('exist');
cy.get('#command-button').should('exist');
cy.step('Select and use Picture command');
cy.get('#chat-input').type('/pic');
cy.get('[data-index="0"]').click();
cy.get('#chat-input').type('Generate a sunset{enter}');
cy.step('Verify all commands are cleared after Picture');
cy.get('#command-Search').should('not.exist');
cy.get('#command-StickyButton').should('not.exist');
cy.get('#command-button').should('not.exist');
cy.step('Verify slash command shows no commands');
cy.get('#chat-input').type('/');
cy.get('[data-index]').should('not.exist');
cy.step('Send regular message without commands');
cy.get('#chat-input').clear();
cy.get('#chat-input').type('Regular message{enter}');
cy.get('.step').last().should('contain', 'Command:');
});
});