127 lines
3.9 KiB
TypeScript
127 lines
3.9 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
$composerSuggestionsBySession,
|
|
type ComposerSuggestion,
|
|
markSuggestionInvoked,
|
|
offerSuggestions,
|
|
suggestionKey
|
|
} from './composer-suggestions'
|
|
|
|
const suggestion = (id: string, provider = 'test'): ComposerSuggestion => ({
|
|
doneLabel: 'done',
|
|
doneTip: 'done',
|
|
id,
|
|
invoke: async () => {},
|
|
label: id,
|
|
provider,
|
|
tip: 'because',
|
|
workingLabel: 'working',
|
|
workingTip: 'working'
|
|
})
|
|
|
|
const pillsFor = (sessionId: string) => ($composerSuggestionsBySession.get()[sessionId] ?? []).map(s => s.id)
|
|
|
|
describe('composer suggestion bus', () => {
|
|
it('publishes event offerings per session and withdraws on empty offer', () => {
|
|
offerSuggestions('s1', 'test', [suggestion('a')])
|
|
|
|
expect(pillsFor('s1')).toEqual(['a'])
|
|
expect(pillsFor('s2')).toEqual([])
|
|
|
|
offerSuggestions('s1', 'test', [])
|
|
|
|
expect(pillsFor('s1')).toEqual([])
|
|
})
|
|
|
|
it('caps merged suggestions at two', () => {
|
|
offerSuggestions('s3', 'test', [suggestion('a'), suggestion('b'), suggestion('c')])
|
|
|
|
expect(pillsFor('s3')).toHaveLength(2)
|
|
|
|
offerSuggestions('s3', 'test', [])
|
|
})
|
|
|
|
it('dedupes by provider-namespaced key across providers', () => {
|
|
offerSuggestions('s4', 'p1', [suggestion('same', 'p1')])
|
|
offerSuggestions('s4', 'p2', [suggestion('same', 'p2')])
|
|
|
|
// Different providers, same id — distinct keys, both allowed.
|
|
expect(pillsFor('s4')).toEqual(['same', 'same'])
|
|
|
|
offerSuggestions('s4', 'p1', [])
|
|
offerSuggestions('s4', 'p2', [])
|
|
})
|
|
|
|
it('quiets a suggestion after it is repeatedly withdrawn uninvoked', () => {
|
|
// Three offer/withdraw cycles = three strikes.
|
|
for (let i = 0; i < 3; i += 1) {
|
|
offerSuggestions('s5', 'test', [suggestion('naggy')])
|
|
offerSuggestions('s5', 'test', [])
|
|
}
|
|
|
|
offerSuggestions('s5', 'test', [suggestion('naggy')])
|
|
|
|
expect(pillsFor('s5')).toEqual([])
|
|
|
|
offerSuggestions('s5', 'test', [])
|
|
})
|
|
|
|
it('an invoked suggestion never accrues strikes', () => {
|
|
for (let i = 0; i < 3; i += 1) {
|
|
offerSuggestions('s6', 'test', [suggestion('used')])
|
|
markSuggestionInvoked('s6', suggestionKey(suggestion('used')))
|
|
offerSuggestions('s6', 'test', [])
|
|
}
|
|
|
|
offerSuggestions('s6', 'test', [suggestion('used')])
|
|
|
|
expect(pillsFor('s6')).toEqual(['used'])
|
|
|
|
offerSuggestions('s6', 'test', [])
|
|
})
|
|
|
|
it('replaces an offer whose rendered copy changed under the same key', () => {
|
|
offerSuggestions('s7', 'test', [{ ...suggestion('linear'), tip: 'because you mentioned “linear”' }])
|
|
offerSuggestions('s7', 'test', [{ ...suggestion('linear'), tip: 'because you pasted linear.app' }])
|
|
|
|
// Same key, new trigger — the strip must paint the new reason, not the
|
|
// first one it ever saw.
|
|
expect(($composerSuggestionsBySession.get().s7 ?? []).map(s => s.tip)).toEqual(['because you pasted linear.app'])
|
|
|
|
offerSuggestions('s7', 'test', [])
|
|
})
|
|
|
|
it('re-offering the same key swaps in the fresh invoke closure', async () => {
|
|
const calls: string[] = []
|
|
|
|
const offer = (tag: string) =>
|
|
offerSuggestions('s8', 'test', [
|
|
{ ...suggestion('linear'), invoke: async () => void calls.push(tag), label: `Add linear ${tag}` }
|
|
])
|
|
|
|
offer('first')
|
|
offer('second')
|
|
|
|
await ($composerSuggestionsBySession.get().s8 ?? [])[0]!.invoke({ cancelled: () => false, sessionId: 's8' })
|
|
|
|
// A pinned first object means the pill runs work built for a draft the
|
|
// user has since changed.
|
|
expect(calls).toEqual(['second'])
|
|
|
|
offerSuggestions('s8', 'test', [])
|
|
})
|
|
|
|
it('keeps the array reference when nothing the pill paints changed', () => {
|
|
offerSuggestions('s9', 'test', [suggestion('linear')])
|
|
|
|
const first = $composerSuggestionsBySession.get().s9
|
|
|
|
offerSuggestions('s9', 'test', [suggestion('linear')])
|
|
|
|
expect($composerSuggestionsBySession.get().s9).toBe(first)
|
|
|
|
offerSuggestions('s9', 'test', [])
|
|
})
|
|
})
|