Просмотр исходного кода

Remove ColorManager (#22974)

GitOrigin-RevId: 32cb6fd599345eaf8e474553da5c6d3080575ee8
Alf Eaton 1 год назад
Родитель
Сommit
71ab3a41ed
22 измененных файлов с 195 добавлено и 208 удалено
  1. 0 1
      services/web/frontend/js/features/chat/components/chat-pane.tsx
  2. 8 8
      services/web/frontend/js/features/chat/components/message-list.tsx
  3. 17 19
      services/web/frontend/js/features/chat/components/message.tsx
  4. 2 2
      services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.jsx
  5. 2 2
      services/web/frontend/js/features/history/components/change-list/metadata-users-list.tsx
  6. 3 2
      services/web/frontend/js/features/history/components/change-list/user-name-with-colored-badge.tsx
  7. 2 2
      services/web/frontend/js/features/history/context/history-context.tsx
  8. 3 3
      services/web/frontend/js/features/history/utils/highlights-from-diff-response.ts
  9. 0 8
      services/web/frontend/js/features/history/utils/history-details.ts
  10. 2 2
      services/web/frontend/js/features/ide-react/context/online-users-context.tsx
  11. 3 3
      services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts
  12. 2 2
      services/web/frontend/js/features/project-list/util/tag.ts
  13. 4 3
      services/web/frontend/js/features/review-panel-new/components/review-panel-entry-user.tsx
  14. 0 95
      services/web/frontend/js/ide/colors/ColorManager.js
  15. 19 5
      services/web/frontend/js/shared/utils/colors.ts
  16. 49 10
      services/web/frontend/js/shared/utils/md5.ts
  17. 9 1
      services/web/frontend/stories/chat.stories.jsx
  18. 39 20
      services/web/test/frontend/features/chat/components/message-list.test.jsx
  19. 6 6
      services/web/test/frontend/features/chat/components/message.test.jsx
  20. 14 5
      services/web/test/frontend/features/project-list/helpers/render-with-context.tsx
  21. 10 8
      services/web/test/frontend/shared/utils/colors.test.js
  22. 1 1
      services/web/test/frontend/shared/utils/md5.test.js

+ 0 - 1
services/web/frontend/js/features/chat/components/chat-pane.tsx

@@ -94,7 +94,6 @@ const ChatPane = React.memo(function ChatPane() {
             {shouldDisplayPlaceholder && <Placeholder />}
             <MessageList
               messages={messages}
-              userId={user.id}
               resetUnreadMessages={markMessagesAsRead}
             />
           </Suspense>

+ 8 - 8
services/web/frontend/js/features/chat/components/message-list.tsx

@@ -1,7 +1,7 @@
 import moment from 'moment'
 import Message from './message'
-import { UserId } from '../../../../../types/user'
 import type { Message as MessageType } from '@/features/chat/context/chat-context'
+import { useUserContext } from '@/shared/context/user-context'
 
 const FIVE_MINUTES = 5 * 60 * 1000
 
@@ -16,14 +16,11 @@ function formatTimestamp(date: moment.MomentInput) {
 interface MessageListProps {
   messages: MessageType[]
   resetUnreadMessages(...args: unknown[]): unknown
-  userId: UserId | null
 }
 
-function MessageList({
-  messages,
-  resetUnreadMessages,
-  userId,
-}: MessageListProps) {
+function MessageList({ messages, resetUnreadMessages }: MessageListProps) {
+  const user = useUserContext()
+
   function shouldRenderDate(messageIndex: number) {
     if (messageIndex === 0) {
       return true
@@ -61,7 +58,10 @@ function MessageList({
               </time>
             </div>
           )}
-          <Message message={message} userId={userId} />
+          <Message
+            message={message}
+            fromSelf={message.user ? message.user.id === user.id : false}
+          />
         </li>
       ))}
     </ul>

+ 17 - 19
services/web/frontend/js/features/chat/components/message.tsx

@@ -1,42 +1,40 @@
-import { getHueForUserId } from '../../../shared/utils/colors'
+import { getHueForUserId } from '@/shared/utils/colors'
 import MessageContent from './message-content'
 import type { Message as MessageType } from '@/features/chat/context/chat-context'
 import { User } from '../../../../../types/user'
 
 interface MessageProps {
   message: MessageType
-  userId: string | null
+  fromSelf: boolean
 }
 
-function Message({ message, userId }: MessageProps) {
-  function hue(user?: User) {
-    return user ? getHueForUserId(user.id, userId) : 0
-  }
+function hue(user?: User) {
+  return user ? getHueForUserId(user.id) : 0
+}
 
-  function getMessageStyle(user?: User) {
-    return {
-      borderColor: `hsl(${hue(user)}, 85%, 40%)`,
-      backgroundColor: `hsl(${hue(user)}, 85%, 40%`,
-    }
+function getMessageStyle(user?: User) {
+  return {
+    borderColor: `hsl(${hue(user)}, 85%, 40%)`,
+    backgroundColor: `hsl(${hue(user)}, 85%, 40%`,
   }
+}
 
-  function getArrowStyle(user?: User) {
-    return {
-      borderColor: `hsl(${hue(user)}, 85%, 40%)`,
-    }
+function getArrowStyle(user?: User) {
+  return {
+    borderColor: `hsl(${hue(user)}, 85%, 40%)`,
   }
+}
 
-  const isMessageFromSelf = message.user ? message.user.id === userId : false
-
+function Message({ message, fromSelf }: MessageProps) {
   return (
     <div className="message-wrapper">
-      {!isMessageFromSelf && message.user.id && (
+      {!fromSelf && (
         <div className="name">
           <span>{message.user.first_name || message.user.email}</span>
         </div>
       )}
       <div className="message" style={getMessageStyle(message.user)}>
-        {!isMessageFromSelf && (
+        {!fromSelf && (
           <div className="arrow" style={getArrowStyle(message.user)} />
         )}
         <div className="message-content">

+ 2 - 2
services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.jsx

@@ -10,7 +10,7 @@ import {
   DropdownToggle,
 } from '@/features/ui/components/bootstrap-5/dropdown-menu'
 import Icon from '../../../shared/components/icon'
-import { getHueForUserId } from '../../../shared/utils/colors'
+import { getBackgroundColorForUserId } from '@/shared/utils/colors'
 import ControlledDropdown from '../../../shared/components/controlled-dropdown'
 import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
 import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
@@ -107,7 +107,7 @@ OnlineUsersWidget.propTypes = {
 }
 
 function UserIcon({ user, showName, onClick }) {
-  const backgroundColor = `hsl(${getHueForUserId(user.user_id)}, 70%, 50%)`
+  const backgroundColor = getBackgroundColorForUserId(user.user_id)
 
   function handleOnClick() {
     onClick?.(user)

+ 2 - 2
services/web/frontend/js/features/history/components/change-list/metadata-users-list.tsx

@@ -1,7 +1,7 @@
 import { useTranslation } from 'react-i18next'
-import { getUserColor } from '../../utils/history-details'
 import { LoadedUpdate } from '../../services/types/update'
 import UserNameWithColoredBadge from './user-name-with-colored-badge'
+import { getBackgroundColorForUserId } from '@/shared/utils/colors'
 
 type MetadataUsersListProps = {
   currentUserId: string
@@ -28,7 +28,7 @@ function MetadataUsersList({
         <li>
           <span
             className="history-version-user-badge-color"
-            style={{ backgroundColor: getUserColor() }}
+            style={{ backgroundColor: getBackgroundColorForUserId() }}
           />
           {origin?.kind === 'history-resync' ||
           origin?.kind === 'history-migration'

+ 3 - 2
services/web/frontend/js/features/history/components/change-list/user-name-with-colored-badge.tsx

@@ -1,7 +1,8 @@
 import { useTranslation } from 'react-i18next'
-import { formatUserName, getUserColor } from '../../utils/history-details'
+import { formatUserName } from '../../utils/history-details'
 import { User } from '../../services/types/shared'
 import { Nullable } from '../../../../../../types/utils'
+import { getBackgroundColorForUserId } from '@/shared/utils/colors'
 
 type UserNameWithColoredBadgeProps = {
   currentUserId: string
@@ -29,7 +30,7 @@ function UserNameWithColoredBadge({
     <>
       <span
         className="history-version-user-badge-color"
-        style={{ backgroundColor: getUserColor(user) }}
+        style={{ backgroundColor: getBackgroundColorForUserId(user?.id) }}
       />
       <span className="history-version-user-badge-text">{userName}</span>
     </>

+ 2 - 2
services/web/frontend/js/features/history/context/history-context.tsx

@@ -17,7 +17,6 @@ import { isFileRenamed } from '../utils/file-diff'
 import { loadLabels } from '../utils/label'
 import { autoSelectFile } from '../utils/auto-select-file'
 import usePersistedState from '../../../shared/hooks/use-persisted-state'
-import ColorManager from '../../../ide/colors/ColorManager'
 import moment from 'moment'
 import { cloneDeep } from 'lodash'
 import {
@@ -28,6 +27,7 @@ import {
 import { Selection } from '../services/types/selection'
 import { useErrorHandler } from 'react-error-boundary'
 import { getUpdateForVersion } from '../utils/history-details'
+import { getHueForUserId } from '@/shared/utils/colors'
 
 // Allow testing of infinite scrolling by providing query string parameters to
 // limit the number of updates returned in a batch and apply a delay
@@ -120,7 +120,7 @@ function useHistory() {
       for (const [index, update] of loadedUpdates.entries()) {
         for (const user of update.meta.users) {
           if (user) {
-            user.hue = ColorManager.getHueForUserId(user.id)
+            user.hue = getHueForUserId(user.id)
           }
         }
         if (

+ 3 - 3
services/web/frontend/js/features/history/utils/highlights-from-diff-response.ts

@@ -1,8 +1,8 @@
 import moment from 'moment/moment'
-import ColorManager from '../../../ide/colors/ColorManager'
 import { DocDiffChunk, Highlight } from '../services/types/doc'
 import { TFunction } from 'i18next'
 import displayNameForUser from './display-name-for-user'
+import { getHueForUserId } from '@/shared/utils/colors'
 
 export function highlightsFromDiffResponse(
   chunks: DocDiffChunk[],
@@ -37,7 +37,7 @@ export function highlightsFromDiffResponse(
           // There doesn't seem to be a convenient way to make this translatable
           label: t('added_by_on', { name, date }),
           range,
-          hue: ColorManager.getHueForUserId(user?.id),
+          hue: getHueForUserId(user?.id),
         })
       } else if (isDeletion) {
         highlights.push({
@@ -45,7 +45,7 @@ export function highlightsFromDiffResponse(
           // There doesn't seem to be a convenient way to make this translatable
           label: t('deleted_by_on', { name, date }),
           range,
-          hue: ColorManager.getHueForUserId(user?.id),
+          hue: getHueForUserId(user?.id),
         })
       }
     }

+ 0 - 8
services/web/frontend/js/features/history/utils/history-details.ts

@@ -1,15 +1,7 @@
-import ColorManager from '../../../ide/colors/ColorManager'
-import { Nullable } from '../../../../../types/utils'
 import { User } from '../services/types/shared'
 import { LoadedUpdate, ProjectOp, Version } from '../services/types/update'
 import { Selection } from '../services/types/selection'
 
-export const getUserColor = (user?: Nullable<{ id: string }>) => {
-  const hue = ColorManager.getHueForUserId(user?.id) || 100
-
-  return `hsl(${hue}, 70%, 50%)`
-}
-
 export const formatUserName = (user: User) => {
   let name = [user.first_name, user.last_name]
     .filter(n => n != null)

+ 2 - 2
services/web/frontend/js/features/ide-react/context/online-users-context.tsx

@@ -11,7 +11,6 @@ import { ReactScopeValueStore } from '@/features/ide-react/scope-value-store/rea
 import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
 import { useConnectionContext } from '@/features/ide-react/context/connection-context'
 import useScopeValue from '@/shared/hooks/use-scope-value'
-import ColorManager from '@/ide/colors/ColorManager'
 import { CursorPosition } from '@/features/ide-react/types/cursor-position'
 import { omit } from 'lodash'
 import { Doc } from '../../../../../types/doc'
@@ -20,6 +19,7 @@ import { findDocEntityById } from '@/features/ide-react/util/find-doc-entity-by-
 import useSocketListener from '@/features/ide-react/hooks/use-socket-listener'
 import { debugConsole } from '@/utils/debugging'
 import { IdeEvents } from '@/features/ide-react/create-ide-event-emitter'
+import { getHueForUserId } from '@/shared/utils/colors'
 
 type OnlineUser = {
   id: string
@@ -139,7 +139,7 @@ export const OnlineUsersProvider: FC = ({ children }) => {
             row: user.row,
             column: user.column,
           },
-          hue: ColorManager.getHueForUserId(user.user_id),
+          hue: getHueForUserId(user.user_id),
         })
       }
 

+ 3 - 3
services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts

@@ -25,7 +25,6 @@ import {
 } from '@/features/ide-react/context/editor-manager-context'
 import { debugConsole } from '@/utils/debugging'
 import { deleteJSON, getJSON, postJSON } from '@/infrastructure/fetch-json'
-import ColorManager from '@/ide/colors/ColorManager'
 import RangesTracker from '@overleaf/ranges-tracker'
 import type * as ReviewPanel from '@/features/source-editor/context/review-panel/types/review-panel-state'
 import {
@@ -66,6 +65,7 @@ import {
 import { RangesTrackerWithResolvedThreadIds } from '@/features/ide-react/editor/document-container'
 import getMeta from '@/utils/meta'
 import { useEditorContext } from '@/shared/context/editor-context'
+import { getHueForUserId } from '@/shared/utils/colors'
 
 const dispatchReviewPanelEvent = (type: string, payload?: any) => {
   window.dispatchEvent(
@@ -86,7 +86,7 @@ const formatUser = (user: any): any => {
       email: null,
       name: 'Anonymous',
       isSelf: false,
-      hue: ColorManager.ANONYMOUS_HUE,
+      hue: getHueForUserId(),
       avatar_text: 'A',
     }
   }
@@ -108,7 +108,7 @@ const formatUser = (user: any): any => {
     email: user.email,
     name,
     isSelf,
-    hue: ColorManager.getHueForUserId(id),
+    hue: getHueForUserId(id),
     avatar_text: [user.first_name, user.last_name]
       .filter(n => n != null)
       .map(n => n[0])

+ 2 - 2
services/web/frontend/js/features/project-list/util/tag.ts

@@ -1,5 +1,5 @@
 import { Tag } from '../../../../../app/src/Features/Tags/types'
-import ColorManager from '../../../ide/colors/ColorManager'
+import { getHueForId } from '@/shared/utils/colors'
 
 export const MAX_TAG_LENGTH = 50
 
@@ -7,5 +7,5 @@ export function getTagColor(tag?: Tag): string | undefined {
   if (!tag) {
     return undefined
   }
-  return tag.color || `hsl(${ColorManager.getHueForTagId(tag._id)}, 70%, 45%)`
+  return tag.color || `hsl(${getHueForId(tag._id)}, 70%, 45%)`
 }

+ 4 - 3
services/web/frontend/js/features/review-panel-new/components/review-panel-entry-user.tsx

@@ -2,7 +2,7 @@ import { memo } from 'react'
 import { buildName } from '../utils/build-name'
 import { ReviewPanelUser } from '../../../../../types/review-panel/review-panel'
 import { ChangesUser } from '../context/changes-users-context'
-import ColorManager from '@/ide/colors/ColorManager'
+import { getBackgroundColorForUserId } from '@/shared/utils/colors'
 
 const ReviewPanelEntryUser = ({
   user,
@@ -10,13 +10,14 @@ const ReviewPanelEntryUser = ({
   user?: ReviewPanelUser | ChangesUser
 }) => {
   const userName = buildName(user)
-  const hue = ColorManager.getHueForUserId(user?.id) || 100
 
   return (
     <div className="review-panel-entry-user">
       <span
         className="review-panel-entry-user-color-badge"
-        style={{ backgroundColor: `hsl(${hue}, 70%, 50%)` }}
+        style={{
+          backgroundColor: getBackgroundColorForUserId(user?.id),
+        }}
       />
       {userName}
     </div>

+ 0 - 95
services/web/frontend/js/ide/colors/ColorManager.js

@@ -1,95 +0,0 @@
-/* eslint-disable
-    camelcase,
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-
-// NOTE: this file is being refactored over to frontend/js/shared/utils/colors.js
-
-import { generateMD5Hash } from './../../shared/utils/md5'
-import getMeta from '@/utils/meta'
-
-let ColorManager
-
-export default ColorManager = {
-  getColorScheme(hue, element) {
-    if (this.isDarkTheme(element)) {
-      return {
-        cursor: `hsl(${hue}, 70%, 50%)`,
-        labelBackgroundColor: `hsl(${hue}, 70%, 50%)`,
-        highlightBackgroundColor: `hsl(${hue}, 100%, 28%);`,
-        strikeThroughBackgroundColor: `hsl(${hue}, 100%, 20%);`,
-        strikeThroughForegroundColor: `hsl(${hue}, 100%, 60%);`,
-      }
-    } else {
-      return {
-        cursor: `hsl(${hue}, 70%, 50%)`,
-        labelBackgroundColor: `hsl(${hue}, 70%, 50%)`,
-        highlightBackgroundColor: `hsl(${hue}, 70%, 85%);`,
-        strikeThroughBackgroundColor: `hsl(${hue}, 70%, 95%);`,
-        strikeThroughForegroundColor: `hsl(${hue}, 70%, 40%);`,
-      }
-    }
-  },
-
-  isDarkTheme(element) {
-    const rgb = element.find('.ace_editor').css('background-color')
-    let [m, r, g, b] = Array.from(
-      rgb.match(/rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)/)
-    )
-    r = parseInt(r, 10)
-    g = parseInt(g, 10)
-    b = parseInt(b, 10)
-    return r + g + b < 3 * 128
-  },
-
-  ANONYMOUS_HUE: 100,
-  OWN_HUE: 200, // We will always appear as this color to ourselves
-  OWN_HUE_BLOCKED_SIZE: 20, // no other user should havea HUE in this range
-  TOTAL_HUES: 360, // actually 361, but 360 for legacy reasons
-  getHueForUserId(user_id) {
-    if (user_id == null || user_id === 'anonymous-user') {
-      return this.ANONYMOUS_HUE
-    }
-
-    if (getMeta('ol-user').id === user_id) {
-      return this.OWN_HUE
-    }
-
-    let hue = this.getHueForId(user_id)
-
-    // if `hue` is within `OWN_HUE_BLOCKED_SIZE` degrees of the personal hue
-    // (`OWN_HUE`), shift `hue` to the end of available hues by adding
-    if (
-      hue > this.OWN_HUE - this.OWN_HUE_BLOCKED_SIZE &&
-      hue < this.OWN_HUE + this.OWN_HUE_BLOCKED_SIZE
-    ) {
-      hue = hue - this.OWN_HUE // `hue` now at 0 +/- `OWN_HUE_BLOCKED_SIZE`
-      hue = hue + this.TOTAL_HUES - this.OWN_HUE_BLOCKED_SIZE
-    }
-
-    return hue
-  },
-
-  getHueForTagId(tag_id) {
-    return this.getHueForId(tag_id)
-  },
-
-  getHueForId(id) {
-    const hash = generateMD5Hash(id)
-    const hue =
-      parseInt(hash.toString().slice(0, 8), 16) %
-      (this.TOTAL_HUES - this.OWN_HUE_BLOCKED_SIZE * 2)
-    return hue
-  },
-}

+ 19 - 5
services/web/frontend/js/shared/utils/colors.ts

@@ -1,16 +1,17 @@
 import { generateMD5Hash } from './md5'
+import getMeta from '@/utils/meta'
 
 const ANONYMOUS_HUE = 100
 const OWN_HUE = 200 // We will always appear as this color to ourselves
 const OWN_HUE_BLOCKED_SIZE = 20 // no other user should have a HUE in this range
 const TOTAL_HUES = 360 // actually 361, but 360 for legacy reasons
 
-export function getHueForUserId(userId: string, currentUserId: string | null) {
+export function getHueForUserId(userId?: string): number {
   if (userId == null || userId === 'anonymous-user') {
     return ANONYMOUS_HUE
   }
 
-  if (currentUserId === userId) {
+  if (getMeta('ol-user_id') === userId) {
     return OWN_HUE
   }
 
@@ -29,10 +30,23 @@ export function getHueForUserId(userId: string, currentUserId: string | null) {
   return hue
 }
 
-function getHueForId(id: string) {
+export function getBackgroundColorForUserId(userId?: string) {
+  return `hsl(${getHueForUserId(userId)}, 70%, 50%)`
+}
+
+const cachedHues = new Map()
+
+export function getHueForId(id: string) {
+  if (cachedHues.has(id)) {
+    return cachedHues.get(id)
+  }
+
   const hash = generateMD5Hash(id)
+
   const hue =
-    parseInt(hash.toString().slice(0, 8), 16) %
-    (TOTAL_HUES - OWN_HUE_BLOCKED_SIZE * 2)
+    parseInt(hash.slice(0, 8), 16) % (TOTAL_HUES - OWN_HUE_BLOCKED_SIZE * 2)
+
+  cachedHues.set(id, hue)
+
   return hue
 }

+ 49 - 10
services/web/frontend/js/shared/utils/md5.js → services/web/frontend/js/shared/utils/md5.ts

@@ -8,9 +8,9 @@
  * See http://pajhome.org.uk/crypt/md5 for more info.
  */
 
-export function generateMD5Hash(inputString) {
+export function generateMD5Hash(inputString: string): string {
   const hc = '0123456789abcdef'
-  function rh(n) {
+  function rh(n: number) {
     let j
     let s = ''
     for (j = 0; j <= 3; j++)
@@ -18,30 +18,69 @@ export function generateMD5Hash(inputString) {
         hc.charAt((n >> (j * 8 + 4)) & 0x0f) + hc.charAt((n >> (j * 8)) & 0x0f)
     return s
   }
-  function ad(x, y) {
+  function ad(x: number, y: number) {
     const l = (x & 0xffff) + (y & 0xffff)
     const m = (x >> 16) + (y >> 16) + (l >> 16)
     return (m << 16) | (l & 0xffff)
   }
-  function rl(n, c) {
+  function rl(n: number, c: number) {
     return (n << c) | (n >>> (32 - c))
   }
-  function cm(q, a, b, x, s, t) {
+  function cm(
+    q: number,
+    a: number,
+    b: number,
+    x: number,
+    s: number,
+    t: number
+  ) {
     return ad(rl(ad(ad(a, q), ad(x, t)), s), b)
   }
-  function ff(a, b, c, d, x, s, t) {
+  function ff(
+    a: number,
+    b: number,
+    c: number,
+    d: number,
+    x: number,
+    s: number,
+    t: number
+  ) {
     return cm((b & c) | (~b & d), a, b, x, s, t)
   }
-  function gg(a, b, c, d, x, s, t) {
+  function gg(
+    a: number,
+    b: number,
+    c: number,
+    d: number,
+    x: number,
+    s: number,
+    t: number
+  ) {
     return cm((b & d) | (c & ~d), a, b, x, s, t)
   }
-  function hh(a, b, c, d, x, s, t) {
+  function hh(
+    a: number,
+    b: number,
+    c: number,
+    d: number,
+    x: number,
+    s: number,
+    t: number
+  ) {
     return cm(b ^ c ^ d, a, b, x, s, t)
   }
-  function ii(a, b, c, d, x, s, t) {
+  function ii(
+    a: number,
+    b: number,
+    c: number,
+    d: number,
+    x: number,
+    s: number,
+    t: number
+  ) {
     return cm(c ^ (b | ~d), a, b, x, s, t)
   }
-  function sb(x) {
+  function sb(x: string) {
     let i
     const nblk = ((x.length + 8) >> 6) + 1
     const blks = new Array(nblk * 16)

+ 9 - 1
services/web/frontend/stories/chat.stories.jsx

@@ -3,6 +3,7 @@ import useFetchMock from './hooks/use-fetch-mock'
 import { generateMessages } from './fixtures/chat-messages'
 import { ScopeDecorator } from './decorators/scope'
 import { bsVersionDecorator } from '../../.storybook/utils/with-bootstrap-switcher'
+import { UserProvider } from '@/shared/context/user-context'
 
 export const Conversation = args => {
   useFetchMock(fetchMock => {
@@ -48,5 +49,12 @@ export default {
   args: {
     resetUnreadMessages: () => {},
   },
-  decorators: [ScopeDecorator],
+  decorators: [
+    ScopeDecorator,
+    Story => (
+      <UserProvider>
+        <Story />
+      </UserProvider>
+    ),
+  ],
 }

+ 39 - 20
services/web/test/frontend/features/chat/components/message-list.test.jsx

@@ -4,6 +4,7 @@ import { screen, render, fireEvent } from '@testing-library/react'
 
 import MessageList from '../../../../../frontend/js/features/chat/components/message-list'
 import { stubMathJax, tearDownMathJaxStubs } from './stubs'
+import { UserProvider } from '@/shared/context/user-context'
 
 describe('<MessageList />', function () {
   const currentUser = {
@@ -37,13 +38,25 @@ describe('<MessageList />', function () {
     tearDownMathJaxStubs()
   })
 
+  let olUser
+  beforeEach(function () {
+    olUser = window.metaAttributesCache.get('ol-user')
+    window.metaAttributesCache.set('ol-user', currentUser)
+  })
+
+  afterEach(function () {
+    window.metaAttributesCache.set('ol-user', olUser)
+  })
+
   it('renders multiple messages', function () {
     render(
-      <MessageList
-        userId={currentUser.id}
-        messages={createMessages()}
-        resetUnreadMessages={() => {}}
-      />
+      <UserProvider>
+        <MessageList
+          userId={currentUser.id}
+          messages={createMessages()}
+          resetUnreadMessages={() => {}}
+        />
+      </UserProvider>
     )
 
     screen.getByText('a message')
@@ -56,11 +69,13 @@ describe('<MessageList />', function () {
     msgs[1].timestamp = new Date(2019, 6, 3, 4, 27).getTime()
 
     render(
-      <MessageList
-        userId={currentUser.id}
-        messages={msgs}
-        resetUnreadMessages={() => {}}
-      />
+      <UserProvider>
+        <MessageList
+          userId={currentUser.id}
+          messages={msgs}
+          resetUnreadMessages={() => {}}
+        />
+      </UserProvider>
     )
 
     screen.getByText('4:23 am Wed, 3rd Jul 19')
@@ -73,11 +88,13 @@ describe('<MessageList />', function () {
     msgs[1].timestamp = new Date(2019, 6, 3, 4, 31).getTime()
 
     render(
-      <MessageList
-        userId={currentUser.id}
-        messages={msgs}
-        resetUnreadMessages={() => {}}
-      />
+      <UserProvider>
+        <MessageList
+          userId={currentUser.id}
+          messages={msgs}
+          resetUnreadMessages={() => {}}
+        />
+      </UserProvider>
     )
 
     screen.getByText('4:23 am Wed, 3rd Jul 19')
@@ -87,11 +104,13 @@ describe('<MessageList />', function () {
   it('resets the number of unread messages after clicking on the input', function () {
     const resetUnreadMessages = sinon.stub()
     render(
-      <MessageList
-        userId={currentUser.id}
-        messages={createMessages()}
-        resetUnreadMessages={resetUnreadMessages}
-      />
+      <UserProvider>
+        <MessageList
+          userId={currentUser.id}
+          messages={createMessages()}
+          resetUnreadMessages={resetUnreadMessages}
+        />
+      </UserProvider>
     )
 
     fireEvent.click(screen.getByRole('list'))

+ 6 - 6
services/web/test/frontend/features/chat/components/message.test.jsx

@@ -26,7 +26,7 @@ describe('<Message />', function () {
       user: currentUser,
     }
 
-    render(<Message userId={currentUser.id} message={message} />)
+    render(<Message message={message} fromSelf />)
 
     screen.getByText('a message')
   })
@@ -37,7 +37,7 @@ describe('<Message />', function () {
       user: currentUser,
     }
 
-    render(<Message userId={currentUser.id} message={message} />)
+    render(<Message message={message} fromSelf />)
 
     screen.getByText('a message')
     screen.getByText('another message')
@@ -51,7 +51,7 @@ describe('<Message />', function () {
       user: currentUser,
     }
 
-    render(<Message userId={currentUser.id} message={message} />)
+    render(<Message message={message} fromSelf />)
 
     screen.getByRole('link', { name: 'https://overleaf.com' })
   })
@@ -63,7 +63,7 @@ describe('<Message />', function () {
     }
 
     it('does not render the user name nor the email', function () {
-      render(<Message userId={currentUser.id} message={message} />)
+      render(<Message message={message} fromSelf />)
 
       expect(screen.queryByText(currentUser.first_name)).to.not.exist
       expect(screen.queryByText(currentUser.email)).to.not.exist
@@ -82,7 +82,7 @@ describe('<Message />', function () {
     }
 
     it('should render the other user name', function () {
-      render(<Message userId={currentUser.id} message={message} />)
+      render(<Message message={message} />)
 
       screen.getByText(otherUser.first_name)
     })
@@ -96,7 +96,7 @@ describe('<Message />', function () {
         },
       }
 
-      render(<Message userId={currentUser.id} message={msg} />)
+      render(<Message message={msg} />)
 
       expect(screen.queryByText(otherUser.first_name)).to.not.exist
       screen.getByText(msg.user.email)

+ 14 - 5
services/web/test/frontend/features/project-list/helpers/render-with-context.tsx

@@ -6,6 +6,7 @@ import { ProjectListProvider } from '../../../../../frontend/js/features/project
 import { Project } from '../../../../../types/project/dashboard/api'
 import { projectsData } from '../fixtures/projects-data'
 import { SplitTestProvider } from '@/shared/context/split-test-context'
+import { UserProvider } from '@/shared/context/user-context'
 
 type Options = {
   projects?: Project[]
@@ -31,16 +32,24 @@ export function renderWithProjectListContext(
     body: [],
   })
 
+  window.metaAttributesCache.set('ol-user', {
+    id: 'fake_user',
+    first_name: 'fake_user_first_name',
+    email: 'fake@example.com',
+  })
+
   const ProjectListProviderWrapper = ({
     children,
   }: {
     children: React.ReactNode
   }) => (
-    <ProjectListProvider>
-      <SplitTestProvider>
-        <ColorPickerProvider>{children}</ColorPickerProvider>
-      </SplitTestProvider>
-    </ProjectListProvider>
+    <UserProvider>
+      <ProjectListProvider>
+        <SplitTestProvider>
+          <ColorPickerProvider>{children}</ColorPickerProvider>
+        </SplitTestProvider>
+      </ProjectListProvider>
+    </UserProvider>
   )
 
   return render(component, {

+ 10 - 8
services/web/test/frontend/shared/utils/colors.test.js

@@ -3,11 +3,15 @@ import { expect } from 'chai'
 import { getHueForUserId } from '@/shared/utils/colors'
 
 describe('colors', function () {
-  const currentUser = '5bf7dab7a18b0b7a1cf6738c'
+  const currentUserId = '5bf7dab7a18b0b7a1cf6738c'
+
+  beforeEach(function () {
+    window.metaAttributesCache.set('ol-user_id', currentUserId)
+  })
 
   describe('getHueForUserId', function () {
     it('returns the OWN_HUE for the current user', function () {
-      expect(getHueForUserId(currentUser, currentUser)).to.equal(200)
+      expect(getHueForUserId(currentUserId)).to.equal(200)
     })
 
     it('returns the ANONYMOUS_HUE for an anonymous user', function () {
@@ -16,18 +20,16 @@ describe('colors', function () {
     })
 
     it('generates a hue based on user id', function () {
-      expect(getHueForUserId('59ad79f46337430b3d37cb9e', currentUser)).to.equal(
-        146
-      )
+      expect(
+        getHueForUserId('59ad79f46337430b3d37cb9e', currentUserId)
+      ).to.equal(146)
     })
 
     it('shifts the hue away from the OWN_HUE if it is within a threshold', function () {
       // Ordinarily, this user id would generate a hue of 183. However, this is
       // visually "too close" to the OWN_HUE, meaning that it could be
       // misinterpreted. Therefore we shift it away
-      expect(getHueForUserId('20ad79f46337430b3d37cb9f', currentUser)).to.equal(
-        323
-      )
+      expect(getHueForUserId('20ad79f46337430b3d37cb9f')).to.equal(323)
     })
   })
 })

+ 1 - 1
services/web/test/frontend/shared/utils/md5.test.js

@@ -1,6 +1,6 @@
 import { expect } from 'chai'
 
-import { generateMD5Hash } from '../../../../frontend/js/shared/utils/md5'
+import { generateMD5Hash } from '@/shared/utils/md5'
 
 describe('md5', function () {
   describe('generateSHA1Hash', function () {