Remove ColorManager (#22974)

GitOrigin-RevId: 32cb6fd599345eaf8e474553da5c6d3080575ee8
This commit is contained in:
Alf Eaton
2025-01-29 09:04:48 +00:00
committed by Copybot
parent cce8d12bf7
commit 71ab3a41ed
22 changed files with 196 additions and 209 deletions
@@ -94,7 +94,6 @@ const ChatPane = React.memo(function ChatPane() {
{shouldDisplayPlaceholder && <Placeholder />}
<MessageList
messages={messages}
userId={user.id}
resetUnreadMessages={markMessagesAsRead}
/>
</Suspense>
@@ -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>
@@ -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">
@@ -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)
@@ -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'
@@ -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>
</>
@@ -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 (
@@ -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),
})
}
}
@@ -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)
@@ -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),
})
}
@@ -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])
@@ -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%)`
}
@@ -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>
@@ -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
},
}
@@ -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
}
@@ -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)
@@ -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>
),
],
}
@@ -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'))
@@ -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)
@@ -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, {
@@ -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,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 () {