diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 2daafdb6f64..9c10d335507 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,4 +1,12 @@ +## 15.6.1 + +_Released 11/18/2025 (PENDING)_ + +**Misc:** + +- The keyboard shortcuts modal now displays the keyboard shortcut for saving Studio changes - `⌘` + `s` for Mac or `Ctrl` + `s` for Windows/Linux. Addressed [#32862](https://github.com/cypress-io/cypress/issues/32862). Addressed in [#32864](https://github.com/cypress-io/cypress/pull/32864). + ## 15.6.0 _Released 11/4/2025_ @@ -23,7 +31,6 @@ _Released 11/4/2025_ - The hitbox for expanding a grouped command has been widened. Addresses [#32778](https://github.com/cypress-io/cypress/issues/32778). Addressed in [#32783](https://github.com/cypress-io/cypress/pull/32783). - Have cursor on hover of the AUT URL to show as pointer. Addresses [#32777](https://github.com/cypress-io/cypress/issues/32777). Addressed in [#32782](https://github.com/cypress-io/cypress/pull/32782). - WebKit now prefers a cookie's fully qualified `domain` when requesting a cookie value via [`cy.getCookie()`](https://docs.cypress.io/api/commands/getcookie). If none are found, the cookie's apex domain will be used as a fallback. Addresses [#29954](https://github.com/cypress-io/cypress/issues/29954), [#29973](https://github.com/cypress-io/cypress/issues/29973) and [#30392](https://github.com/cypress-io/cypress/issues/30392). Addressed in [#32852](https://github.com/cypress-io/cypress/pull/32852). -- The 'Next' tooltip style was updated. Addressed in [#32866](https://github.com/cypress-io/cypress/pull/32866). - Make test name header sticky in studio mode and in the tests list. Addresses [#32591](https://github.com/cypress-io/cypress/issues/32591). Addressed in [#32840](https://github.com/cypress-io/cypress/pull/32840) - The [`cy.exec()`](https://docs.cypress.io/api/commands/exec) type now reflects the correct yielded response type of `exitCode`. Addresses [#32875](https://github.com/cypress-io/cypress/issues/32875). Addressed in [#32885](https://github.com/cypress-io/cypress/pull/32885). diff --git a/packages/app/src/navigation/KeyboardBindingsModal.cy.tsx b/packages/app/src/navigation/KeyboardBindingsModal.cy.tsx index 667ed919cd9..c030c79dffa 100644 --- a/packages/app/src/navigation/KeyboardBindingsModal.cy.tsx +++ b/packages/app/src/navigation/KeyboardBindingsModal.cy.tsx @@ -2,16 +2,143 @@ import KeyboardBindingsModal from './KeyboardBindingsModal.vue' // tslint:disable-next-line: no-implicit-dependencies - unsure how to handle these import { defaultMessages } from '@cy/i18n' +const setPlatformConfig = (platform: string) => { + return cy.window().then((win) => { + // @ts-ignore + win.__CYPRESS_CONFIG__ = { + base64Config: Cypress.Buffer.from(JSON.stringify({ platform })).toString('base64'), + } + }) +} + describe('KeyboardBindingsModal', () => { - it('renders expected content', () => { - cy.mount(() => { - return + describe('rendering', () => { + it('renders expected content', () => { + cy.mount(() => { + return + }) + + const expectedContent = defaultMessages.sidebar.keyboardShortcuts + + Object.values(expectedContent).forEach((text) => { + cy.contains(text).should('be.visible') + }) + }) + + it('renders all keyboard bindings with their keys', () => { + cy.mount(() => { + return + }) + + // Check that all keyboard shortcuts are displayed with their keys + cy.contains(defaultMessages.sidebar.keyboardShortcuts.rerun).should('be.visible') + cy.contains('r').should('be.visible') + + cy.contains(defaultMessages.sidebar.keyboardShortcuts.stop).should('be.visible') + cy.contains('s').should('be.visible') + + cy.contains(defaultMessages.sidebar.keyboardShortcuts.toggle).should('be.visible') + cy.contains('f').should('be.visible') + + cy.contains(defaultMessages.sidebar.keyboardShortcuts.studioSave).should('be.visible') + }) + + it('does not render when show is false', () => { + cy.mount(() => { + return + }) + + cy.get('[data-cy="keyboard-modal"]').should('not.exist') + }) + }) + + describe('platform-specific keyboard shortcuts', () => { + it('shows ⌘+s on macOS (darwin)', () => { + setPlatformConfig('darwin').then(() => { + cy.mount(() => { + return + }) + + cy.contains(defaultMessages.sidebar.keyboardShortcuts.studioSave).should('be.visible') + cy.contains('⌘').should('be.visible') + cy.contains('+').should('be.visible') + cy.contains('s').should('be.visible') + cy.contains('Ctrl').should('not.exist') + + cy.percySnapshot() + }) + }) + + it('shows Ctrl+s on Windows', () => { + setPlatformConfig('win32').then(() => { + cy.mount(() => { + return + }) + + cy.contains(defaultMessages.sidebar.keyboardShortcuts.studioSave).should('be.visible') + cy.contains('Ctrl').should('be.visible') + cy.contains('+').should('be.visible') + cy.contains('s').should('be.visible') + cy.contains('⌘').should('not.exist') + + cy.percySnapshot() + }) }) - const expectedContent = defaultMessages.sidebar.keyboardShortcuts + it('shows Ctrl+s on Linux', () => { + setPlatformConfig('linux').then(() => { + cy.mount(() => { + return + }) + + cy.contains(defaultMessages.sidebar.keyboardShortcuts.studioSave).should('be.visible') + cy.contains('Ctrl').should('be.visible') + cy.contains('+').should('be.visible') + cy.contains('s').should('be.visible') + cy.contains('⌘').should('not.exist') + }) + }) + + it('falls back to darwin if platform is not available', () => { + cy.window().then((win) => { + // @ts-ignore + win.__CYPRESS_CONFIG__ = undefined + }).then(() => { + cy.mount(() => { + return + }) + + // Should fallback to darwin and show ⌘ + cy.contains('⌘').should('be.visible') + cy.contains('Ctrl').should('not.exist') + }) + }) + }) + + describe('modal behavior', () => { + it('emits close event when close button is clicked', () => { + const closeSpy = cy.stub().as('closeSpy') + + cy.mount(() => { + return + }) + + cy.get('[data-cy="keyboard-modal"]').should('be.visible') + cy.findByLabelText('Close').click() + cy.get('@closeSpy').should('have.been.calledOnce') + }) + + it('emits close event when clicking outside the modal', () => { + const closeSpy = cy.stub().as('closeSpy') + + cy.mount(() => { + return + }) - Object.values(expectedContent).forEach((text) => { - cy.contains(text).should('be.visible') + cy.get('[data-cy="keyboard-modal"]').should('be.visible') + // Click outside the modal (on the backdrop) + cy.get('body').click(0, 0) + cy.get('@closeSpy').should('have.been.calledOnce') }) }) }) diff --git a/packages/app/src/navigation/KeyboardBindingsModal.vue b/packages/app/src/navigation/KeyboardBindingsModal.vue index 6b02ad8a29a..ab69cde8241 100644 --- a/packages/app/src/navigation/KeyboardBindingsModal.vue +++ b/packages/app/src/navigation/KeyboardBindingsModal.vue @@ -17,21 +17,36 @@

{{ binding.description }}

- - {{ key }} - + + {{ key }} + + + {{ key }} + + diff --git a/packages/frontend-shared/src/locales/en-US.json b/packages/frontend-shared/src/locales/en-US.json index 8a9b600c69c..16766eb0ac5 100644 --- a/packages/frontend-shared/src/locales/en-US.json +++ b/packages/frontend-shared/src/locales/en-US.json @@ -285,7 +285,8 @@ "title": "Keyboard shortcuts", "rerun": "Re-run tests", "stop": "Stop tests", - "toggle": "Toggle specs list" + "toggle": "Toggle specs list", + "studioSave": "Save Studio changes" }, "toggleLabel": { "expanded": "Collapse sidebar",