diff --git a/src/renderer/settings/components/ProviderApiConfig.vue b/src/renderer/settings/components/ProviderApiConfig.vue index a29263063..3564a1ffa 100644 --- a/src/renderer/settings/components/ProviderApiConfig.vue +++ b/src/renderer/settings/components/ProviderApiConfig.vue @@ -122,9 +122,17 @@
- {{ maskedApiKey }} + {{ maskedApiKey }} +
!isEditingKey.value && Boolean(props.provider.apiKey?.trim())) const maskedApiKey = computed(() => { const key = props.provider.apiKey?.trim() ?? '' diff --git a/test/renderer/components/ProviderApiConfig.test.ts b/test/renderer/components/ProviderApiConfig.test.ts index 38f3760d5..a9842ecf0 100644 --- a/test/renderer/components/ProviderApiConfig.test.ts +++ b/test/renderer/components/ProviderApiConfig.test.ts @@ -41,6 +41,19 @@ const buttonStub = defineComponent({ template: '' }) +const copyButtonStub = defineComponent({ + name: 'CopyButton', + inheritAttrs: false, + emits: ['copied', 'error'], + props: { + copyText: { + type: String, + default: '' + } + }, + template: '' +}) + const labelStub = defineComponent({ name: 'Label', inheritAttrs: false, @@ -114,7 +127,8 @@ async function setup(options?: { Input: createInputStub() })) vi.doMock('@dc-ui/components/button', () => ({ - DcButton: buttonStub + DcButton: buttonStub, + DcCopyButton: copyButtonStub })) vi.doMock('@shadcn/components/ui/label', () => ({ Label: labelStub @@ -416,6 +430,25 @@ describe('ProviderApiConfig', () => { expect((input.element as HTMLInputElement).value).toBe('') }) + it('renders a hover-revealed copy button in the masked key summary', async () => { + const { wrapper } = await setup({ + provider: createProvider({ apiKey: 'sk-1234567890abcd' }) + }) + + const summary = wrapper.get('[data-testid="provider-api-key-summary"]') + const copyButton = wrapper.get('[data-testid="provider-copy-key-button"]') + + // The button lives inside the summary and stays hidden until hovered or focused. + expect(summary.find('[data-testid="provider-copy-key-button"]').exists()).toBe(true) + expect(copyButton.classes()).toContain('opacity-0') + expect(copyButton.classes()).toContain('pointer-events-none') + expect(copyButton.classes()).toContain('group-hover:opacity-100') + expect(copyButton.classes()).toContain('group-hover:pointer-events-auto') + expect(copyButton.classes()).toContain('focus-visible:opacity-100') + expect(copyButton.attributes('tooltip')).toBe('common.copy') + expect(wrapper.findComponent(copyButtonStub).props('copyText')).toBe('sk-1234567890abcd') + }) + it('keeps the stored key when the Update key editor is left empty', async () => { const { wrapper } = await setup({ provider: createProvider({ apiKey: 'sk-1234567890abcd' }) @@ -504,7 +537,10 @@ describe('ProviderApiConfig', () => { useModelCheckStore: () => ({ openDialog: vi.fn() }) })) vi.doMock('@shadcn/components/ui/input', () => ({ Input: createInputStub() })) - vi.doMock('@dc-ui/components/button', () => ({ DcButton: buttonStub })) + vi.doMock('@dc-ui/components/button', () => ({ + DcButton: buttonStub, + DcCopyButton: copyButtonStub + })) vi.doMock('@shadcn/components/ui/label', () => ({ Label: labelStub })) vi.doMock('@shadcn/components/ui/tooltip', () => ({ Tooltip: passthrough('Tooltip'),