Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion src/renderer/src/components/sidebar/SidebarToolbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { act, type ReactNode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { AppState } from '@/store'
import { i18n } from '@/i18n/i18n'
import ko from '@/i18n/locales/ko.json'
import SidebarToolbar from './SidebarToolbar'

const mocks = vi.hoisted(() => ({
Expand Down Expand Up @@ -82,12 +84,15 @@ describe('SidebarToolbar moved workspace board hint', () => {
}
})

afterEach(() => {
afterEach(async () => {
roots.splice(0).forEach((root) => {
act(() => root.unmount())
})
document.body.replaceChildren()
vi.clearAllMocks()
// Why: the language-change case mutates the shared i18n instance; restore
// here so a failing assertion cannot leave later tests running in Korean.
await i18n.changeLanguage('en')
})

it('does not show the moved hint to brand-new users after their first board click', async () => {
Expand Down Expand Up @@ -131,6 +136,26 @@ describe('SidebarToolbar moved workspace board hint', () => {
expect(window.localStorage.getItem('orca.workspaceBoardMovedHintSeen.v1')).toBe('true')
})

// Why: the toolbar is a React.memo boundary whose props never change on a
// language switch, so without its own useTranslation() subscription it keeps
// the English copy it rendered at boot (the persisted locale is applied
// asynchronously, after the lazy catalog loads).
it('relabels itself when the UI language changes after mount', async () => {
const { container } = await renderToolbar()
expect(container.querySelector('button[aria-label="Workspace board"]')).not.toBeNull()

await act(async () => {
await i18n.changeLanguage('ko')
})

// Why: read the expected copy from the catalog instead of hardcoding it, so
// editing the Korean wording cannot fail this test as a bogus stale-render
// report.
const localized = ko.auto.components.sidebar.SidebarToolbar['49f62c5665']
expect(localized).not.toBe('Workspace board')
expect(container.querySelector(`button[aria-label="${localized}"]`)).not.toBeNull()
})

it('renders the profile switcher before settings in the footer controls', async () => {
const { container } = await renderToolbar()
const html = container.innerHTML
Expand Down
7 changes: 7 additions & 0 deletions src/renderer/src/components/sidebar/SidebarToolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import { Kanban } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { Button } from '@/components/ui/button'
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
import { ScrollToCurrentWorkspaceToolbarButton } from './ScrollToCurrentWorkspaceToolbarButton'
Expand All @@ -23,6 +24,12 @@ const SidebarToolbar = React.memo(function SidebarToolbar({
workspaceBoardDragPreviewOpen = false,
onWorkspaceBoardToggle
}: SidebarToolbarProps) {
// Why: this memo boundary needs its own language subscription, while
// translate() preserves Orca's pseudo-localization behavior. Without it the
// toolbar (and the ScrollToCurrentWorkspaceToolbarButton it renders) keeps
// whatever language was active at boot — English, since the persisted locale
// is applied asynchronously after the lazy catalog loads.
useTranslation()
const [workspaceBoardMovedHintOpen, setWorkspaceBoardMovedHintOpen] = React.useState(false)
const movedHintEligibleRef = React.useRef<boolean | null>(null)
const persistedUIReady = useAppStore((state) => state.persistedUIReady)
Expand Down
Loading