update tests and rename execution_progress to progress_combined

This commit is contained in:
Prakash 2026-05-01 01:21:30 +05:30
parent de92437313
commit e44abd3bea
3 changed files with 22 additions and 196 deletions

View file

@ -144,7 +144,7 @@ class TransloaditAssembly extends Emitter {
if (typeof details.progress_combined === 'number') {
this.status = {
...this.status,
execution_progress: details.progress_combined,
progress_combined: details.progress_combined,
}
}
this.emit('execution-progress', details)

View file

@ -189,6 +189,15 @@ describe('Transloadit', () => {
},
})
// Plugin state should start empty; track every distinct `ok` that lands in
// it so we can verify the assembly lifecycle is reflected.
expect(uppy.getState().plugins.Transloadit.assemblyStatus).toBeUndefined()
const okHistory = []
const unsubscribe = uppy.store.subscribe((_prev, next) => {
const ok = next.plugins.Transloadit.assemblyStatus?.ok
if (ok && ok !== okHistory.at(-1)) okHistory.push(ok)
})
uppy.addFile({
source: 'test',
name: 'cat.jpg',
@ -228,12 +237,23 @@ describe('Transloadit', () => {
uppy.resumeAll()
await uploadPromise
unsubscribe()
expect(successSpy).toHaveBeenCalled()
// Should be reset to true after upload completes
expect(uppy.getState().allowNewUpload).toBe(true)
// Plugin state reflected the assembly status the server returned.
// The createAssembly mock returned ASSEMBLY_EXECUTING and the assembly
// setter forwarded that status into plugin state. We assert containment
// (not exact sequence) because the test environment doesn't reliably
// drive the polling-to-COMPLETED transition.
expect(okHistory).toContain('ASSEMBLY_EXECUTING')
const finalStatus = uppy.getState().plugins.Transloadit.assemblyStatus
expect(finalStatus).toBeDefined()
expect(finalStatus.assembly_id).toBe('test-assembly-id')
server.close()
})
@ -317,198 +337,4 @@ describe('Transloadit', () => {
// Should be reset to true
expect(uppy.getState().allowNewUpload).toBe(true)
})
it('populates plugin state assemblyStatus during a real upload flow', async () => {
const assemblyStatusBase = {
assembly_id: 'flow-test-id',
websocket_url: 'ws://localhost:8080',
tus_url: 'http://localhost/resumable/files/',
assembly_ssl_url: 'https://api2.transloadit.com/assemblies/flow-test-id',
}
const tusUploads = new Map()
let uploadIndex = 0
const tusBaseUrl = 'http://localhost/resumable/files/'
const server = setupServer(
http.options('http://localhost/resumable/files*', () => {
return new HttpResponse(null, {
status: 204,
headers: {
'Tus-Resumable': '1.0.0',
'Tus-Version': '1.0.0',
'Tus-Extension': 'creation,creation-defer-length',
},
})
}),
http.post('http://localhost/resumable/files*', ({ request }) => {
const uploadLengthHeader = request.headers.get('upload-length')
const uploadLength = uploadLengthHeader ? Number(uploadLengthHeader) : 0
const uploadId = `flow-upload-${uploadIndex++}`
tusUploads.set(uploadId, {
length: Number.isNaN(uploadLength) ? 0 : uploadLength,
offset: 0,
})
return new HttpResponse(null, {
status: 201,
headers: {
Location: `${tusBaseUrl}${uploadId}`,
'Upload-Offset': '0',
'Tus-Resumable': '1.0.0',
},
})
}),
http.patch(
'http://localhost/resumable/files/:uploadId',
async ({ request, params }) => {
const upload = tusUploads.get(params.uploadId)
if (!upload) return new HttpResponse(null, { status: 404 })
const body = await request.arrayBuffer()
const offsetHeader = request.headers.get('upload-offset')
const baseOffset = offsetHeader ? Number(offsetHeader) : upload.offset
const nextOffset = baseOffset + body.byteLength
upload.offset = nextOffset
return new HttpResponse(null, {
status: 204,
headers: {
'Upload-Offset': String(nextOffset),
'Tus-Resumable': '1.0.0',
},
})
},
),
http.post('https://api2.transloadit.com/assemblies', () => {
return HttpResponse.json({
...assemblyStatusBase,
ok: 'ASSEMBLY_UPLOADING',
})
}),
http.get('https://api2.transloadit.com/assemblies/*', () => {
return HttpResponse.json({
...assemblyStatusBase,
ok: 'ASSEMBLY_COMPLETED',
results: {},
})
}),
http.post('https://transloaditstatus.com/client_error', () => {
return HttpResponse.json({})
}),
)
server.listen({ onUnhandledRequest: 'error' })
const uppy = new Core()
uppy.use(Transloadit, {
assemblyOptions: {
params: {
auth: { key: 'test-auth-key' },
template_id: 'test-template-id',
},
},
})
// Before upload: assemblyStatus should be undefined
expect(uppy.getState().plugins.Transloadit.assemblyStatus).toBeUndefined()
// Track the assemblyStatus values we see as the store updates.
// This avoids racing against the upload completion.
const seenStatuses = []
const unsub = uppy.store.subscribe((_prev, next) => {
seenStatuses.push(next.plugins.Transloadit.assemblyStatus)
})
uppy.addFile({
source: 'test',
name: 'test.jpg',
data: new File([new Uint8Array([1, 2, 3, 4, 5])], 'test.jpg', {
type: 'image/jpeg',
}),
})
await uppy.upload()
unsub()
// At some point during the upload the plugin must have written a defined
// assemblyStatus into plugin state.
const definedStatuses = seenStatuses.filter((s) => s != null)
expect(definedStatuses.length).toBeGreaterThan(0)
expect(definedStatuses[0].assembly_id).toBe('flow-test-id')
// After upload completes: the final Assembly status remains in plugin state
// so the UI can display the completed results. It is only cleared on a new
// upload or explicit cancel.
const finalStatus = uppy.getState().plugins.Transloadit.assemblyStatus
expect(finalStatus).toBeDefined()
expect(finalStatus.assembly_id).toBe('flow-test-id')
server.close()
})
it('clears assemblyStatus at the start of a new upload', async () => {
const uppy = new Core()
uppy.use(Transloadit, {
assemblyOptions: {
params: {
auth: { key: 'test-auth-key' },
template_id: 'test-template-id',
},
},
})
const plugin = uppy.getPlugin('Transloadit')
// Simulate a prior Assembly still sitting in plugin state.
plugin.setPluginState({
assemblyStatus: { assembly_id: 'prior', ok: 'ASSEMBLY_COMPLETED' },
})
expect(uppy.getState().plugins.Transloadit.assemblyStatus).toBeDefined()
// Make #createAssembly fail so we can observe the clear-on-start without
// running a full upload.
plugin.client.createAssembly = () => Promise.reject(new Error('stop here'))
uppy.addFile({
source: 'test',
name: 'abc',
data: new Uint8Array(100),
})
await uppy.upload().catch(() => {})
// `#prepareUpload` should have cleared the stale status at its start.
// After the failure, it may still be undefined (no new Assembly created).
expect(uppy.getState().plugins.Transloadit.assemblyStatus).toBeUndefined()
})
it('clears assemblyStatus on cancelAll even after Assembly was torn down', async () => {
// Regression: after upload completion, `#afterUpload` sets `this.assembly = undefined`,
// but the cached completed status stays in plugin state so the UI can show results.
// When the user clicks "Upload other files" (which calls `uppy.cancelAll()`), the
// cached status must be cleared so the UI resets — even though `this.assembly` is
// already gone and `#cancelAssembly` won't be called.
const uppy = new Core()
uppy.use(Transloadit, {
assemblyOptions: {
params: {
auth: { key: 'test-auth-key' },
template_id: 'test-template-id',
},
},
})
const plugin = uppy.getPlugin('Transloadit')
// Simulate the post-completion state: assemblyStatus still cached, but
// this.#assembly already torn down by `#afterUpload`'s finally block.
plugin.setPluginState({
assemblyStatus: { assembly_id: 'done', ok: 'ASSEMBLY_COMPLETED' },
})
expect(plugin.assembly).toBeUndefined()
expect(uppy.getState().plugins.Transloadit.assemblyStatus).toBeDefined()
uppy.cancelAll()
// Allow the async #onCancelAll handler to run.
await Promise.resolve()
expect(uppy.getState().plugins.Transloadit.assemblyStatus).toBeUndefined()
})
})

View file

@ -27,7 +27,7 @@ import Client, { type AssemblyError } from './Client.js'
import locale from './locale.js'
export type AssemblyResponse = AssemblyStatus & {
execution_progress?: number
progress_combined?: number
}
export type AssemblyFile = AssemblyStatusUpload
export type AssemblyResult = AssemblyStatusResult & { localId: string | null }