Skip to content
Open
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
13 changes: 11 additions & 2 deletions src/widgets/buttons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,7 @@ export type attachmentListOptions = {
noun?: string
renderSupportingInfo?: RenderSupportingInfo
renderNameSuffix?: RenderNameSuffix
refreshOnDocumentLoad?: boolean
}

/**
Expand All @@ -892,6 +893,11 @@ export function attachmentList (dom: HTMLDocument, subject: NamedNode, div: HTML
// options = options || {}
const docsWaitingForRowRefresh = new Set<string>()
const hasAsyncEnrichedRowOptions = !!(options.renderSupportingInfo || options.renderNameSuffix)
// Keep the generic default on for simple consumers: if row decoration depends on
// linked-profile data arriving later, attachmentList will rerender once that
// target document finishes loading. Complex callers with their own streaming or
// batched refresh pipeline can opt out to avoid duplicate whole-list refreshes.
const refreshOnDocumentLoad = options.refreshOnDocumentLoad ?? true
Comment thread
SharonStrats marked this conversation as resolved.

const deleteAttachment = function (target) {
if (!kb.updater) {
Expand All @@ -918,13 +924,16 @@ export function attachmentList (dom: HTMLDocument, subject: NamedNode, div: HTML
opt.renderSupportingInfo = options.renderSupportingInfo
opt.renderNameSuffix = options.renderNameSuffix

if (hasAsyncEnrichedRowOptions && target?.uri && kb.fetcher) {
if (hasAsyncEnrichedRowOptions && refreshOnDocumentLoad && target?.uri && kb.fetcher) {
const targetDoc = target.doc()
const requestState = targetDoc?.uri ? kb.fetcher.requested?.[targetDoc.uri] : undefined
const shouldWaitForFetch = requestState !== 'done' && requestState !== 'failed'
if (targetDoc?.uri && shouldWaitForFetch && !docsWaitingForRowRefresh.has(targetDoc.uri)) {
docsWaitingForRowRefresh.add(targetDoc.uri)
// Root fix: these row options can depend on async profile data, so rerender once fetch completes.
// The row renderer may need data from the target profile that is not loaded yet.
// Register one follow-up refresh per target document so simple attachmentList
// consumers eventually show the enriched row contents without building their own
// async refresh orchestration.
kb.fetcher.nowOrWhenFetched(targetDoc, undefined, () => {
docsWaitingForRowRefresh.delete(targetDoc.uri)
refresh()
Expand Down
69 changes: 69 additions & 0 deletions test/unit/widgets/buttons.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ describe('askName', () => {
})

describe('attachmentList', () => {
afterEach(() => {
clearStore()
jest.restoreAllMocks()
})

it('exists', () => {
expect(attachmentList).toBeInstanceOf(Function)
})
Expand All @@ -116,6 +121,70 @@ describe('attachmentList', () => {
const options = {}
expect(attachmentList(dom, subject, div, options)).toBeTruthy()
})

it('refreshes rows after pending profile fetches by default', () => {
const subject = sym('https://subject.example/profile/card#me')
const predicate = ns.foaf('knows')
const target = sym('https://friend.example/profile/card#me')
const div = element
const fetcher = store.fetcher as any

store.add(subject, predicate, target, subject.doc())
fetcher.requested = {
...fetcher.requested,
[target.doc().uri]: 'requested'
}

const nowOrWhenFetched = jest
.spyOn(fetcher, 'nowOrWhenFetched')
.mockImplementation(() => undefined as any)

attachmentList(dom, subject, div, {
predicate,
renderSupportingInfo: () => null
})

expect(nowOrWhenFetched).toHaveBeenCalledWith(
target.doc(),
undefined,
expect.any(Function)
)
})

it('adds one extra document-load refresh hook only when enabled', () => {
const subject = sym('https://subject.example/profile/card#me')
const predicate = ns.foaf('knows')
const target = sym('https://friend.example/profile/card#me')
const div = element
const fetcher = store.fetcher as any

store.add(subject, predicate, target, subject.doc())
fetcher.requested = {
...fetcher.requested,
[target.doc().uri]: 'requested'
}

const nowOrWhenFetched = jest
.spyOn(fetcher, 'nowOrWhenFetched')
.mockImplementation(() => undefined as any)

attachmentList(dom, subject, div, {
predicate,
refreshOnDocumentLoad: false,
renderSupportingInfo: () => null
})
const disabledCallCount = nowOrWhenFetched.mock.calls.length

nowOrWhenFetched.mockClear()
div.innerHTML = ''

attachmentList(dom, subject, div, {
predicate,
renderSupportingInfo: () => null
})

expect(nowOrWhenFetched.mock.calls.length).toBe(disabledCallCount + 1)
})
})

describe('button', () => {
Expand Down