feat(release): stage GitWorkshop and next node updates
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import IdentitySuccessPane from '../IdentitySuccessPane.vue'
|
||||
|
||||
describe('IdentitySuccessPane', () => {
|
||||
it('shows the saved identity, relay coverage, event id, and identity animation', () => {
|
||||
const wrapper = mount(IdentitySuccessPane, {
|
||||
props: {
|
||||
identityName: 'Alice', eventId: 'abc123', accepted: 2, attempted: 3,
|
||||
relayNote: 'One relay rejected the event.',
|
||||
},
|
||||
})
|
||||
expect(wrapper.text()).toContain('IDENTITY UPDATED')
|
||||
expect(wrapper.text()).toContain('Alice is updated on this node.')
|
||||
expect(wrapper.text()).toContain('Published to 2/3 configured relays.')
|
||||
expect(wrapper.text()).toContain('abc123')
|
||||
expect(wrapper.text()).toContain('One relay rejected')
|
||||
expect(wrapper.find('.nostr-orb-success').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('reports a local-only save without claiming relay publication', () => {
|
||||
const wrapper = mount(IdentitySuccessPane, {
|
||||
props: { identityName: 'Alice', accepted: 0, attempted: 0 },
|
||||
})
|
||||
expect(wrapper.text()).toContain('Saved locally; no relay publication was attempted.')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,35 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import NostrSignConsent from '../NostrSignConsent.vue'
|
||||
|
||||
const baseProps = {
|
||||
show: true,
|
||||
appName: 'GitWorkshop',
|
||||
method: 'signEvent',
|
||||
identityLabel: 'Personal',
|
||||
eventKind: 1621,
|
||||
}
|
||||
|
||||
describe('NostrSignConsent', () => {
|
||||
it('renders as a contained absolute overlay, not a body teleport', () => {
|
||||
const wrapper = mount(NostrSignConsent, { props: baseProps })
|
||||
expect(wrapper.findComponent({ name: 'Teleport' }).exists()).toBe(false)
|
||||
expect(wrapper.find('.absolute.inset-0').exists()).toBe(true)
|
||||
expect(wrapper.text()).toContain('Approve this request?')
|
||||
expect(wrapper.text()).toContain('Git issue')
|
||||
})
|
||||
|
||||
it('uses the shared identity animation while the node signs', () => {
|
||||
const wrapper = mount(NostrSignConsent, { props: { ...baseProps, phase: 'signing' } })
|
||||
expect(wrapper.text()).toContain('Signing locally…')
|
||||
expect(wrapper.find('.nostr-orb-loading').exists()).toBe(true)
|
||||
expect(wrapper.findAll('.nostr-orb-segment')).toHaveLength(48)
|
||||
expect(wrapper.text()).not.toContain('Approve')
|
||||
})
|
||||
|
||||
it('shows a short in-app completion state', () => {
|
||||
const wrapper = mount(NostrSignConsent, { props: { ...baseProps, phase: 'success' } })
|
||||
expect(wrapper.text()).toContain('Request signed')
|
||||
expect(wrapper.find('.nostr-orb-success').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -69,8 +69,8 @@ describe('paid tick renders the branded ring (FED-06)', () => {
|
||||
const rings = wrapper.findAllComponents(ScreensaverRing)
|
||||
expect(rings).toHaveLength(1)
|
||||
expect(rings[0]?.props('size')).toBe('badge')
|
||||
expect(document.querySelector('.scan-success-core')).not.toBeNull()
|
||||
expect(document.querySelector('svg path[d="M5 13l4 4L19 7"]')).not.toBeNull()
|
||||
expect(document.querySelector('.burst-core')).not.toBeNull()
|
||||
expect(document.querySelector('.burst-check')).not.toBeNull()
|
||||
// The plain fixed-size circle the ring replaced is gone.
|
||||
expect(document.querySelectorAll('.success-ring')).toHaveLength(0)
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import SendBitcoinModal from '../SendBitcoinModal.vue'
|
||||
import ReceiveBitcoinModal from '../ReceiveBitcoinModal.vue'
|
||||
import i18n from '@/i18n'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
|
||||
vi.mock('@/api/rpc-client', () => ({
|
||||
rpcClient: {
|
||||
call: vi.fn(),
|
||||
payLightningInvoice: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('qrcode', () => ({
|
||||
toCanvas: vi.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useLightningRequired', () => ({
|
||||
useLightningRequired: () => ({
|
||||
requireLightningReady: vi.fn().mockResolvedValue(true),
|
||||
handleLightningFailure: vi.fn().mockReturnValue(false),
|
||||
}),
|
||||
}))
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
describe('payment completion receipts', () => {
|
||||
it('replaces the Cashu send form and keeps the token copyable', async () => {
|
||||
vi.mocked(rpcClient.call).mockResolvedValue({ token: 'cashuB-test-token' } as never)
|
||||
const wrapper = mount(SendBitcoinModal, {
|
||||
props: { show: true },
|
||||
attachTo: document.body,
|
||||
global: { plugins: [i18n] },
|
||||
})
|
||||
const vm = wrapper.vm as unknown as {
|
||||
sendMethod: string
|
||||
amountEntry: number
|
||||
send: () => Promise<void>
|
||||
}
|
||||
vm.sendMethod = 'ecash'
|
||||
vm.amountEntry = 2100
|
||||
|
||||
await vm.send()
|
||||
await flushPromises()
|
||||
|
||||
expect(document.body.textContent).toContain('2,100')
|
||||
expect(document.body.textContent).toContain('SENT')
|
||||
expect(document.body.textContent).toContain('cashuB-test-token')
|
||||
expect(document.body.textContent).toContain('Token to share')
|
||||
expect(document.body.querySelector('textarea')).toBeNull()
|
||||
expect(document.body.querySelector('input[type="number"]')).toBeNull()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('replaces the ecash receive form and preserves the redeemed token and mint', async () => {
|
||||
vi.mocked(rpcClient.call).mockResolvedValue({
|
||||
received_sats: 42,
|
||||
kind: 'cashu',
|
||||
mint_url: 'https://mint.example',
|
||||
} as never)
|
||||
const wrapper = mount(ReceiveBitcoinModal, {
|
||||
props: { show: true },
|
||||
attachTo: document.body,
|
||||
global: { plugins: [i18n] },
|
||||
})
|
||||
const vm = wrapper.vm as unknown as {
|
||||
receiveMethod: string
|
||||
ecashToken: string
|
||||
receive: () => Promise<void>
|
||||
}
|
||||
vm.receiveMethod = 'ecash'
|
||||
vm.ecashToken = 'cashuB-redeemed-token'
|
||||
|
||||
await vm.receive()
|
||||
await flushPromises()
|
||||
|
||||
expect(document.body.textContent).toContain('42')
|
||||
expect(document.body.textContent).toContain('RECEIVED')
|
||||
expect(document.body.textContent).toContain('https://mint.example')
|
||||
expect(document.body.textContent).toContain('cashuB-redeemed-token')
|
||||
expect(document.body.querySelector('textarea')).toBeNull()
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import ReceiveBitcoinModal from '../ReceiveBitcoinModal.vue'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
|
||||
@@ -27,6 +27,12 @@ vi.mock('@/composables/useLightningRequired', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useRealTimers()
|
||||
vi.clearAllMocks()
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
// Guards an operator report (2026-09-08): clicking the Ecash tab appeared to
|
||||
// close the whole Receive modal. Not reproduced here — the tab switch alone
|
||||
// (success or failure of wallet.ecash-lnaddress) never emits `close` or
|
||||
@@ -108,4 +114,71 @@ describe('ReceiveBitcoinModal — ecash tab click', () => {
|
||||
wrapper.unmount()
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('shows a recent receipt claimed by another active browser context', async () => {
|
||||
vi.mocked(rpcClient.call).mockImplementation(async ({ method }: { method: string }) => {
|
||||
if (method === 'wallet.ecash-lnaddress') {
|
||||
return { address: 'someone@minibits.cash' } as never
|
||||
}
|
||||
if (method === 'wallet.ecash-lnaddress-claim') {
|
||||
return {
|
||||
received_sats: 0,
|
||||
failed_count: 0,
|
||||
receipt_id: 7,
|
||||
receipt_sats: 1000,
|
||||
receipt_at: Math.floor(Date.now() / 1000),
|
||||
} as never
|
||||
}
|
||||
return {} as never
|
||||
})
|
||||
|
||||
const wrapper = mount(ReceiveBitcoinModal, {
|
||||
props: { show: true },
|
||||
attachTo: document.body,
|
||||
})
|
||||
const ecashTab = Array.from(document.body.querySelectorAll('button')).find((b) =>
|
||||
b.textContent?.toLowerCase().includes('ecash'),
|
||||
)
|
||||
ecashTab!.dispatchEvent(new Event('click', { bubbles: true }))
|
||||
await flushPromises()
|
||||
|
||||
expect(document.body.textContent).toContain('1,000')
|
||||
expect(document.body.textContent).toContain('RECEIVED')
|
||||
expect(document.body.querySelector('textarea')).toBeNull()
|
||||
expect(document.body.querySelectorAll('[role="dialog"]')).toHaveLength(1)
|
||||
expect(document.body.querySelector('[role="dialog"] h3')?.textContent).toBe('Payment received')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('does not replay an older durable receipt when the receive screen is reopened', async () => {
|
||||
vi.mocked(rpcClient.call).mockImplementation(async ({ method }: { method: string }) => {
|
||||
if (method === 'wallet.ecash-lnaddress') {
|
||||
return { address: 'someone@minibits.cash' } as never
|
||||
}
|
||||
if (method === 'wallet.ecash-lnaddress-claim') {
|
||||
return {
|
||||
received_sats: 0,
|
||||
failed_count: 0,
|
||||
receipt_id: 7,
|
||||
receipt_sats: 1000,
|
||||
receipt_at: Math.floor(Date.now() / 1000) - 30,
|
||||
} as never
|
||||
}
|
||||
return {} as never
|
||||
})
|
||||
|
||||
const wrapper = mount(ReceiveBitcoinModal, {
|
||||
props: { show: true },
|
||||
attachTo: document.body,
|
||||
})
|
||||
const ecashTab = Array.from(document.body.querySelectorAll('button')).find((b) =>
|
||||
b.textContent?.toLowerCase().includes('ecash'),
|
||||
)
|
||||
ecashTab!.dispatchEvent(new Event('click', { bubbles: true }))
|
||||
await flushPromises()
|
||||
|
||||
expect(document.body.querySelector('[role="dialog"] h3')?.textContent).not.toBe('Payment received')
|
||||
expect(document.body.querySelector('textarea')).not.toBeNull()
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user