-
Notifications
You must be signed in to change notification settings - Fork 132
Feat/540-emoji-codes #2776
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Feat/540-emoji-codes #2776
Changes from 36 commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
7cbc74a
Basic working emojicode replacement
holmesworcester 9be1478
WIP with stubbed functions and instructions for writing tests
holmesworcester 41d7a0d
replace as you type works, without it being over eager on :s as you t…
holmesworcester 277b2f9
Emojis replace on typing, send. Not over-eager. Jest tests pass.
holmesworcester 8f31d47
fixes bug where hearts weren't red in messages
holmesworcester 712b990
adds big red hearts to the text entry field
holmesworcester 2e61af1
adds a large emoji list
holmesworcester 5d9e45c
Adds basic tab completion for emojicodes
holmesworcester 9915d86
adds a nice dropdown for autocompletion
holmesworcester 798b90f
adds some passing cypress tests
holmesworcester 9e17571
updates CHANGELOG
holmesworcester ff1ae59
Emoji dropds matches layout in design for mentions
holmesworcester aabbae6
Increases number of emojis in dropdown and matches the designs better
holmesworcester f6c2754
Fixes bug where emoji dropdown did not disappear on sending message
holmesworcester 088035c
dropdown now closes when clicking away
holmesworcester da11694
adds tests for emoji picker dropdown
holmesworcester 9430c78
Adds a shout out to agiledev24 )(whose work I did not end up using be…
holmesworcester 98999aa
Merge branch 'develop' into feat/540-emoji-codes
holmesworcester c2bd331
linter changes
holmesworcester 4e15dc1
updates to avoid conflict with upstream?
holmesworcester 2823a21
Merge branch 'develop' into feat/540-emoji-codes
holmesworcester 8e5d649
cypress emojicode and emoji dropdown tests are working correctly
holmesworcester 29dc4d4
updates snapshots
holmesworcester c71af05
Fixes breaking pageup/pagedown Cypress tests
holmesworcester 21ddaab
Adds a storybook for messages with emojis
holmesworcester 63b5da9
breaks out emoji dropdown to component
holmesworcester b5d0d8b
enter key (not just tab) autocompletes emoji
holmesworcester 83bda79
emoji dropdown scrolls with arrow keys
holmesworcester 056797a
renames emoji tests
holmesworcester 4e22b02
adds test for ensuring dropdown scrolls, ensuring enter autocompletes
holmesworcester 8b0e679
Merge remote-tracking branch 'origin/develop' into feat/540-emoji-codes
holmesworcester 7e36e19
Updates changelog to remove redundant fixes section
holmesworcester d4a4176
Fixes emoji dropdown component layout in storybook
holmesworcester 034ae8e
adds emoji dropdown to storybook
holmesworcester f80af1f
removes needless wait() statements
holmesworcester 4f314ab
Update CSS class references in ChannelInput tests
holmesworcester d7bb1ee
Update CHANGELOG.md
holmesworcester 2bbdbc8
Update packages/desktop/src/renderer/components/widgets/channels/Chan…
holmesworcester 90369ff
Removes emoticons beginning with > to avoid conflict with markdown, b…
holmesworcester f91cc9d
Comments out emoticons whose quotes are being stripped by our linter
holmesworcester File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
packages/desktop/src/renderer/components/Channel/Channel.emoji.cy.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| import React from 'react' | ||
| import CssBaseline from '@mui/material/CssBaseline' | ||
| import { composeStories, setGlobalConfig } from '@storybook/testing-react' | ||
| import { it, beforeEach, cy, Cypress, describe } from 'local-cypress' | ||
|
|
||
| import * as stories from './Channel.stories' | ||
| import { withTheme } from '../../storybook/decorators' | ||
| import { mount } from 'cypress/react18' | ||
|
|
||
| const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/ | ||
| Cypress.on('uncaught:exception', err => { | ||
| // returning false here prevents Cypress from failing the test | ||
| if (resizeObserverLoopErrRe.test(err.message)) { | ||
| return false | ||
| } | ||
| }) | ||
|
|
||
| // @ts-expect-error | ||
| setGlobalConfig(withTheme) | ||
|
|
||
| // Use SendingMessagesWithScroll story to avoid TypeScript errors in other stories | ||
| const { SendingMessagesWithScroll } = composeStories(stories) | ||
|
|
||
| describe('Emoji conversion in code blocks test', () => { | ||
| beforeEach(() => { | ||
| mount( | ||
| <React.Fragment> | ||
| <CssBaseline> | ||
| <SendingMessagesWithScroll /> | ||
| </CssBaseline> | ||
| </React.Fragment> | ||
| ) | ||
| cy.wait(0) | ||
| }) | ||
|
|
||
| it('should NOT convert text typed inside an unclosed code fence', () => { | ||
| cy.get('[data-testid="messageInput"]').type('```Some code :) ') | ||
|
|
||
| // The code fence is still open (no closing triple backticks). | ||
| // So ":) " should remain literal and not become an emoji. | ||
| cy.get('[data-testid="messageInput"]').should('have.value', '```Some code :) ') | ||
| }) | ||
|
|
||
| it('should convert text immediately after closing the code fence', () => { | ||
| cy.get('[data-testid="messageInput"]') | ||
| // Start an open code fence | ||
| .type('```Inside code block :smile:') | ||
| // Still open => :smile: remains literal | ||
| .should('have.value', '```Inside code block :smile:') | ||
| // Close the code block | ||
| .type('``` ') | ||
| // Now that fence is closed, the space after “``` ” is outside code block | ||
| // Type a known emoticon | ||
| .type(':p') | ||
| .should('have.value', '```Inside code block :smile:``` :p') | ||
| // Type punctuation => triggers conversion of :p | ||
| .type('.') | ||
|
|
||
| cy.get('[data-testid="messageInput"]').should('have.value', '```Inside code block :smile:``` 😛.') | ||
| }) | ||
|
|
||
| it('should convert text typed entirely outside code fences', () => { | ||
| // Type something normal outside code block | ||
| cy.get('[data-testid="messageInput"]').type('Hello :smile: ').should('have.value', 'Hello 😄 ') | ||
| }) | ||
|
|
||
| it('should handle multiple code fences correctly', () => { | ||
| cy.get('[data-testid="messageInput"]') | ||
| // First code fence | ||
| .type('```Block1 :)``` code between ```Block2 :heart: ') | ||
|
|
||
| // "Block1 :)" is inside the first code fence => no conversion | ||
| // "Block2 :heart:" is inside second code fence => no conversion yet | ||
| cy.get('[data-testid="messageInput"]').should('have.value', '```Block1 :)``` code between ```Block2 :heart: ') | ||
|
|
||
| // close second code fence | ||
| cy.get('[data-testid="messageInput"]').type('``` ') | ||
|
|
||
| // After closing the second fence, type a space + emoticon | ||
| cy.get('[data-testid="messageInput"]').type(':p ') | ||
|
|
||
| // Now the :p should convert to 😛 because we’re outside all fences | ||
| cy.get('[data-testid="messageInput"]').should( | ||
| 'have.value', | ||
| '```Block1 :)``` code between ```Block2 :heart: ``` 😛 ' | ||
| ) | ||
| }) | ||
| }) |
172 changes: 172 additions & 0 deletions
172
packages/desktop/src/renderer/components/Channel/Channel.emoji.dropdown.cy.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| import React from 'react' | ||
| import CssBaseline from '@mui/material/CssBaseline' | ||
| import { composeStories, setGlobalConfig } from '@storybook/testing-react' | ||
| import { it, beforeEach, cy, Cypress, describe } from 'local-cypress' | ||
|
|
||
| import * as stories from './Channel.stories' | ||
| import { withTheme } from '../../storybook/decorators' | ||
| import { mount } from 'cypress/react18' | ||
| import { ArrowKeyStepper } from 'react-virtualized' | ||
|
|
||
| const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/ | ||
| Cypress.on('uncaught:exception', err => { | ||
| if (resizeObserverLoopErrRe.test(err.message)) { | ||
| return false | ||
| } | ||
| }) | ||
|
|
||
| // @ts-expect-error | ||
| setGlobalConfig(withTheme) | ||
|
|
||
| // Use SendingMessagesWithScroll story to avoid TypeScript errors in other stories | ||
| const { SendingMessagesWithScroll } = composeStories(stories) | ||
|
|
||
| describe('Emoji dropdown behavior', () => { | ||
| // Tests for checking that the emoji dropdown appears and disappears as expected | ||
| // - appears when typing colon + characters | ||
| // - disappears with Tab key (emoji selection) | ||
| // - disappears with mouse click (emoji selection) | ||
| // - disappears with Enter key (message sending) | ||
| // - disappears when clicking away | ||
| // - disappears when typing non-emoji pattern | ||
| beforeEach(() => { | ||
| mount( | ||
| <React.Fragment> | ||
| <CssBaseline> | ||
| <SendingMessagesWithScroll /> | ||
| </CssBaseline> | ||
| </React.Fragment> | ||
| ) | ||
| cy.wait(0) | ||
| }) | ||
|
|
||
| it('should show dropdown when typing colon followed by characters', () => { | ||
| // Start typing an emoji code | ||
| cy.get('[data-testid="messageInput"]').type(':sm') | ||
|
|
||
| // Verify the dropdown appears | ||
| cy.get('[data-testid="emoji-dropdown"]').should('be.visible').should('contain', ':smile:') | ||
| }) | ||
|
|
||
| it('should hide dropdown when selecting an emoji with Tab key', () => { | ||
| // Start typing an emoji code | ||
| cy.get('[data-testid="messageInput"]').type(':sm') | ||
|
|
||
| // Verify dropdown appears | ||
| cy.get('[data-testid="emoji-dropdown"]').should('be.visible') | ||
|
|
||
| // Instead of Tab, trigger the keydown event directly | ||
| cy.get('[data-testid="messageInput"]').trigger('keydown', { | ||
| key: 'Tab', | ||
| keyCode: 9, | ||
| which: 9, | ||
| code: 'Tab', | ||
| }) | ||
|
|
||
| // Verify dropdown disappears | ||
| cy.get('[data-testid="emoji-dropdown"]').should('not.exist') | ||
| }) | ||
|
|
||
| it('should hide dropdown when selecting an emoji with Enter key', () => { | ||
| // Start typing an emoji code | ||
| cy.get('[data-testid="messageInput"]').type(':sm') | ||
|
|
||
| // Verify dropdown appears | ||
| cy.get('[data-testid="emoji-dropdown"]').should('be.visible') | ||
|
|
||
| // Trigger the Enter keydown event directly | ||
| cy.get('[data-testid="messageInput"]').trigger('keydown', { | ||
| key: 'Enter', | ||
| keyCode: 13, | ||
| which: 13, | ||
| code: 'Enter', | ||
| }) | ||
|
|
||
| // Verify dropdown disappears | ||
| cy.get('[data-testid="emoji-dropdown"]').should('not.exist') | ||
| }) | ||
|
|
||
| it('should hide dropdown when clicking an emoji suggestion', () => { | ||
| // Start typing an emoji code | ||
| cy.get('[data-testid="messageInput"]').type(':sm') | ||
|
|
||
| // Verify dropdown appears | ||
| cy.get('[data-testid="emoji-dropdown"]').should('be.visible') | ||
|
|
||
| // Click the first emoji suggestion | ||
| cy.get('[data-testid="emoji-dropdown"] > div').first().click() | ||
|
|
||
| // Verify dropdown disappears | ||
| cy.get('[data-testid="emoji-dropdown"]').should('not.exist') | ||
|
|
||
| // Verify emoji was inserted (smiley emoji U+1F603) | ||
| cy.get('[data-testid="messageInput"]').should('have.value', '😃') | ||
| }) | ||
|
|
||
| it('should hide dropdown when clicking away', () => { | ||
| // Start typing an emoji code | ||
| cy.get('[data-testid="messageInput"]').type(':sm') | ||
|
|
||
| // Verify dropdown appears | ||
| cy.get('[data-testid="emoji-dropdown"]').should('be.visible') | ||
|
|
||
| // Click away from the input | ||
| cy.get('body').click() | ||
|
|
||
| // Verify dropdown no longer exists in the DOM | ||
| cy.get('[data-testid="emoji-dropdown"]').should('not.exist') | ||
| }) | ||
|
|
||
| it('should scroll dropdown when navigating with arrow keys', () => { | ||
| // Type ":h" to get a decent number of emoji suggestions without being too specific | ||
| cy.get('[data-testid="messageInput"]').type(':h') | ||
|
|
||
| // Verify dropdown appears | ||
| cy.get('[data-testid="emoji-dropdown"]').should('be.visible') | ||
|
|
||
| // Check initial scroll position | ||
| cy.get('[data-testid="emoji-dropdown"]').then($dropdown => { | ||
| const initialScrollTop = $dropdown[0].scrollTop | ||
|
|
||
| // Add a data attribute to track initial scroll position | ||
| $dropdown[0].setAttribute('data-initial-scroll', initialScrollTop.toString()) | ||
|
|
||
| // Press arrow down key multiple times to navigate through suggestions | ||
| for (let i = 0; i < 8; i++) { | ||
| cy.get('[data-testid="messageInput"]').trigger('keydown', { | ||
| key: 'ArrowDown', | ||
| keyCode: 40, | ||
| which: 40, | ||
| code: 'ArrowDown', | ||
| bubbles: true, | ||
| }) | ||
| } | ||
|
|
||
| // After key presses, verify the dropdown is still visible | ||
| cy.get('[data-testid="emoji-dropdown"]') | ||
| .should('be.visible') | ||
| .then($updatedDropdown => { | ||
| // Get the initial scroll position from the data attribute | ||
| const initialScroll = parseInt($updatedDropdown[0].getAttribute('data-initial-scroll') || '0') | ||
| const currentScrollTop = $updatedDropdown[0].scrollTop | ||
|
|
||
| // If scrollTop changed, scrolling occurred | ||
| expect(currentScrollTop).to.be.gte(initialScroll) | ||
| }) | ||
| }) | ||
| }) | ||
|
|
||
| it('should hide dropdown when typing text that no longer matches emoji pattern', () => { | ||
| // Start typing an emoji code | ||
| cy.get('[data-testid="messageInput"]').type(':sm') | ||
|
|
||
| // Verify dropdown appears | ||
| cy.get('[data-testid="emoji-dropdown"]').should('be.visible') | ||
|
|
||
| // Type a space to break the emoji pattern | ||
| cy.get('[data-testid="messageInput"]').type(' ') | ||
|
|
||
| // Verify dropdown disappears | ||
| cy.get('[data-testid="emoji-dropdown"]').should('not.exist') | ||
| }) | ||
| }) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.