diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx index be9b6d44b15..489c4d208ca 100644 --- a/src/containers/costume-tab.jsx +++ b/src/containers/costume-tab.jsx @@ -148,19 +148,27 @@ class CostumeTab extends React.Component { const blob = new Blob([item.asset.data], {type: item.asset.assetType.contentType}); downloadBlob(`${item.name}.${item.asset.dataFormat}`, blob); } - handleNewCostume (costume, fromCostumeLibrary, targetId) { + async handleNewCostume (costume, fromCostumeLibrary, targetId) { const costumes = Array.isArray(costume) ? costume : [costume]; - return Promise.all(costumes.map(c => { + // Costumes should be added one by one to ensure they are not received out of + // order by the VM. Parallel adding of costumes were causing out of order gifs + // for large gif costumes per #5875. Additionally, updated to async await + // for most up to date syntax. + const result = []; + for (const c of costumes) { if (fromCostumeLibrary) { - return this.props.vm.addCostumeFromLibrary(c.md5, c); + result.push(await this.props.vm.addCostumeFromLibrary(c.md5, c)); + } else { + // If targetId is falsy, VM should default it to editingTarget.id + // However, targetId should be provided to prevent #5876, + // if making new costume takes a while + result.push(await this.props.vm.addCostume(c.md5, c, targetId)); } - // If targetId is falsy, VM should default it to editingTarget.id - // However, targetId should be provided to prevent #5876, - // if making new costume takes a while - return this.props.vm.addCostume(c.md5, c, targetId); - })); + } + return result; } + handleNewBlankCostume () { const name = this.props.vm.editingTarget.isStage ? this.props.intl.formatMessage(messages.backdrop, {index: 1}) : diff --git a/test/fixtures/catshocked.gif b/test/fixtures/catshocked.gif new file mode 100644 index 00000000000..9ef98adc90a Binary files /dev/null and b/test/fixtures/catshocked.gif differ diff --git a/test/fixtures/catshocked.gif:Zone.Identifier b/test/fixtures/catshocked.gif:Zone.Identifier new file mode 100644 index 00000000000..d831b34ce33 --- /dev/null +++ b/test/fixtures/catshocked.gif:Zone.Identifier @@ -0,0 +1,4 @@ +[ZoneTransfer] +ZoneId=3 +ReferrerUrl=https://blobs.gg/ +HostUrl=https://cdn.discordapp.com/emojis/403093165348880385.gif?size=128 diff --git a/test/fixtures/stones.gif b/test/fixtures/stones.gif new file mode 100644 index 00000000000..27be99e24ea Binary files /dev/null and b/test/fixtures/stones.gif differ diff --git a/test/integration/costumes.test.js b/test/integration/costumes.test.js index 3dcd4485456..2a6cfe885bd 100644 --- a/test/integration/costumes.test.js +++ b/test/integration/costumes.test.js @@ -182,6 +182,44 @@ describe('Working with costumes', () => { await expect(logs).toEqual([]); }); + test('Adding a costume with larger gif', async () => { + await loadUri(uri); + await clickText('Costumes'); + const el = await findByXpath('//button[@aria-label="Choose a Costume"]'); + await driver.actions().mouseMove(el) + .perform(); + await driver.sleep(500); // Wait for thermometer menu to come up + const input = await findByXpath('//input[@type="file"]'); + await input.sendKeys(path.resolve(__dirname, '../fixtures/stones.gif')); + + // Verify all frames (1-45) + for (let i = 1; i <= 45; i++) { + const frameName = i === 1 ? 'stones' : `stones${i}`; + await findByText(frameName, scope.costumesTab); + } + const logs = await getLogs(); + await expect(logs).toEqual([]); + }); + + test('Adding a costume from gif with large number of frames', async () => { + await loadUri(uri); + await clickText('Costumes'); + const el = await findByXpath('//button[@aria-label="Choose a Costume"]'); + await driver.actions().mouseMove(el) + .perform(); + await driver.sleep(500); // Wait for thermometer menu to come up + const input = await findByXpath('//input[@type="file"]'); + await input.sendKeys(path.resolve(__dirname, '../fixtures/catshocked.gif')); + + // Verify all frames (1-45) + for (let i = 1; i <= 45; i++) { + const frameName = i === 1 ? 'catshocked' : `catshocked${i}`; + await findByText(frameName, scope.costumesTab); + } + const logs = await getLogs(); + await expect(logs).toEqual([]); + }); + test('Adding a letter costume through the Letters filter in the library', async () => { await loadUri(uri); await driver.manage()