Core.test.js 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187
  1. import Core from './Core'
  2. import utils from './Utils'
  3. import Plugin from './Plugin'
  4. import AcquirerPlugin1 from '../../test/mocks/acquirerPlugin1'
  5. import AcquirerPlugin2 from '../../test/mocks/acquirerPlugin2'
  6. import InvalidPlugin from '../../test/mocks/invalidPlugin'
  7. import InvalidPluginWithoutId from '../../test/mocks/invalidPluginWithoutId'
  8. import InvalidPluginWithoutType from '../../test/mocks/invalidPluginWithoutType'
  9. const sampleImageDataURI =
  10. 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QCMRXhpZgAATU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABIAAAAAQAAAEgAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAASygAwAEAAAAAQAAANIAAAAA/+0AOFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAAOEJJTQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/CABEIANIBLAMBIgACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAADAgQBBQAGBwgJCgv/xADDEAABAwMCBAMEBgQHBgQIBnMBAgADEQQSIQUxEyIQBkFRMhRhcSMHgSCRQhWhUjOxJGIwFsFy0UOSNIII4VNAJWMXNfCTc6JQRLKD8SZUNmSUdMJg0oSjGHDiJ0U3ZbNVdaSVw4Xy00Z2gONHVma0CQoZGigpKjg5OkhJSldYWVpnaGlqd3h5eoaHiImKkJaXmJmaoKWmp6ipqrC1tre4ubrAxMXGx8jJytDU1dbX2Nna4OTl5ufo6erz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAECAAMEBQYHCAkKC//EAMMRAAICAQMDAwIDBQIFAgQEhwEAAhEDEBIhBCAxQRMFMCIyURRABjMjYUIVcVI0gVAkkaFDsRYHYjVT8NElYMFE4XLxF4JjNnAmRVSSJ6LSCAkKGBkaKCkqNzg5OkZHSElKVVZXWFlaZGVmZ2hpanN0dXZ3eHl6gIOEhYaHiImKkJOUlZaXmJmaoKOkpaanqKmqsLKztLW2t7i5usDCw8TFxsfIycrQ09TV1tfY2drg4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwAFAwQEBAMFBAQEBQUFBgcMCAcHBwcPCwsJDBEPEhIRDxERExYcFxMUGhURERghGBodHR8fHxMXIiQiHiQcHh8e/9sAQwEFBQUHBgcOCAgOHhQRFB4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e/9oADAMBAAIRAxEAAAHzwbCz0ysmMTtlSrWrl6XTNxiqs0XmVCWZod3TyqvYauSrcZQ5aGbuRgN3KktNCqcRZqUMlL1gUkwBrE6kJlW3d1I+rnfrGjdDMWgMNhW9Qcuuuys3NY00kFJBVoWQBBc3FEiwZCrJATYyzKgnCWY0N3QEPmakDlrBYNguWmmkZEtPBS3AfrYlAfyGUQ2UdlqGwtrtjBwKrWs7ffHlA2TNWu6lwwlzI5M9W78VgUpRnJnoN10voeW3mVR7f5sp5VEOn5gGbpM5bOa+sLToyFKxk5RKghQAFnKQTKHAFYjRq+lK6c2dGplx0CBtT1QHVBhEydwJUQ1jX9jm3byzJye1e+Kercjpx8DIY6+ApWhBLQ59TXTygPqjJNPNydpzD4tQrSyqWFFP8x0HCNEUQpLSofsSIVlqYK0dkXvTbmeb1XVT1vsGnD5Lb+kkVPPbnpa0PRUPS81j6dnYUPc2fgtf9BJ6PP8AAj+5iI5mp7zh8Oumi0eZ9HJ0va8PtwVso3VzzEzSZmKlY8LbSaJhdQlShDtapcfQOZ6fluT2uu9L8m7y5ruzZuLNuyLWJtzOaWL9vNes+XdU/B3A4a5YuqKOfY2NXFM2XR3dRbYdlB5F0/KdnnzKlaZhmUmVKXIm0PGlRtjaY1TsqkOm/Uq3WcRbL4/XJ63Q9SeN7XGZqGtZAx00nL9TSPuLrKv0GxBW2HPWRAvqd+Z9zT6mOZelrLjZPHWfrfIheTP2DzDPhFd3lXlHPSbNadVspR5ttvT6p0apiNSjt1i7G64Xu+c9ZcGqseq1mpZKyGxRWlfTvq1q9uOZuLXVayuWzWjc6+U5S2ha4cMCOvW13H9JnswL1HI4cSh7YYrka4pmNXnyrkvo9FK8uHmS8rFs+NSPLsuKMuz5n0xdLGuu6vT0ec53r6ZVpRoYiKxcgt2/aedeha51FPf0tV7hbB+UzyvdB7d/T9SGreS9e82fB09CjHmlu6ViteokKrhMRSYYkMddUtmsCc/bAPhiZitOx4a5G99yFjR69tlAjrjUrv6ZupmO0fHfl/ROW9Jk5jm+s5tdOZH0Y24KPWw7kH09E7V+o5OxYlTgWjnwQ3lDkxhtaNLdyRXN3zbZkSiyMFTpklZs3zNVcFrnCsWYwcB2lrKJLV2ZzDaV2Lc86pU6GhRB1iHFfcaoQYsjAdGlbIsJQAxbOmYYPKyKrdn7TB3foQEd8qMHwzCa5ctPRLDlQd63FkkEBlITKIrVGjPj1zzEJC3E8Z1GIUyfEJUIBEhGWwMXGZynaKZ6vZ1rROhFCN06pUJTHp3IWvn5N7MBnCrFsbJa8svFOgDctB2Cislz8q3YvQUJypiHVmVqZwweRZwARl0Oo7EMVZaeW2oIbdAUCR9GiFDl3ykQYrYmjfjIPgwtzgOitLimuS1PZ1lnLSvWVgXr1oXFnbVFtZhdNXWb0zJ4z6K322QZWFdYGBaVdrlPQmEUp66xrddmqVJ6g2GtGuqomDImJrbYn//aAAgBAQABBQIRRqHMTEY54ym7iB7xQBaSmisUvpa8a4qpCPpFRx4o1UtJCgCRIepSauSuSg0dgC1K1UMEpVrGsoKno0FgZOGMCOzEcYkCjNLKGUQyBaYQcmm1q6cp3MiBHBGZFGKRJinKHJJzGKuhoNUwkrZTR5FLRJRU8iiOZiwkqJOitRTRXDEPhFHES1Akp9rOj1LFGKMEFxrKWmqlZBIlWlaMilkEuFCVHnJhFzKFhwyGNctzm6MjQDJ+UHSFjCWrVTEjoQouoeQoVVY1eVGTVMdaqAdTjWjTi1DUktOigWouGpI0kBJKxV4sANManzUct07B5ApilxYBWZ04FC6Mqr3Kul6v8qOPEuNLrV+wBKxRTWnrWD2SepakVaKtCiwtkuAZMiKsc2K5VBagzo/aUINDAeXAkFVrEAhOPvNzy8mjjJHklVOwWXDbSyuPZ76k8arddRSqiaUdajl6LBSStXcd0OnVRpoHzSGkEjuXDQSyRpKlFHPuCgrtjIp3EcqDJjVQGMKKj3gcnTIJD2XaUztBRDEgSrHilYN7WjiolKjkomjRIcVKNSxr3OjjVQyKSogloBLxdKEORJQoGneZIQ5r5ao1KJUVJMW3XXu8u53aJy6ksEBABaQcoYVTXN0UQx2Y565ZUpe7pXHfI45PJ6MLxIBUpdvOhI7YmpDAYo8tEK05gDUp5FnX7oeHSnQoQZSsYqaWk0adXlr4UhMu5b3V2isLa5FbfxVa5HvXSztpLq4gt7XbIlX5rLFtt01bVZF3kRgn1Zen3KvRgdsSXw7AsasQqU9jgWu4mniS7rb0TO22y+mXD4Uu1CLwpbpA8NbcHY2dvYJvoxKmx6IY0kpv40mK92yREiduvlP9F7g07Re02bbhaRbwpQcESMZYnVYe46qeX3R2UaurFz9FWpLAR2t1y5FKbS3nlyXt5OVrhHDzU1yBKlByS1dwrFMi6mxuQLmWIKHJSHy2tGvMo80rG8po7AwojWqVQXblb3GnO/mx9za1IRf3HUuXSXbuKV5oimgUtJ0mUaPdtwHvCgQhcuM9tfRywSK0T7KgHdzxoCJ0E7gRK9pjEaZQKbioQwL6lYl4n+aBp2p2AdnDJJtsmtxbKxFnnPa2VohDkrVTuVUY29Kru/nCngFHZaLu1CqlLo7qdRa5ADOqSSFMq6W1zHypp0BG/wB0ZVvJT1USKHtEHIkOh/mLePnSblKLS0jS7RBWq0t+VGVaSnVZFJupFymSFM03SJhh4dSpEK51FrmqZkzBxpjWZIyl3CcDbdCtxsVmK8RMifshqSpxwSLMVlKWjb11RYpD9yi+9wepfhsIF1eqVPNt9sZLq2t40bylbrU3RoFZ1FHcx85qt0pG1WyZLjFCXcDQkZGX6NcuBVcqpItalWCUrRt02m7WiLoJ22NLRY2zEcSXil0fAdgn74LikVGu3u0LR4fWJ95UVDeEr0QvWfVEtVPgJQ5K5bbMIok3GaZrvkiSeOVwy5C6VVwlzEOyLVJhMuaJcF5C6YvQJZNBV+T1/mE8YgSfCkao9wVGkKPSeY5Lmj56HJJG5pKmReu2rBC0xRuQRrONrDCq4xkjmzSpdGo1FvoJtTJJLDJZXqJZbq3gWm6yiKFNXF+VSHq0oKmEEvlGvuyUqVCjJMcbiijJxTzI0lD8PoVzimrmRrdoUGtSydQVKqZdGs6WS+Xc3FcUquMbqWdbQkv2Eg1MerRxCCs3lt9FBmLi3lXI+lRUjUkkIKqVoCA6uO3lCfdzjHZJllkjjSzATKi3iBEMaR0uqXs4T7mvQSrQ55kZTTQpc9zU5FaqVcpCUxLrcWmS4LkSJeSgZJNCpRY0KZQxJiNqGTlhiMV0OVepTgkmjqxKnlyBC3XqXkUjR1SGNU5KQoGsaCKdPLYLSKGwvEot7q5kxN2sKiWiVMlqspVby1CFJa1lm0uJBBan3iwszGi7jJM0anMlbx+ghj+glQrOdGCdtlKUXC1ci8HOUiTQuIVJKsqoDP7qLmBSMyE5xtRxEi8ghRwEjiV9HzglEyZEyJyIi1lnkqq6jopITDEZlRIE/TcyrVPs8VUXClAQSfxiMBUF4hJJUQpQSuOKJCJwpEZgCVDlqXMhqlK04pWpKcTLpGiT6MVUlZSmIUkSmahzSkKBK5FZGPDkpS0W9WtNUTQGQTnF6mOIrQLdAjFyciF9CQpUKZVciTFRTK1LBOCMoruRMEsunSpVFtJVkpYcP7tMmCyo8/Ih5kq5kUbkmyRb0RGmnNStNYtFXCcJsqOGQRMnmSFY5Vnhy5lAG1VVPOQlrKQqGQ5yVMt0QlaqKgV0nIuRRiiQlSk5pTIlOLKsF5dfMOFuOjyuZSmMDCADEEFU8ilVmkolSqQ4dSBH7uD0yydMk/0KzSSVZWokNak0i1Xa8pCzTBajkRi+SEidISm0iwCFFCpE/SRJSgSA81C1KP0k0ypFcw0plVM5xRoGSVISvlu55otkRLkQtaUtUgKIllaFJ6KALuKO4GSFSZRSYlzY4qk1LJ7KxIhQmKO7SGnlzJkThcIjAn5mo1IRpBUKlQpKUVajkoEZSqUmJXtmqEakXSuorxktS7gBJrqT9BPVTjNZUqjgjulrUuCOkZlQplQkXmEqlUSqWQqNWHQ1eVJCcZFE1iKOXfArRBRyVkdtqrqBjPKjiX1Rqa1krg/eqhSqa6UoqmH0eqBPJg1fubRGCL2WRa5xgCVKiCfpJCtCpRzFBa1KUsJTCoRvm9S1dSqlbpVlnslfLMy182OELhuEclSiURTI93hTIExpqE8wVimTyk0TISuiEZFZAjiouVWQWiPEIkzAQudy/RSGUIXcAJSgqXb2+eWKlC6iUhyYe7+8ctqWovi+hLWQe9XXWrBcn73irhCr25v3h1MH708Ve1iKzf4xN7APTN/iVp+6P+LzH6L+8Wv+LTe3N/jVx+8R/jEX7qP2ZfauP8XH7ocVMscRxVx+5//aAAgBAxEBPwE8fiKBUvtbCN1tPhI3NG9A232FlC0BED+bOO5AEeEwBNpttrlrSu8NNNtDTLPagksRybRwyP5aY8MpuXB7YvUfRMQfOmyO69QNxpxx44eqhxesMZkywH0ZQMfOtai9Dfo9N0gEd0nLj2y4dhYxN8OLftsuWYPl2OxxkAAMoub8voxzboNfcyN8OKKSPAc4Lt4RB8F38OWe4/QiLNNiMUyJYxY8O6mc95pNJmzNsfyZHb5TlD7r7pd57RLmtLfffcJcUuHIAWb5CQnZl4cuE4zrSZu93u5wRuWlOxAoMZfk5JkhpAdoLlqJuKchPl4PaUB6eVT5eoyDghjmF8vuQDW8WgCJZ7ac04xfdLjyGxbnkN3CQgaFD4fItFvhJ4rUZpCNBGYuTqbQeWta0EWn0ZHhukU+dLKG0BPKPHCNPVHKI6lPhHhJR4t2pYvppXNsni3muGIQGtRoPDPw+ifL+esPVPgJ9E/iYvqjs//aAAgBAhEBPwEXL8IZSuP3O0+WRjXGgFpsPBHKEFu0yQX/AANaYs9UmQHqnKD6MJbWRvlEiBTQTFEuNC1p4S1SNKSGyxgfLZ0xQ3MgAk8A6AD1bplkA8MZ7ilq2N9h0J13keG3fLbWlsvzZOE8tNMpiKM35omJeNKdo1Os8pkaCJ8O9MrSPRESH3H3GXJbcPn6BQKLI8aW0im+UHQjlxQ2jUlBvskaCEt3oEnQRYpZZYxHLLqR6P6r+ieok+5L8+00GRb0tpAQlBT+RZYqFjsOYJzX4TmAZZmH3FIa0DLhjJ3ab2zttj1RZEE2E6bb4DsZY6RAsZGKC7uXcmW08MpGSH3AH3fV3eEzIfVpCbYp4bJ50PBTaWvV9ESLbEG08DQppqy48fDGJpugzNi0FHlMreaRdPlkxjYSPREqD6IFlA5RFhiIa0pkQeH0p9HaUxb4oIlRSLCRtoIlRT9oYi2IJYY6CItax8jSXh9WT+WkvLHwy/Gy9HJ4R+Fh4R2//9oACAEBAAY/AqpVo6DVjpeY71Lo9O9fJir0ePkyKOlGA699e9A+PHsfj31PajKjqfJ5kMdHT6uqH7T0YVWrofZeI1q6BkY8O1afcwde1Xp2ydHR07082Vd6nvr2o9DR0UrV4g0dK6dtTR48Q9D2yD6Xq6AdqVdX0qpV8e1e2v3vg6PEfe1ej49q0+5weKka/d+PaqXo9fuU+7p3xD49qeT6BR69+ntV699XxdVcXUD7gCNWT+b0fVwa8DV/ScGce+SQad8XjEhch/kh1MBDKFpIU6U1749tHx+/p2q+kvX7qa8KtKk9OjNVMFLIRxdZE0YAdCn7Wr0DwD6nwfvNzUW48v2yxglMaPypS6k0aUcSBUujq/g+LpV6/e1FX0in3qH7iSlpCfJ5PX2nUjR6DtqXRJ6vPvHCnTM0ccEYohIoA+YrgODSn9o0c4kUVLz83r92gFSfIPKSGRI9Sn7mvej0+5r96te3SNXQ8fuGrqHzlcI0skNKE6nzdQdU6tN4lPtJ6vuphiGp/U8YxWQ+0s8S9aU+LquLlK9YzR/R3cqT/KSHgVJV6EfzfD7lMn0asg9KQKqV6PFMaAn0oxJbjH1SHSK3UXWa4RF8Mav6WeZfy0fCU/NTKYUaFlry410eLwKdKUZ5SSRXT5PS1l/B/wCKSuqoilhZHVi9OLrKvX0fQFPgWk/zeGOvr31PZMcftE0D93Sfis/tF6NLGgD9e9AHkfs7JjkOhOher0T2oA9XiO1OXmt6IozV4en+oLdajwW1aujDGtED9bKY1An59wKvBHDgHUsDgeNXGonqWNR2p9jyq9XQZV9CHTzeofDRq1oyonjr/qGO5Wdf4Q1f2n8WkycOAYXjSnDvkxMtXSDwemnkwT5MjyQH6UZfS85dacA1KxMSgK0LqotNHk+WDp5/f1/mkxD2lGgaYI/IYh1YTTX0caD5B07lLrWqXXi68A1zLSarOj4B4q0LypowVgVdKVQyAwVCqK6vK1k4jgrgymdGK/ucC9EvUPqUA+pVf5mSZfGNPSGVEfJxpPCr4flr3o/RnJX4Pqk0+D0aUcUjqftfg+khTyJ+D0foHoS6lrBarZf5OHyeKk6jgp0USS9UB9KA+AfD+cyS+rixT2Upq8hwSKHvmk99Tp2WvhrR1SavqXR5fm8qM14h6H7gkB1fNBrTi9PaPB+jqfuV/m6ANZI4oaleanQ9tFPV8Xp2UhdD8HSJONdX9InJ9MaUslL4PR8de5p7CxQsc30xD0IqwjjTzdO3wY7mnkzpwDAYqrR+1o9SWNCTV4iL5vRIcqj5aDvpUunaj49q186vJHn5Mkxj5EuhSQ/Z0+6Al411eA4sVLIU6gsUGroRqxqaNCh2+KgyknqLFVaB0Sjho9RpV8NH0Di/jxdHHijT1fF6kOmlH5H5P4P0evfAavg+D17cXoxpqyfNmidWlaeBOrVpXzT+LL+1lJRrXizTg8S0pD1PYK+DOlC1H0dXVlh/B4D9qv4sqSulGrJRxdauqTV6gvV8Pk6rH2MCnmyVKZ6nwfBgaZyLoPkyUjrPSwgCiUdI/rLyTUrKsdfJqGYSEAVLKQfkWk1oeLUNOJ/BhXA0IYPmxlRmqPmQysHgODUvU01+T6mErKaDWvq19IOtA0murl11qAzWuLzVQJcdQerVnDQHhq68UqeJ4ebXrXyo/g0poEmgr8/R5jzJD5ihxZok0S1XM3tcEJ9HVJ1YUeIPUGkuldXTL7XkpILjV6DHF+yTRjXMjip6eQCmD+XSvxZy4V0YNaMmgJaKDQPooTXyeVMR6UeVdfJ1BoXLlx0YFQ9CDUANSBxGtWMjq+Wk5BJBNPV5KSKo4JUdGIwcjXj8WQSKDRA9HhqQigeg4GlPTR6EZD9bXLxKz5taj+Ygh68E6fMtSuNNQHjShDMqdDonH0Yi1UR6OmmjOX6vNnq8vLi68KhglkU0qyvzDyOpNWgAeXBj56+r4aMeZLw9OBfTxowkefo8nRSeI4+jpjXH2Q5arwIFfmyhSq+ZVTtTD5NXTj01aQgUA+LB4P8AW9V+xx+bCR1H4eTCkn1P2Mp1IrqWEo4npaqGmGlD5tPVwOpfKaiDikasfPz9GMvmf6mo04aA/N0qOOrHowRp6tR8+DTmeqj6nlWlSxIPbKa+rqRRpUEdIU+gjUeTTi+ZTiXIv9Tr0jIsdNI0ng+J0acdD2yV8uxCqqIHterVRWWSKOpGfVo/JWulXnTIhFQ8YyKkprRqTUmvGjNNdMBrq6rSUk+XozJ6DRplUda6spUqpJq0k6a6tStPwdSOgaGroKGtANWqmpB/WxQAZegZq0g8RrVqABJOgaIzQGjJSkn4uMEdOWtXzFeyAyRQgjINPSTqypQIofI8WKappRoRwfSqlNauiaCurWMuPH4MHXUashPAvXuEp9ojH7HRQ4ng8goIAX1UZoTh+DEX5cemnm4gB51Kjxq5ADr/AAPJBoqlNNGaqdFfstI4p4UeXmWU1dPPyfLrQqPH4OMRk1pxaAPRoI82UeVXUjWlCzQvIiopQNFagU4NBHpUMU04NQTSifN5DgeAeJSQKejr6/qfXrieLEdKIPH4PlJ4p4FjVkutHq6dsU6lfH4P5aH4NUcmquPF9JxHAOHl1rXF8w+TKAOOlXVIolJePHzadAVGtS+o9R4MA8QKvR/Y81moHAMRoHm0eoDAH5QwfwaTxVxeR82EhOgLC+NBTXzcaAmnwfSRoKUfKCOn1adfJoQn2R+tkjVTJUMqs4vLgfvg0rJ+X4OijVPwcigOvgGhMbSnzZSl0A4hj1akE004vlnXVmvtA6BqJYNfJ504MyOvq6qV7DPqS6tFPSjSgmoLUv8AMxGtX0nEMVNX0aGryWtSvVpoelmg09XWr17affLp5U7Icf8AaZr2LPzadB2V9wMfNq+bHy7Bhn5Mdg1fNr/m/wD/xAAzEAEAAwACAgICAgMBAQAAAgsBEQAhMUFRYXGBkaGxwfDREOHxIDBAUGBwgJCgsMDQ4P/aAAgBAQABPyGOusGjS7BS14pecrFErsjBspIoQhigT5LNjRwcKGSlfdlFeVJmYsIao44inGF+pFaBuhHGVYYKKHqyS6xd+hUKMkyoynBeUDYBcKHLuxdtYgogsjxUSYdqyoHmw1181A7QsDASxiMpsw4Cp/HZoPJooBPkaahBsLdi0cJpSmGFKIdabpvKqIPyqh9Wdsmw6UcyohQPSi8H3fCmnlML5XYEfFUuR6s7s0+dMZLlICYvKJK27pvNK4CqQbehV5JiwK4LnQeUmnuWvxQvNw0xpvqkNxKJNVJECTZ/AZqoDcOV4rLSAHmjYmSjz2vkcWVhX8LlEy3lOf8AgQY7UOtUHPzSpB17sUefdixNSUU1uNxXqrdsRQOzTQdtSZegVa6EsTZZC1i7XGym+1FJ2HWTTGUrRkGavPq89TWR4snropTEplE6llpKksbzjzRXWqxR7uoyYvp5s0geVmWC9nTy8UALxNZK1fN6nVnPKs5xYiR2qp4WUiP3URqcxJolisZHJZ5Mm8YNJZvQI5ob8VdcHS0rzPNUA6PNX6qgNRYgc3MjnunNQQDaxpPEv74sUd8Lv6s4V0gD2P8AxRJakkc2bggLLlF8lWfP/BLYhqVswNVRn3FlyZLKZCerNFq190FGOqMNlzNSfUm4r+1JueqIMJRQvlFA6PmuC/NIYDU8dCnEeKE0PiKM5M01lHAw+D0URNYKKTaegoKj7EseTXlugRqP1SLONR3KhqmqiUNwcs+qFbTnoPIQs71XV6qerHSwiqLJwrtIjebhSeaIxjavTr4uNTXKp3kZHD/yLCQUQsWDzWT6qIxtbKQTOvL+LB0vxAochxaKU9w6plQieUdfqkdIqJQ4pv1QRM5ZiAaGVwglfq+7HsKPxeXKALlLvZ1dMsSgT4WR5s7JQ+61Spf+kd1pmsc95HNSE48VUzHP/HOVJ5vmqSpQOiwGVifLeIRtFrjP+t7DYU8lLh5/9V5/7PFmwPy8ezcRZh/+B6o4j0KbqNOdz6u54Ag/VihYntlZO8WXfFXpZov/ACEBZqUm8ZQUj/FiUJfbY86oOpPmuJw0TX0iHXzWnzqlPzVpkvifJeRSiU4pubdJf5vz7YB/W3ffkCVSnJtO/VhBa5LNnDT4kzD2Wd8yMcULLzzf/wAA/wB1cHxOk/VNlmLH5uy2TF7OemtDX8SqZaryaQ2fNkkdV5//AAcqh5sr1fQsLh8KlM3eUJ215yo2YQdrTAFmHzXHhFZElngFJYvX/wCIISmUR+TQdOcFnNsZkgvXr/ikLAoEmeuL/BFIUI5+KsoTW4vd+VMxYVD9FOUprETMf/wRY/79/wD4JHFf+kwDSfhq7SWic9NfroSej9rIwuxKxaucM1kk5/qh8pFQIwpNUmeB7TXExYThsOQTmsAsSg61PdkSjxHdmESSQaDr6WEvL5LiAKUEZl+KnbHyonV9FSH/AKCk1P8A8PGi91zI1s5K5ZMT/wDB+apk9oqG0WxIwGcUMjFwI+/xXwZXrNQQmHKhslA7sEEh0p8MjR5nT5q4PgZ5oDyKKi3in5ut1X1crxod9SON2Mx5pXlOXgzineYmDRp0UY01tP8ApNdfm+j/APDNlazNSX8KsV5GH/DMCyiok8X57pHgsqeKjlXoSvJZjmdPJXhqfiuzYPz6uhwB6oFPzFFxHzX2FSK8yJkhOe6GgE4sEgaci8gkr7qOPF/r/gtM+6x/oqoYjzTsT5vFB6vLFfP/APgixU8tvZY8nYeXv9VKEcChVs5cQDXzbyzeF1Sgy/Fa6+VIPkV3AjgrF1mpWGSZZQ0Ln0qzYXqIquLWE+fFnHIMiu2UlYz1F5g9Xl7jKwztUyhoXJVIU5pWFR5vGr6vYfiuulRrCc83h2qkx/8AgmzZqDllsj3vNmBP6WN7LpyTN+68iwyGweQ9lYSOrSduaOoenurvytzTo3iaexpzEfddikuSphSUga/dmEaBywi03iHNQQBGHXm7D8Z2XSE+1BHfxeHL8+tg88HFUJtsz5s+T/8AkecoSXPixVj+yxi9lb7aoMW8HCymY8RSSsvFHxKPtT0BZolKFKKTh+VXFQ8VVsVYnUzZUYqG/ReE908HFEJ43zeFUA/XbRQdGN0jxSxboo3+KAnJF8y4awRb1ZHp6qFdSlIVvFyUn0E82QTMshFnNAVaEqE8SuRAzi9SgUca3iu1dYHi7VWJN2jGf8CcLAOMUljjRw5Dmlqcc8FEchcSbTm7uo0zlvh6pFS9UpCMeXzYEncfFEnydP4qmc2B44/wuH66fdOzT/d0GCfupEMorNU90aMdCOLD0IsgoAMXnoYJeFfJF5QRxNkwzCb3E5MosUcp78WTEIl9u7CQCWTk+6GBULOSNRZ54F3JhXadbP8ANIU2a55j919FjXn0vN9mxFRCs0H1S4rAWC2xtFcvjb8zOz4gBXg1h1WrHGwWAHHIj6vSiR9qJKgwbxDL/dgjGc3kAmauJ7lIuwOFlgiC67W7ibmAyqG2dc7rLTSOeadnjWjOh8+qDgZJl810wMh8apFS/axwNM3UCPY38P6jpG8i8YkuA5W8YvXheGzixQOigAV8N1URjhZecgvA/wDajyxC/PVWjyryk6/LQKAZVgOKuFRHwefzfJuPExRmGKjyJZ4soPntYn3B/wA28QveeLGh0c8TVkITwn782eoAx/dQBjn9N2iDOTzZXGKOj1Rapx+SCsiiEO99Vc3gXE6th0EYd7uOAD/4UbGmwCcrIJUzxFjTCgIZ4vTMHpUiDUe0F5M2Qx7LIO8TMf5c02OiTxVjOhB4KQObWM4rGYms/JutEopIMXHpzWmPpZRULirn2SxAkUQmZ5sZgOQ40qmzEN3DN4YOAsQNhLR1A6afhrYiMAc+2qE5GeP8ykBpD7msxEnBrUTDnZQF4qTTk5sKaZqRszGsfZclwjnXq+ICaTWQWc569VPIoZ65sgZO4kX03VEiP8JijygdheF80c4qsmZa0haKF0IpwAMGz93KmyA9tFYdxV/L/VPjwXcjz8P9VedxDrI/qnzcUo+Rq8BJ+ttZEUTxEPCUiLA0s+FjzGgD+f1S2nh/urzMecQuKGnE4PKP6/dBxg0T55sgRyBSSJ3Q8xcqYzn90GpFRET5+LqBTp4h3RWR08ng+LKgn1MMo5Ar5+bMxcYXcDMGVaaxZa+FOhMsYZHVDSIXjlGGLocoVQQ5K8p4vgK9BNVjX2NlmU56ZXnmCeOHj3fe6Hh8Ub8eWeviq4hkYeDzWLH9qcj1QSINhUNNQBj9vzeFLGTuLs161x4wjT7fvZqRQchzG2a+wzHVXiGn/j9VQgTl1P8A2txMpo7z2/FJtzLgk0BfjvmilBlk9eKjXcvLkvJMDt4r87yKcvbZ7CZ3xYyBzfXmtgYBjBPiwqBz7isPyT5junEzZenmyD6Nd5BCfzeBmIIXD3TaJj3vmh6Yz5zuiPmenmz84VU5PVfs2qQmQ5Rxek0cFnhHyimDPTXcJQAjGh4YajlbkHLPd7xiTs/9ikHx3CZP5stqz2Hl/q4TLl0+ChC6W3NJ2HE2GQBQNl4KUl7O88FdxYlM6ETPwcshCCeQGgUkOSg8q+RSl6T3SKUIJBDOxZ0uD8tVA5RHjxUSBhvzSHR09DZiBx8JaqyE0oyIykPakOUHizpgBz6KZwXBFGFOOEKzGzcWQ0S4WGU/yi8GsNHh6ulgfkXmEsFZDDKPV8xKXHWkR1ebMpM5LxQoznTUPzmgfFmzQR5V81h7nOS+K6TnyiFeCEML0sJIUbht0EkIjy/5t4KIX03FRGH82IXywEcUuhw/dkBzJ/ClvxSO0XjAlArVgmceCk2Q6qLTQ2q0vM8WT+J1ZSvyB8XMlip2aiWm81xYSVHmoXwGM91UCcZ37o5EECo+LvnZAVLzeEORL5YK51RcJmKuQheS67BeHYsDGurJffm/HWPLQxUFn+n3dxTsRL7WZrNJOllXCAsGxs+LIOp7XiWMXiTYTHgHmqDiJn0SWIB0rZHKqw8BeTpIPzeoBOfdCHHpLc5Jvd0ZyK/SoPuz4PbITlce6FSHH91k5VNSwQRPNjjhTXJpYIhPpTfAaPXdwSRKyJp8AaPGVFMZajP0gqnkX5rF5nTXiIC7lXmZ4r2UKRN0T3WZ2nE1IHXtZgcPIj80NAmZciyOXIztrKlUD5u4p7LuJyPipwCWtUzjXtScDiLjjyDxZS6mvU+KeM4p7srwBR2iINymKDWN9wT82eE/o1VkhMk1Q3EyWcmIM5opFhITSKYkuhmNizq+BPgrRNch4o1RH8WakPos4OfRXh1stILNiEA+KP5q7/x7LxihHBcqeLwrU5Lyxml/Va3meN8wx5oJEf5P/QzLdXi7E+L/AAq0NeL/ABFyo8Xl9zUkl3XuqijD/wAdve6VvJ4qeduYOsUBn/w2TuVMctcP0Xn+LwP/AMMlp/xv/9oADAMBAAIRAxEAABAIy8IwsiIw9FeZ5KYg1g4oZvSRMaCkGi8ofE5FBOe1QLgG4JKogJeHHbQN/wB2hk/4aPzB6qyrOWjEllFPKNpIyKPD5dXxR3zpNbepZxntKmSRt9oh8Nfnp2u5PLTUxobstqZLMHJdm1Eme4Pkn6jOCfH0kjOWLkmXMxIV27yt2zx+09tTmmOxSD2lUSf58ck0fGWe7iz+aqOwP62cGyqNbw1TAX20Rw07dnStit62nJ9SQI9j22H9o9W/uIJxptR58D7RmCaVjA//xAAzEQEBAQADAAECBQUBAQABAQkBABEhMRBBUWEgcfCRgaGx0cHh8TBAUGBwgJCgsMDQ4P/aAAgBAxEBPxAn0lkPRnbN5sHvV+aXEyEZAB1fOsrlxM0WQeHz7JdayKaRPsgRvJ9IzFtBzLHBAXLbqxZxzMyQOZ5sTuHb4hK1bezlyCdPEQFxj82JLl/e4Ak3hblypwXOGeCzbW8+bLxCpsvxcHUffzE7ZYMp9WYMnH6oQxkD4ssneOof1XRI4ssxx4lunghafLbp0sjcueEY1smVXeJ+hir3Bk3Nf/AcdkDOmTtYY6bJlwTjQ+YgXuXPGS62Dc6ufGHfwYanX+AmNubCKIYx7uAHBEEw5s425OnhjeTBwX5Zk05/C6E8QJzGHfG3YnHzEclzePiN5JgEgAuFvr4s+YJgkpB2c8bLWTf6Q6TrqFc4zDszEt7tYYb5Iz8rEsy363XFikc5k+YAhBfIpb9ORv1/pkAfg9WVbXTKcNyLD/W/RK11ZM36W0h+dwhyucEuVlfsLR56s1MiaJcnPxau6vqZhsrgJWbH53OWHlg1trkWMA7XBhfIsOubQuLbS8yuckwkLCGRvMt+Au9EHldLZ9LD4toY9WWJdZ1BnMAw+bWx4Qgib8T3+qyccPUI0NkDYBnzJwwTiDdPGMGXcjpuu7o6+RDpvp6rpOm7seDPn//aAAgBAhEBPxB/AJaR3LQwWT8p1eLZjN0js+YO6zDzfFYZBuDOjsMTxfAdRj9V0RkHHD9ZXqXB4hnlmzSQxOrAZI2HUhyhrHyWXks+IywBoXw2PTLGPghW5Q+JkhwftJytnV5IHkuIlht9TuGnYAtiOfNuObcGE98eGnptqC+nLxOi0GpfME5OzCc+YHpcAo25SkYeAOpfrGfNoHgvmTRGMTwmeIxxH2WlZ5QHZZ6i+NxDTLcH4uBbI26bDGrL5SPVgkrE5e2VjqwNjG/g0H6T1fqzziVO2VhHNp1cHJjzkCZ0xKzCJhe/GeIZFGPYzHe2bnzZcnjg8W3DFFDSbuC3mX4IPrG+Z0CgOW1eNm6MLKsjzFew7G+r7yg0sPqQ3A/XEnDL6rqH8i2DsgvkTHPbdCyNXLuTLwCJBkI1/wASkGynfZHyLhqzE16sviGOknW/LA9dELoMOFknb7Lio6lyml5sjgR4D9ZOCflaTrviH8rWk+t/WQBtnT5bA+uAM+3Et5yJZQriD9LOHEUw/XE+vy2hwlxO/wBEAhOZUzgkdHxPn3ObAdsHUhGk/KzB+uLer42N4THJx8zAHLzl1f1gwvPzMDnctN+LJC4fFvD8xDwX0WMGX9JPgeR3+n0n/d2/ndf53bdPyep/f/xdo6vn3//aAAgBAQABPxCdF2J6K8ISEHjemuyR3HFN4Q4jH/29wNoqBJHNwiuRVtu4SJKAkiOClJjufFZEYOrybJxqkzyKON0CgSceDZbD5VOwDK+ryQc/dn5AGU9QCExVc+KCEHGq/NpR1mTVOM8PumiuZTCmHWXulHR3NrYNZaGGg7pCgDPdLnCoLhBRqWRg2HJ8RV0LZA7FleEiLApPOae7CFjd3Qi5higKhlfmycZvMUWHKh4qhaSuov8AAqPDxQ6C2QVQxysJLmVVkZMKAZHPxfH/ANgph45rnxTtNwBjHdToTQCFbJpCFJlIGfNAQnzNw5Zc0BJHmlGWjmoDgkahAfJm6QY1UTCYKo7nK8UGEp5CyxRwKWPRTmIzVs5SSiboppkK+ABrET+bCTTzsqM+gqKYPL1TKoMOYssLZSzymQQxnhLpCaJFlCOXnzZkIJUTFnzSZ7ogEhEVoYQj02ESkk/ivT5B/NBmvi8zhRIycNJBBFRDGOIWU4Ci1WRQfkWnJJHVkIkUiTvcq4YZtQGThVBnWlp4nwViHn1WyK3dvqvJAdViQDUvVB00AJO2ygJiQ4qRAcphJCafQOYmjeDJL5p0IWKFM+KFomoSF+agEvImuEBHkeqqGHUXKESxT0AjWiCn8KTgm9WJZBGzTnASyoSVOT97MojzV0ieK0oV5ChKJA/iiGJidosaTtQN4KbkaOzV5YJd0ZqeT5oOGDxTOuLMOnXxdhChJxY00lKJ0ioaS92Cyv4oRV+V5RdtoQCDoU2tBHpplJNsf5VJ9savgSddUFEZJ4ppU2Pso2wUw7nqxgLX4HFamAUFg+LBM4BwlSZpJ1CQfi9gIhjRsseF9UxRSXCp8oTiPh4PtLALjhv4SP3ZJGTJn5sG3eCwEQBklYrHunZjkhdY+E+amYvE8VBm3NSFV905rAJl1bNTxE0RyPNyeHM1gLMc7RJZdc1QEiKUMULUqqlr4JOVQUYSzIpLRCkRPU1qPDd5Zb0ZDBuhrSWa7G+DXhtwS1oATASO2vHjk8qYzdx0aVlDBJdFI8PKsRRwfFcx0RSch6e014O5gpAJmHHHVBvNRv3XTaKAagJ+po+BS+Cr1UT3U6SXKkJ5ZlZLMmrBcs7VVNikCQeazQycXiAKYBE7Q5h4aysGxWqSB3VILq+S54sIwnxXDsWABTscbNSiea1cXeB8WkTCJR5IaP3lL4sWBpWiBEuM0A7RziqQoMuCnM8NhnvJcTUngUZkHaWkFowDWKmnIHg9gX8UJp8EAOY9xXz8ZXGP8m9Exhy81OKJp4H6yLmscy0M1Fxqcjwyz7DwrryIJql5RhHgEr9XFXzCnyxn3FZ0jHJpQYBNVGB8NkWiMarKzysWsvlqNAQ5SuT5qxM08U+lpkCsurcrYXgrzZTs+q4jZjiahgWfMWcKep2pwkOmvx0JwLH5wS8UDZYaUnzSUElkOKLhlVmx+LimM6xjkfgH80RWUJTxxcRhee3itBL2cJKT8VT0PYcnS+JfpuEM88VXjijBFZ+NpTCB4Dv0H7uSQM6/p/AKjS8fP5Gjq6mKvy8K9CDJ+SgNRBQkI9g8PqsEiWxQWdetL3PN7wUQgsUO2rpMPFZWCqiwiy9HNnEh3S5APhoENH4I9CuA1U+A9sZcRFhmD7HD78XgLiD8ieVoYIryBgdb/Nxp2oBBjaaTBLvywD8UJKxibeSH4LVpESbr6e6jSknTYMbJhOXo/wA8VDMRUOfZTe0YB7e6w5BUkHCeK+Qeqc8+5wPitTcmBB+JSzGN8PcUX7NMxw7/ADUBW81PK+ZpBIVB1VojpJ+ShFQGGKlCPKtMN0+yGkYhNeAel0m7Ths5FWMonaXADwqMgE2zBMICK8pNcwAFlaviFWqkpCO5kA/78Up48IGy+DgLg44e2nJkjlgisFwXyiu5Euy7ACRXRoHClXQWcTllqs09+Vq7rvA4WbqhOc8vT0+aqmJOxuNY5AYFJkjzRZhyPakMZzIYk3/ywTJwiOlSmjPP6yxkOyUmV6PVDgLqzCtUcXDFwACWcguzSJT31/woTxS3GWFsxZWua3jLImVUnLNKm02ZZqAkCftKz5SoWxp62a3H0+LhgYT1mvwfzcIJHIhP1llIbsTZjxW81iMSB37L8c0+sjanOv8APdW7zgq+wA7oMR8INTUZwuJ/Y1nHOk6I/usDCkORO0xFISr+PXqsC9INXEfM3TMjAnkbH+wkeVCsdvJ/wsnji3iYBUaLV21nfqLwTaSQwrR0dpDoVQDC+qLFB81sS1gkMndCYeaAwY/8YMFlNzPgiEWE9jWxnjREzDH9U7OOPV0ZqaEWrHbYTiIgSkKPMI3jiibIAhUZjIZPXuygJb4Dqa1CQEzmRX5sPgJGQjJ+6MccvQulA3ALwmD+6llIOTLsTritjAk/Hr3VUxYHDuf7pIo41nwDtq/1D7EmP5RJOVqRaFTZQyZSd8N0wJieqZFk3Jjgf5+rAi1zOYOqAUGx4z7o7F4MmzamJ4pPd0VJ5f8AFykWaRZLIvgXJi/AXv6JfqzaQkcQYx+W44lHF5aO3IIQrNKQEUcTyT6lsPYMVswOJVjmuolCxD+rDYACFmVFPZeM5KWIkpNIzs0iWRoS+j3SYZQIUnl62gAPJAf580YfmQJmeLOuWMNIbpf8Uj7/ADzXJJQOX7PvzSuFBfNQyrEeB5/3U02R+SZHI/dPxGVmJ5XZ7vdJw480RIK8BNHQwe01YkJYpV+vMSskSnIWSX4iz9/nWjzeaSo3i4klUKoVKXUPjd8/QgfLQ6yyOQ7fulY552jNURgyeiCaOnA8RzFWSzjztHCjgCVsZUV5PLef6DGPzN2s9fgNX+KeMjRCpHd09pPh1627BnhgPFUE+0L6qXi05HLDuUgymo6Pun4lIHx6mqTvZSlkTKXwseBEyevHuoQXdvK4PritJTCQv9nqzzF2iaA5ClS1Zi+MFn5A9KHgT6OLPJ1wpBlTKHb/ALFDasOWQyWcUc7tLqizkjCQB4br99JwKAANDxOA/wA1qiADof7igkoPT4KIFQyXv4qyAx4CmFit/wA9UOU/JswRtRmvVZMUX7igpAeIGsep6pAPATNrwxdnBSzgDyj0+/my1GcsdyAglz/dRGyPZQZDxjVaUE56pN2I8niiYKUJEwE65muHCuR7pT0WWnHhtcyRBE5azhncsJKYhODdmjVlpUKmWnqnIEUmb4YP/wADN1ujZsYExWBFwQ4ri4ok8f8Aqnoc7SsEOFdUegwrBlLENoSFqF8ObuLiTPBFyoNZ1ZRExkUL5JCVP9/1YJFjir3TQoY70siLIh2oYCoJws9+qIGLk4S5rL1eZW9P3UikZFuuPR80XU0nHRk8xFdlMEcOTPvPwVn7mWLEkHElI2acLWRPNS4ghf7aAkIgqPA8PxWZkcOYoaPDDDumQScAsgAAtYiBHn4L5TlTwXnGoDko5+OxG1IGBx83ilAhx8TTlQWBxZ4UqYajWnDh3lV/qzp56jIuCJDIxV+QTFz80WgOkBxYRLDXzUlPBTJmR6eWyXfwWQ4Et5eKxwVImNeh6+LxH+gL+P3RO7jpnyfzTwzBUwpmCz1ON6rzUT1WEIOA9VyBYufNEo5ADDy/VhXGVA2oFOEBdeuKbjwHeJl+6bwDA+dgsYU5G8xz8rKBtM3sS+gvADpuHiT1UswQwQwYZsgT2siYIOu6s7UxzixEBvEueH3cQwFBOTQU9JRPLE0CCeWlx4n5rcQY/JVOl+zuaHNkM8TYEKZLln7s4FllBmJ31eMguJe5rf5gGr+ELRhI5TFCW4QArcjD4jl+6Q2AQBUwVWeatUINalVljeA83YABJJj89NMs+vN3ASaN339VwATR93XjGA/xSJMiWUxcaqYxZuQwERZ4zxHddoBy6yIP1dHwLJ1LKIQ02CY/cH3VcpYAY8n7/VIQB9vAyod7FYBEjvxJH9P5sPSmUhDIHcpnxdnJMkinP03Sh4XMOzPmw9qEvmNT81BRJM+aqbQkzhYlMyJysOPu4CMj6Ey+kn8VAiTXKczu92GgTEhI5vJ4Cjkl0zonaBJGJAlToD21gwRc9uk8WJRH8TyfjeqxVdnIGPtaJi5jgEeOygwAhsxGpF6EcI5/xLAnMxfIcr+rHoB3C2VAA1SKCFJgp8Is+pKZIP8AatSjCkiuoC4MGG0Sy7ZX4uYw8rUcAOKFCkYQihJ91PBeYsEAh709liiUAStDtD31xtKqbE4iJ356zK84IwOdXlZAeZr2IoV0sn44T4oZXHRGQefMftrNsnkzDCo8Ds1OD0EIT++H92UFAj0kXWOFMo3EIMAjkeciwpOweqQ68o81b3DN2dV0B+aYxjgiHZZ0WCPTQ35oIg1jyzSxESyvAh678WYpqlFgh8Qy1yL0QU4Dzy5Y2BnDILIezCo442RCcMeFd+DaRHOrx3ADzOb3TK5EAAyXqVX6oXBEZADV5M67+qxWdKcrT1c4IIBiQcev/tSxYAMBy+lWsY/H3fA+HSySNNjAK/OSe1jEtCkRCH4Viwokz7Z4Pb3U7Wm5ScbzyXpX/ID8kvfERZQNkND2HZTJjnsNpFCMveuz+6s0e+ZYnkXv88h8WOWEwOBMc8tZZceAdk7E/wAFPZOCqRF+Bf1Y+XShoanQq4cTZsQOhmLs44PugvoKAlPQ7NPTtCXGt1n9ufV1bIwlaKJ7/ld41sqrbHUAEexqXBjw6/59UQPtfPjR2QhzuIqJtTzMNK6motbrAPiH+Ca9KopYk3PNA5o1xAY+CBokBMBIYt7hh2ZFHrDm0HQn+uNoXsyxmUp0UydFmjDd6BiTlrG9Yy8QGqVYcwrxAWMiCBhHUYeqvTdpta/a/igc0NC9+5IjP8WTvrDkAPuIXxNQGxaomIQuivCuJ3klZCS108zPheI2IOgDH4fuy5HBOWP5N/NE0tJ6gI6UfEpheONxkBdnZ576rW0DYDMpcjteWfimT3gOhI9EJ3+6WBYYe1iPYf4ukZuciCBNiIPmaCVJp0ZMnohUNBE+Uhx+CrhFM6GJc8StQEAZRAGBx3H6uBRYKAExHrx5aERxQtJRJ9J/FRzCQkryjHI/FElMxScfxJw91MPMyij6cTYls8wJyE9bNIKVIEC9h9d2A9khGOJevVk4OIDhofHf3UQDhMAK8PHO18JpeCD93XGoeucJx8Q2VhRMJSGM75pdQdUs4L4692EjHnAfivnQSh09vKpQLA9l89JLk8fihQ7u0nZeyZmshYRYBLE8iTPmiyQAEHlRqTFLEwXEGAflLnTJVJunGBrK8c9fPQ0HhRhMCVeJ4d57qoXPiBBw4nTnps9CFOBmJJ6lDesqUChgT47mmTx8VtAUR8ZPE82DhjzCTPHExQVrImw5D3/KhlNZsDR4lQBH1UMCCHBHu4oTphyxaR9ATvNILTKASXMJmBHra3RLuNiH1SIsRO0iB86VmiYGwTen1lkyrlkRlfl3LKQYKRmO96sX5guta/E0jNRSYRPIlfuoxJc6uwfbNQYlCAQv7P5uXACTN0L1n7r61F6IhOfnzX95ss7URiqCxNfO1sCMLoQ4kwjHy3IsqbUOSff8F072UwGMfPn9UzmUEmiMPYzlMDCMMPJZKZwEMYsctkA2G/NTUajISSHACWUoC0pNBfgCMvx7purP0QAHIGFXxYGqcIblQQYzhBmU6Rcp5DFCIZI6LIPZDtlkfbjxFmDXqgslHDuut82G5YIcnORAGfxeSs3IQEDsClxpurlZQ97+ilOKYoI5OFf6oJoBySJP59FeOTE7JmPvj4aZHEoAKQjzG/izXOAnf6AWZpte3UsoCO434qpIUGdK+ET5GtGWXexDqYpRoZCcjxPTlUkLbiFzD7vKaLxrMj15onk7ksBZ47KkuUyIABndnlxtAQD1DGfNUiHIwQ4cAx93awIEejpfFZphwCRiGPmaCBWXGIT4mjqITpVhsAxoUg3zQVuHiI7erMpKzNngcI9VguAFJgVA4Gml4YzxlY96AS/B605oBBYIXj3UEgg4DNhAku6x/IpANkOMH5YsY5hFkHt54iSgXQFooQY4U4Oe7mBQBIHUeaLmKCcjBPJfiK1uoZrGL6JzqCumYME1jk8thsbDRAQeAiYOfzSWiT84wfw1o7BGRFIgPztkMEyiYP0Aj5aJDGpikP8AUbXu8KhJDUn8VcIOidhUv5qjIEcIGFzI/wA0KrdFORzuS1oLPTLwa9s83tqiGWIr1gYZI+ooBiDc6RHnOa0jSxyyXIfNf50ezmPKYsS8BVn798XHEAgScHzewXCHlpFHhOTLRj5iKhHlLLPodS2J+QSUyrpffuxDp5DpxUEMvzMPhqRQIVktDZNgNeTwnmTmuCAfM9/myU5weiKLBCwPmyao4C7GENmsFgLmDvrPBI5bLPgpCgmdIsQOjYl9VAWB1vg9HErUjO/CLMvtj92Oz5XWUjJ1n7qSt8GoeH914nSbyla2quA6QSfvH7oDJDT0g589TSSbmkFel9ZTPGhM8pnxLVIDStjTH65ufs8gzWVmYD1n3ZiwBGDssfi85qQcZXhK8RRVBH5qqFZxMstlgdQlxs+LC7rhqOJ82O9k499UA4HO2T9UjUGNuT+aAtJusp0Sw2ECniiS+acBFYk856oNDeHT2p+RT8vTV6vC4HHD5aFAzZZC7rukUDxHVQBAQhr7fLWhMCfuqIEOXzTR0OqBWBkTmsfjCN4qalLPNGEoUlHv6CscKKWE8iOZafATiEsfndscAPCZI82SBIXJXY97XONsyTE63dI6HlxQUjgjjwvuuq4IYvqiX5dELLyXkDRhFHK/qsiRoCEuB6LCaTQ6d/NRBhkO4oxFgHYPiqApiHd8VBIbRhUzI1TaYcxDn+KMtZfgw2KqL6J8VhvUpR5oim+mY8WOA4EqTPHqjWkKEDU3BAl8jHdYxyRRrqOOrPABWCHF1CIjo9UflbPVRyWLwMVVWqQmq9opVZCJDu2eExeSrxRSS1SX4oampERwnVgD6A0eOSoAIOvOykWSWaX6p4y9talTdNXAkbkZXP8ARUozyZpTAkphzVI1jm8PoIUsBfOt7cyVKkmuLt3BQ98tCKZpz+3msig6MPFaKMM7ndiGdjLE5QDN42w/JSd5y6eGFHVYgG9Fw4yWkAAljTmIIhPutUKk8fdbztp5XmtHLrX/AB5Pip83l/zlf//Z'
  11. describe('src/Core', () => {
  12. const RealCreateObjectUrl = global.URL.createObjectURL
  13. beforeEach(() => {
  14. jest.spyOn(utils, 'findDOMElement').mockImplementation(path => {
  15. return 'some config...'
  16. })
  17. jest.spyOn(utils, 'createThumbnail').mockImplementation(path => {
  18. return Promise.resolve(sampleImageDataURI)
  19. })
  20. utils.createThumbnail.mockClear()
  21. global.URL.createObjectURL = jest.fn().mockReturnValue('newUrl')
  22. })
  23. afterEach(() => {
  24. global.URL.createObjectURL = RealCreateObjectUrl
  25. })
  26. it('should expose a class', () => {
  27. const core = Core()
  28. expect(core.constructor.name).toEqual('Uppy')
  29. })
  30. it('should have a string `id` option that defaults to "uppy"', () => {
  31. const core = Core()
  32. expect(core.getID()).toEqual('uppy')
  33. const core2 = Core({ id: 'profile' })
  34. expect(core2.getID()).toEqual('profile')
  35. })
  36. describe('plugins', () => {
  37. it('should add a plugin to the plugin stack', () => {
  38. const core = Core()
  39. core.use(AcquirerPlugin1)
  40. expect(Object.keys(core.plugins.acquirer).length).toEqual(1)
  41. })
  42. it('should prevent the same plugin from being added more than once', () => {
  43. const core = Core()
  44. core.use(AcquirerPlugin1)
  45. expect(() => {
  46. core.use(AcquirerPlugin1)
  47. }).toThrowErrorMatchingSnapshot()
  48. })
  49. it('should not be able to add an invalid plugin', () => {
  50. const core = Core()
  51. expect(() => {
  52. core.use(InvalidPlugin)
  53. }).toThrowErrorMatchingSnapshot()
  54. })
  55. it('should not be able to add a plugin that has no id', () => {
  56. const core = Core()
  57. expect(() =>
  58. core.use(InvalidPluginWithoutId)
  59. ).toThrowErrorMatchingSnapshot()
  60. })
  61. it('should not be able to add a plugin that has no type', () => {
  62. const core = Core()
  63. expect(() =>
  64. core.use(InvalidPluginWithoutType)
  65. ).toThrowErrorMatchingSnapshot()
  66. })
  67. it('should return the plugin that matches the specified name', () => {
  68. const core = new Core()
  69. expect(core.getPlugin('foo')).toEqual(false)
  70. core.use(AcquirerPlugin1)
  71. const plugin = core.getPlugin('TestSelector1')
  72. expect(plugin.id).toEqual('TestSelector1')
  73. expect(plugin instanceof Plugin)
  74. })
  75. it('should call the specified method on all the plugins', () => {
  76. const core = new Core()
  77. core.use(AcquirerPlugin1)
  78. core.use(AcquirerPlugin2)
  79. core.iteratePlugins(plugin => {
  80. plugin.run('hello')
  81. })
  82. expect(core.plugins.acquirer[0].mocks.run.mock.calls.length).toEqual(1)
  83. expect(core.plugins.acquirer[0].mocks.run.mock.calls[0]).toEqual([
  84. 'hello'
  85. ])
  86. expect(core.plugins.acquirer[1].mocks.run.mock.calls.length).toEqual(1)
  87. expect(core.plugins.acquirer[1].mocks.run.mock.calls[0]).toEqual([
  88. 'hello'
  89. ])
  90. })
  91. it('should uninstall and the remove the specified plugin', () => {
  92. const core = new Core()
  93. core.use(AcquirerPlugin1)
  94. core.use(AcquirerPlugin2)
  95. expect(Object.keys(core.plugins.acquirer).length).toEqual(2)
  96. const plugin = core.getPlugin('TestSelector1')
  97. core.removePlugin(plugin)
  98. expect(Object.keys(core.plugins.acquirer).length).toEqual(1)
  99. expect(plugin.mocks.uninstall.mock.calls.length).toEqual(1)
  100. expect(core.plugins.acquirer[0].mocks.run.mock.calls.length).toEqual(0)
  101. })
  102. })
  103. describe('state', () => {
  104. it('should update all the plugins with the new state when the updateAll method is called', () => {
  105. const core = new Core()
  106. core.use(AcquirerPlugin1)
  107. core.use(AcquirerPlugin2)
  108. core.updateAll({ foo: 'bar' })
  109. expect(core.plugins.acquirer[0].mocks.update.mock.calls.length).toEqual(1)
  110. expect(core.plugins.acquirer[0].mocks.update.mock.calls[0]).toEqual([
  111. { foo: 'bar' }
  112. ])
  113. expect(core.plugins.acquirer[1].mocks.update.mock.calls.length).toEqual(1)
  114. expect(core.plugins.acquirer[1].mocks.update.mock.calls[0]).toEqual([
  115. { foo: 'bar' }
  116. ])
  117. })
  118. it('should update the state', () => {
  119. const core = new Core()
  120. const stateUpdateEventMock = jest.fn()
  121. core.on('state-update', stateUpdateEventMock)
  122. core.use(AcquirerPlugin1)
  123. core.use(AcquirerPlugin2)
  124. core.setState({ foo: 'bar', bee: 'boo' })
  125. core.setState({ foo: 'baar' })
  126. const newState = {
  127. bee: 'boo',
  128. capabilities: { resumableUploads: false },
  129. files: {},
  130. foo: 'baar',
  131. info: { isHidden: true, message: '', type: 'info' },
  132. meta: {},
  133. plugins: {},
  134. totalProgress: 0
  135. }
  136. expect(core.state).toEqual(newState)
  137. expect(core.plugins.acquirer[0].mocks.update.mock.calls[1]).toEqual([
  138. newState
  139. ])
  140. expect(core.plugins.acquirer[1].mocks.update.mock.calls[1]).toEqual([
  141. newState
  142. ])
  143. expect(stateUpdateEventMock.mock.calls.length).toEqual(2)
  144. // current state
  145. expect(stateUpdateEventMock.mock.calls[1][0]).toEqual({
  146. bee: 'boo',
  147. capabilities: { resumableUploads: false },
  148. files: {},
  149. foo: 'bar',
  150. info: { isHidden: true, message: '', type: 'info' },
  151. meta: {},
  152. plugins: {},
  153. totalProgress: 0
  154. })
  155. // new state
  156. expect(stateUpdateEventMock.mock.calls[1][1]).toEqual({
  157. bee: 'boo',
  158. capabilities: { resumableUploads: false },
  159. files: {},
  160. foo: 'baar',
  161. info: { isHidden: true, message: '', type: 'info' },
  162. meta: {},
  163. plugins: {},
  164. totalProgress: 0
  165. })
  166. })
  167. it('should get the state', () => {
  168. const core = new Core()
  169. core.setState({ foo: 'bar' })
  170. expect(core.getState()).toEqual({
  171. capabilities: { resumableUploads: false },
  172. files: {},
  173. foo: 'bar',
  174. info: { isHidden: true, message: '', type: 'info' },
  175. meta: {},
  176. plugins: {},
  177. totalProgress: 0
  178. })
  179. })
  180. })
  181. it('should reset when the reset method is called', () => {
  182. const core = new Core()
  183. // const corePauseEventMock = jest.fn()
  184. const coreCancelEventMock = jest.fn()
  185. const coreStateUpdateEventMock = jest.fn()
  186. core.on('cancel-all', coreCancelEventMock)
  187. core.on('state-update', coreStateUpdateEventMock)
  188. core.setState({ foo: 'bar', totalProgress: 30 })
  189. core.reset()
  190. // expect(corePauseEventMock.mock.calls.length).toEqual(1)
  191. expect(coreCancelEventMock.mock.calls.length).toEqual(1)
  192. expect(coreStateUpdateEventMock.mock.calls.length).toEqual(2)
  193. expect(coreStateUpdateEventMock.mock.calls[1][1]).toEqual({
  194. capabilities: { resumableUploads: false },
  195. files: {},
  196. foo: 'bar',
  197. info: { isHidden: true, message: '', type: 'info' },
  198. meta: {},
  199. plugins: {},
  200. totalProgress: 0
  201. })
  202. })
  203. it('should close, reset and uninstall when the close method is called', () => {
  204. const core = new Core()
  205. core.use(AcquirerPlugin1)
  206. // const corePauseEventMock = jest.fn()
  207. const coreCancelEventMock = jest.fn()
  208. const coreStateUpdateEventMock = jest.fn()
  209. // core.on('pause-all', corePauseEventMock)
  210. core.on('cancel-all', coreCancelEventMock)
  211. core.on('state-update', coreStateUpdateEventMock)
  212. core.close()
  213. // expect(corePauseEventMock.mock.calls.length).toEqual(1)
  214. expect(coreCancelEventMock.mock.calls.length).toEqual(1)
  215. expect(coreStateUpdateEventMock.mock.calls.length).toEqual(1)
  216. expect(coreStateUpdateEventMock.mock.calls[0][1]).toEqual({
  217. capabilities: { resumableUploads: false },
  218. files: {},
  219. info: { isHidden: true, message: '', type: 'info' },
  220. meta: {},
  221. plugins: {},
  222. totalProgress: 0
  223. })
  224. expect(core.plugins.acquirer[0].mocks.uninstall.mock.calls.length).toEqual(
  225. 1
  226. )
  227. })
  228. describe('preprocessors', () => {
  229. it('should add a preprocessor', () => {
  230. const core = new Core()
  231. const preprocessor = function () {}
  232. core.addPreProcessor(preprocessor)
  233. expect(core.preProcessors[0]).toEqual(preprocessor)
  234. })
  235. it('should remove a preprocessor', () => {
  236. const core = new Core()
  237. const preprocessor1 = function () {}
  238. const preprocessor2 = function () {}
  239. const preprocessor3 = function () {}
  240. core.addPreProcessor(preprocessor1)
  241. core.addPreProcessor(preprocessor2)
  242. core.addPreProcessor(preprocessor3)
  243. expect(core.preProcessors.length).toEqual(3)
  244. core.removePreProcessor(preprocessor2)
  245. expect(core.preProcessors.length).toEqual(2)
  246. })
  247. it('should execute all the preprocessors when uploading a file', () => {
  248. const core = new Core()
  249. const preprocessor1 = jest.fn()
  250. const preprocessor2 = jest.fn()
  251. core.addPreProcessor(preprocessor1)
  252. core.addPreProcessor(preprocessor2)
  253. return core
  254. .addFile({
  255. source: 'jest',
  256. name: 'foo.jpg',
  257. type: 'image/jpeg',
  258. data: utils.dataURItoFile(sampleImageDataURI, {})
  259. })
  260. .then(() => core.upload())
  261. .then(() => {
  262. const fileId = Object.keys(core.state.files)[0]
  263. expect(preprocessor1.mock.calls.length).toEqual(1)
  264. expect(preprocessor1.mock.calls[0][0].length).toEqual(1)
  265. expect(preprocessor1.mock.calls[0][0][0]).toEqual(fileId)
  266. expect(preprocessor2.mock.calls[0][0].length).toEqual(1)
  267. expect(preprocessor2.mock.calls[0][0][0]).toEqual(fileId)
  268. })
  269. })
  270. it('should update the file progress state when preprocess-progress event is fired', () => {
  271. const core = new Core()
  272. core.run()
  273. return core
  274. .addFile({
  275. source: 'jest',
  276. name: 'foo.jpg',
  277. type: 'image/jpeg',
  278. data: utils.dataURItoFile(sampleImageDataURI, {})
  279. })
  280. .then(() => {
  281. const fileId = Object.keys(core.state.files)[0]
  282. core.emit('preprocess-progress', fileId, {
  283. mode: 'determinate',
  284. message: 'something',
  285. value: 0
  286. })
  287. expect(core.state.files[fileId].progress).toEqual({
  288. percentage: 0,
  289. bytesUploaded: 0,
  290. bytesTotal: 17175,
  291. uploadComplete: false,
  292. uploadStarted: false,
  293. preprocess: { mode: 'determinate', message: 'something', value: 0 }
  294. })
  295. })
  296. })
  297. it('should update the file progress state when preprocess-complete event is fired', () => {
  298. const core = new Core()
  299. core.run()
  300. return core
  301. .addFile({
  302. source: 'jest',
  303. name: 'foo.jpg',
  304. type: 'image/jpeg',
  305. data: utils.dataURItoFile(sampleImageDataURI, {})
  306. })
  307. .then(() => {
  308. const fileId = Object.keys(core.state.files)[0]
  309. core.emit('preprocess-complete', fileId, {
  310. mode: 'determinate',
  311. message: 'something',
  312. value: 0
  313. })
  314. expect(core.state.files[fileId].progress).toEqual({
  315. percentage: 0,
  316. bytesUploaded: 0,
  317. bytesTotal: 17175,
  318. uploadComplete: false,
  319. uploadStarted: false
  320. })
  321. })
  322. })
  323. })
  324. describe('postprocessors', () => {
  325. it('should add a postprocessor', () => {
  326. const core = new Core()
  327. const postprocessor = function () {}
  328. core.addPostProcessor(postprocessor)
  329. expect(core.postProcessors[0]).toEqual(postprocessor)
  330. })
  331. it('should remove a postprocessor', () => {
  332. const core = new Core()
  333. const postprocessor1 = function () {}
  334. const postprocessor2 = function () {}
  335. const postprocessor3 = function () {}
  336. core.addPostProcessor(postprocessor1)
  337. core.addPostProcessor(postprocessor2)
  338. core.addPostProcessor(postprocessor3)
  339. expect(core.postProcessors.length).toEqual(3)
  340. core.removePostProcessor(postprocessor2)
  341. expect(core.postProcessors.length).toEqual(2)
  342. })
  343. it('should execute all the postprocessors when uploading a file', () => {
  344. const core = new Core()
  345. const postprocessor1 = jest.fn()
  346. const postprocessor2 = jest.fn()
  347. core.addPostProcessor(postprocessor1)
  348. core.addPostProcessor(postprocessor2)
  349. return core
  350. .addFile({
  351. source: 'jest',
  352. name: 'foo.jpg',
  353. type: 'image/jpeg',
  354. data: utils.dataURItoFile(sampleImageDataURI, {})
  355. })
  356. .then(() => core.upload())
  357. .then(() => {
  358. expect(postprocessor1.mock.calls.length).toEqual(1)
  359. // const lastModifiedTime = new Date()
  360. // const fileId = 'foojpg' + lastModifiedTime.getTime()
  361. const fileId = 'uppy-foojpg-image'
  362. expect(postprocessor1.mock.calls[0][0].length).toEqual(1)
  363. expect(postprocessor1.mock.calls[0][0][0].substring(0, 17)).toEqual(
  364. fileId.substring(0, 17)
  365. )
  366. expect(postprocessor2.mock.calls[0][0].length).toEqual(1)
  367. expect(postprocessor2.mock.calls[0][0][0].substring(0, 17)).toEqual(
  368. fileId.substring(0, 17)
  369. )
  370. })
  371. })
  372. it('should update the file progress state when postprocess-progress event is fired', () => {
  373. const core = new Core()
  374. core.run()
  375. return core
  376. .addFile({
  377. source: 'jest',
  378. name: 'foo.jpg',
  379. type: 'image/jpeg',
  380. data: utils.dataURItoFile(sampleImageDataURI, {})
  381. })
  382. .then(() => {
  383. const fileId = Object.keys(core.state.files)[0]
  384. core.emit('postprocess-progress', fileId, {
  385. mode: 'determinate',
  386. message: 'something',
  387. value: 0
  388. })
  389. expect(core.state.files[fileId].progress).toEqual({
  390. percentage: 0,
  391. bytesUploaded: 0,
  392. bytesTotal: 17175,
  393. uploadComplete: false,
  394. uploadStarted: false,
  395. postprocess: { mode: 'determinate', message: 'something', value: 0 }
  396. })
  397. })
  398. })
  399. it('should update the file progress state when postprocess-complete event is fired', () => {
  400. const core = new Core()
  401. core.run()
  402. return core
  403. .addFile({
  404. source: 'jest',
  405. name: 'foo.jpg',
  406. type: 'image/jpeg',
  407. data: utils.dataURItoFile(sampleImageDataURI, {})
  408. })
  409. .then(() => {
  410. const fileId = Object.keys(core.state.files)[0]
  411. core.emit('postprocess-complete', fileId, {
  412. mode: 'determinate',
  413. message: 'something',
  414. value: 0
  415. })
  416. expect(core.state.files[fileId].progress).toEqual({
  417. percentage: 0,
  418. bytesUploaded: 0,
  419. bytesTotal: 17175,
  420. uploadComplete: false,
  421. uploadStarted: false
  422. })
  423. })
  424. })
  425. })
  426. describe('uploaders', () => {
  427. it('should add an uploader', () => {
  428. const core = new Core()
  429. const uploader = function () {}
  430. core.addUploader(uploader)
  431. expect(core.uploaders[0]).toEqual(uploader)
  432. })
  433. it('should remove an uploader', () => {
  434. const core = new Core()
  435. const uploader1 = function () {}
  436. const uploader2 = function () {}
  437. const uploader3 = function () {}
  438. core.addUploader(uploader1)
  439. core.addUploader(uploader2)
  440. core.addUploader(uploader3)
  441. expect(core.uploaders.length).toEqual(3)
  442. core._removeUploader(uploader2)
  443. expect(core.uploaders.length).toEqual(2)
  444. })
  445. })
  446. describe('adding a file', () => {
  447. it('should call onBeforeFileAdded if it was specified in the options when initailising the class', () => {
  448. const onBeforeFileAdded = jest.fn(value => {
  449. return Promise.resolve()
  450. })
  451. const core = new Core({
  452. onBeforeFileAdded
  453. })
  454. return core
  455. .addFile({
  456. source: 'jest',
  457. name: 'foo.jpg',
  458. type: 'image/jpeg',
  459. data: utils.dataURItoFile(sampleImageDataURI, {})
  460. })
  461. .then(() => {
  462. expect(onBeforeFileAdded.mock.calls.length).toEqual(1)
  463. expect(onBeforeFileAdded.mock.calls[0][0].name).toEqual('foo.jpg')
  464. expect(onBeforeFileAdded.mock.calls[0][1]).toEqual({})
  465. })
  466. })
  467. it('should add a file', () => {
  468. const fileData = utils.dataURItoFile(sampleImageDataURI, {})
  469. const fileAddedEventMock = jest.fn()
  470. const core = new Core()
  471. core.run()
  472. core.on('file-added', fileAddedEventMock)
  473. return core
  474. .addFile({
  475. source: 'jest',
  476. name: 'foo.jpg',
  477. type: 'image/jpeg',
  478. data: fileData
  479. })
  480. .then(() => {
  481. const fileId = Object.keys(core.state.files)[0]
  482. const newFile = {
  483. extension: 'jpg',
  484. id: fileId,
  485. isRemote: false,
  486. meta: { name: 'foo.jpg', type: 'image/jpeg' },
  487. name: 'foo.jpg',
  488. preview: sampleImageDataURI,
  489. data: fileData,
  490. progress: {
  491. bytesTotal: 17175,
  492. bytesUploaded: 0,
  493. percentage: 0,
  494. uploadComplete: false,
  495. uploadStarted: false
  496. },
  497. remote: '',
  498. size: 17175,
  499. source: 'jest',
  500. type: 'image/jpeg'
  501. }
  502. expect(core.state.files[fileId]).toEqual(newFile)
  503. newFile.preview = undefined // not sure why this happens.. needs further investigation
  504. expect(fileAddedEventMock.mock.calls[0][0]).toEqual(newFile)
  505. })
  506. })
  507. it('should not allow a file that does not meet the restrictions', () => {
  508. const core = new Core({
  509. restrictions: {
  510. allowedFileTypes: ['image/gif']
  511. }
  512. })
  513. return core
  514. .addFile({
  515. source: 'jest',
  516. name: 'foo.jpg',
  517. type: 'image/jpeg',
  518. data: utils.dataURItoFile(sampleImageDataURI, {})
  519. })
  520. .then(() => {
  521. throw new Error('File was allowed through')
  522. })
  523. .catch(e => {
  524. expect(e.message).toEqual('File not allowed')
  525. })
  526. })
  527. it('should work with restriction errors that are not Error class instances', () => {
  528. const core = new Core({
  529. onBeforeFileAdded () {
  530. return Promise.reject('a plain string') // eslint-disable-line prefer-promise-reject-errors
  531. }
  532. })
  533. return expect(core.addFile({
  534. source: 'jest',
  535. name: 'foo.jpg',
  536. type: 'image/jpeg',
  537. data: null
  538. })).rejects.toMatchObject({ message: 'onBeforeFileAdded: a plain string' })
  539. })
  540. it('should call utils.generatePreview when file-added is triggered and thumbnail generation is allowed', () => {
  541. const core = new Core({
  542. }).run()
  543. const file = {
  544. type: 'image/jpeg',
  545. isRemote: false
  546. }
  547. core.emit('file-added', file)
  548. expect(utils.createThumbnail).toHaveBeenCalledTimes(1)
  549. expect(utils.createThumbnail.mock.calls[0][1]).toEqual(200)
  550. })
  551. it('should return an object url of the image when file-added is triggered and thumbnail generation is disabled', () => {
  552. const core = new Core({
  553. thumbnailGeneration: false
  554. }).run()
  555. const file = {
  556. type: 'image/jpeg',
  557. isRemote: false,
  558. data: 'foo'
  559. }
  560. core.emit('file-added', file)
  561. expect(URL.createObjectURL).toHaveBeenCalledTimes(1)
  562. expect(URL.createObjectURL).toHaveBeenCalledWith('foo')
  563. })
  564. })
  565. describe('uploading a file', () => {
  566. it('should return a { successful, failed } pair containing file objects', () => {
  567. const core = new Core().run()
  568. core.addUploader((fileIDs) => Promise.resolve())
  569. return Promise.all([
  570. core.addFile({ source: 'jest', name: 'foo.jpg', type: 'image/jpeg', data: new Uint8Array() }),
  571. core.addFile({ source: 'jest', name: 'bar.jpg', type: 'image/jpeg', data: new Uint8Array() })
  572. ]).then(() => {
  573. return expect(core.upload()).resolves.toMatchObject({
  574. successful: [
  575. { name: 'foo.jpg' },
  576. { name: 'bar.jpg' }
  577. ],
  578. failed: []
  579. })
  580. })
  581. })
  582. it('should return files with errors in the { failed } key', () => {
  583. const core = new Core().run()
  584. core.addUploader((fileIDs) => {
  585. fileIDs.forEach((fileID) => {
  586. if (/bar/.test(core.getFile(fileID).name)) {
  587. core.emit('upload-error', fileID, new Error('This is bar and I do not like bar'))
  588. }
  589. })
  590. return Promise.resolve()
  591. })
  592. return Promise.all([
  593. core.addFile({ source: 'jest', name: 'foo.jpg', type: 'image/jpeg', data: new Uint8Array() }),
  594. core.addFile({ source: 'jest', name: 'bar.jpg', type: 'image/jpeg', data: new Uint8Array() })
  595. ]).then(() => {
  596. return expect(core.upload()).resolves.toMatchObject({
  597. successful: [
  598. { name: 'foo.jpg' }
  599. ],
  600. failed: [
  601. { name: 'bar.jpg', error: 'This is bar and I do not like bar' }
  602. ]
  603. })
  604. })
  605. })
  606. })
  607. describe('removing a file', () => {
  608. it('should remove the file', () => {
  609. const fileRemovedEventMock = jest.fn()
  610. const core = new Core()
  611. core.on('file-removed', fileRemovedEventMock)
  612. core.run()
  613. return core
  614. .addFile({
  615. source: 'jest',
  616. name: 'foo.jpg',
  617. type: 'image/jpeg',
  618. data: utils.dataURItoFile(sampleImageDataURI, {})
  619. })
  620. .then(() => {
  621. const fileId = Object.keys(core.state.files)[0]
  622. expect(Object.keys(core.state.files).length).toEqual(1)
  623. core.setState({
  624. totalProgress: 50
  625. })
  626. core.removeFile(fileId)
  627. expect(Object.keys(core.state.files).length).toEqual(0)
  628. expect(fileRemovedEventMock.mock.calls[0][0]).toEqual(fileId)
  629. expect(core.state.totalProgress).toEqual(0)
  630. })
  631. })
  632. })
  633. describe('restoring a file', () => {
  634. xit('should restore a file', () => {})
  635. xit("should fail to restore a file if it doesn't exist", () => {})
  636. })
  637. describe('get a file', () => {
  638. it('should get the specified file', () => {
  639. const core = new Core()
  640. return core
  641. .addFile({
  642. source: 'jest',
  643. name: 'foo.jpg',
  644. type: 'image/jpeg',
  645. data: utils.dataURItoFile(sampleImageDataURI, {})
  646. })
  647. .then(() => {
  648. const fileId = Object.keys(core.state.files)[0]
  649. expect(core.getFile(fileId).name).toEqual('foo.jpg')
  650. expect(core.getFile('non existant file')).toEqual(undefined)
  651. })
  652. })
  653. })
  654. describe('meta data', () => {
  655. it('should set meta data by calling setMeta', () => {
  656. const core = new Core({
  657. meta: { foo2: 'bar2' }
  658. })
  659. core.setMeta({ foo: 'bar', bur: 'mur' })
  660. core.setMeta({ boo: 'moo', bur: 'fur' })
  661. expect(core.state.meta).toEqual({
  662. foo: 'bar',
  663. foo2: 'bar2',
  664. boo: 'moo',
  665. bur: 'fur'
  666. })
  667. })
  668. it('should update meta data for a file by calling updateMeta', () => {
  669. const core = new Core()
  670. return core
  671. .addFile({
  672. source: 'jest',
  673. name: 'foo.jpg',
  674. type: 'image/jpeg',
  675. data: utils.dataURItoFile(sampleImageDataURI, {})
  676. })
  677. .then(() => {
  678. const fileId = Object.keys(core.state.files)[0]
  679. core.setFileMeta(fileId, { foo: 'bar', bur: 'mur' })
  680. core.setFileMeta(fileId, { boo: 'moo', bur: 'fur' })
  681. expect(core.state.files[fileId].meta).toEqual({
  682. name: 'foo.jpg',
  683. type: 'image/jpeg',
  684. foo: 'bar',
  685. bur: 'fur',
  686. boo: 'moo'
  687. })
  688. })
  689. })
  690. })
  691. describe('progress', () => {
  692. it('should calculate the progress of a file upload', () => {
  693. const core = new Core()
  694. return core
  695. .addFile({
  696. source: 'jest',
  697. name: 'foo.jpg',
  698. type: 'image/jpeg',
  699. data: utils.dataURItoFile(sampleImageDataURI, {})
  700. })
  701. .then(() => {
  702. const fileId = Object.keys(core.state.files)[0]
  703. core._calculateProgress({
  704. id: fileId,
  705. bytesUploaded: 12345,
  706. bytesTotal: 17175
  707. })
  708. expect(core.state.files[fileId].progress).toEqual({
  709. percentage: 71,
  710. bytesUploaded: 12345,
  711. bytesTotal: 17175,
  712. uploadComplete: false,
  713. uploadStarted: false
  714. })
  715. core._calculateProgress({
  716. id: fileId,
  717. bytesUploaded: 17175,
  718. bytesTotal: 17175
  719. })
  720. expect(core.state.files[fileId].progress).toEqual({
  721. percentage: 100,
  722. bytesUploaded: 17175,
  723. bytesTotal: 17175,
  724. uploadComplete: false,
  725. uploadStarted: false
  726. })
  727. })
  728. })
  729. it('should calculate the total progress of all file uploads', () => {
  730. const core = new Core()
  731. return core
  732. .addFile({
  733. source: 'jest',
  734. name: 'foo.jpg',
  735. type: 'image/jpeg',
  736. data: utils.dataURItoFile(sampleImageDataURI, {})
  737. })
  738. .then(() => {
  739. return core
  740. .addFile({
  741. source: 'jest',
  742. name: 'foo2.jpg',
  743. type: 'image/jpeg',
  744. data: utils.dataURItoFile(sampleImageDataURI, {})
  745. })
  746. }).then(() => {
  747. const fileId1 = Object.keys(core.state.files)[0]
  748. const fileId2 = Object.keys(core.state.files)[1]
  749. core.state.files[fileId1].progress.uploadStarted = new Date()
  750. core.state.files[fileId2].progress.uploadStarted = new Date()
  751. core._calculateProgress({
  752. id: fileId1,
  753. bytesUploaded: 12345,
  754. bytesTotal: 17175
  755. })
  756. core._calculateProgress({
  757. id: fileId2,
  758. bytesUploaded: 10201,
  759. bytesTotal: 17175
  760. })
  761. core._calculateTotalProgress()
  762. expect(core.state.totalProgress).toEqual(65)
  763. })
  764. })
  765. it('should reset the progress', () => {
  766. const resetProgressEvent = jest.fn()
  767. const core = new Core()
  768. core.run()
  769. core.on('reset-progress', resetProgressEvent)
  770. return core
  771. .addFile({
  772. source: 'jest',
  773. name: 'foo.jpg',
  774. type: 'image/jpeg',
  775. data: utils.dataURItoFile(sampleImageDataURI, {})
  776. })
  777. .then(() => {
  778. return core
  779. .addFile({
  780. source: 'jest',
  781. name: 'foo2.jpg',
  782. type: 'image/jpeg',
  783. data: utils.dataURItoFile(sampleImageDataURI, {})
  784. })
  785. }).then(() => {
  786. const fileId1 = Object.keys(core.state.files)[0]
  787. const fileId2 = Object.keys(core.state.files)[1]
  788. core.state.files[fileId1].progress.uploadStarted = new Date()
  789. core.state.files[fileId2].progress.uploadStarted = new Date()
  790. core._calculateProgress({
  791. id: fileId1,
  792. bytesUploaded: 12345,
  793. bytesTotal: 17175
  794. })
  795. core._calculateProgress({
  796. id: fileId2,
  797. bytesUploaded: 10201,
  798. bytesTotal: 17175
  799. })
  800. core._calculateTotalProgress()
  801. expect(core.state.totalProgress).toEqual(65)
  802. core.resetProgress()
  803. expect(core.state.files[fileId1].progress).toEqual({
  804. percentage: 0,
  805. bytesUploaded: 0,
  806. bytesTotal: 17175,
  807. uploadComplete: false,
  808. uploadStarted: false
  809. })
  810. expect(core.state.files[fileId2].progress).toEqual({
  811. percentage: 0,
  812. bytesUploaded: 0,
  813. bytesTotal: 17175,
  814. uploadComplete: false,
  815. uploadStarted: false
  816. })
  817. expect(core.state.totalProgress).toEqual(0)
  818. expect(resetProgressEvent.mock.calls.length).toEqual(1)
  819. })
  820. })
  821. })
  822. describe('checkRestrictions', () => {
  823. it('should enforce the maxNumberOfFiles rule', () => {
  824. const core = new Core({
  825. autoProceed: false,
  826. restrictions: {
  827. maxNumberOfFiles: 1
  828. }
  829. })
  830. // add 2 files
  831. core.addFile({
  832. source: 'jest',
  833. name: 'foo1.jpg',
  834. type: 'image/jpeg',
  835. data: utils.dataURItoFile(sampleImageDataURI, {})
  836. })
  837. return expect(core.addFile({
  838. source: 'jest',
  839. name: 'foo2.jpg',
  840. type: 'image/jpeg',
  841. data: utils.dataURItoFile(sampleImageDataURI, {})
  842. })).rejects.toMatchObject({ message: 'File not allowed' }).then(() => {
  843. expect(core.state.info.message).toEqual('You can only upload 1 file')
  844. })
  845. })
  846. xit('should enforce the minNumberOfFiles rule', () => {})
  847. it('should enfore the allowedFileTypes rule', () => {
  848. const core = new Core({
  849. autoProceed: false,
  850. restrictions: {
  851. allowedFileTypes: ['image/gif', 'image/png']
  852. }
  853. })
  854. return expect(core.addFile({
  855. source: 'jest',
  856. name: 'foo2.jpg',
  857. type: 'image/jpeg',
  858. data: utils.dataURItoFile(sampleImageDataURI, {})
  859. })).rejects.toMatchObject({ message: 'File not allowed' }).then(() => {
  860. expect(core.state.info.message).toEqual('You can only upload: image/gif, image/png')
  861. })
  862. })
  863. it('should enforce the maxFileSize rule', () => {
  864. const core = new Core({
  865. autoProceed: false,
  866. restrictions: {
  867. maxFileSize: 1234
  868. }
  869. })
  870. return expect(core.addFile({
  871. source: 'jest',
  872. name: 'foo.jpg',
  873. type: 'image/jpeg',
  874. data: utils.dataURItoFile(sampleImageDataURI, {})
  875. })).rejects.toMatchObject({ message: 'File not allowed' }).then(() => {
  876. expect(core.state.info.message).toEqual('This file exceeds maximum allowed size of 1.2 KB')
  877. })
  878. })
  879. })
  880. describe('actions', () => {
  881. it('should update the state when receiving the error event', () => {
  882. const core = new Core()
  883. core.run()
  884. core.emit('error', new Error('foooooo'))
  885. expect(core.state.error).toEqual('foooooo')
  886. })
  887. it('should update the state when receiving the upload-error event', () => {
  888. const core = new Core()
  889. core.run()
  890. core.state.files['fileId'] = {
  891. name: 'filename'
  892. }
  893. core.emit('upload-error', 'fileId', { message: 'this is the error' })
  894. expect(core.state.info).toEqual({'message': 'Failed to upload filename', 'details': 'this is the error', 'isHidden': false, 'type': 'error'})
  895. })
  896. it('should reset the error state when receiving the upload event', () => {
  897. const core = new Core()
  898. core.run()
  899. core.emit('error', { foo: 'bar' })
  900. core.emit('upload')
  901. expect(core.state.error).toEqual(null)
  902. })
  903. })
  904. describe('updateOnlineStatus', () => {
  905. const RealNavigatorOnline = global.window.navigator.onLine
  906. function mockNavigatorOnline (status) {
  907. Object.defineProperty(
  908. global.window.navigator,
  909. 'onLine',
  910. {
  911. value: status,
  912. writable: true
  913. }
  914. )
  915. }
  916. afterEach(() => {
  917. global.window.navigator.onLine = RealNavigatorOnline
  918. })
  919. it('should emit the correct event based on whether there is a network connection', () => {
  920. const onlineEventMock = jest.fn()
  921. const offlineEventMock = jest.fn()
  922. const backOnlineEventMock = jest.fn()
  923. const core = new Core()
  924. core.on('is-offline', offlineEventMock)
  925. core.on('is-online', onlineEventMock)
  926. core.on('back-online', backOnlineEventMock)
  927. mockNavigatorOnline(true)
  928. core.updateOnlineStatus()
  929. expect(onlineEventMock.mock.calls.length).toEqual(1)
  930. expect(offlineEventMock.mock.calls.length).toEqual(0)
  931. expect(backOnlineEventMock.mock.calls.length).toEqual(0)
  932. mockNavigatorOnline(false)
  933. core.updateOnlineStatus()
  934. expect(onlineEventMock.mock.calls.length).toEqual(1)
  935. expect(offlineEventMock.mock.calls.length).toEqual(1)
  936. expect(backOnlineEventMock.mock.calls.length).toEqual(0)
  937. mockNavigatorOnline(true)
  938. core.updateOnlineStatus()
  939. expect(onlineEventMock.mock.calls.length).toEqual(2)
  940. expect(offlineEventMock.mock.calls.length).toEqual(1)
  941. expect(backOnlineEventMock.mock.calls.length).toEqual(1)
  942. })
  943. })
  944. describe('info', () => {
  945. it('should set a string based message to be displayed infinitely', () => {
  946. const infoVisibleEvent = jest.fn()
  947. const core = new Core()
  948. core.run()
  949. core.on('info-visible', infoVisibleEvent)
  950. core.info('This is the message', 'info', 0)
  951. expect(core.state.info).toEqual({
  952. isHidden: false,
  953. type: 'info',
  954. message: 'This is the message',
  955. details: null
  956. })
  957. expect(infoVisibleEvent.mock.calls.length).toEqual(1)
  958. expect(typeof core.infoTimeoutID).toEqual('undefined')
  959. })
  960. it('should set a object based message to be displayed infinitely', () => {
  961. const infoVisibleEvent = jest.fn()
  962. const core = new Core()
  963. core.run()
  964. core.on('info-visible', infoVisibleEvent)
  965. core.info({
  966. message: 'This is the message',
  967. details: {
  968. foo: 'bar'
  969. }
  970. }, 'warning', 0)
  971. expect(core.state.info).toEqual({
  972. isHidden: false,
  973. type: 'warning',
  974. message: 'This is the message',
  975. details: {
  976. foo: 'bar'
  977. }
  978. })
  979. expect(infoVisibleEvent.mock.calls.length).toEqual(1)
  980. expect(typeof core.infoTimeoutID).toEqual('undefined')
  981. })
  982. it('should set an info message to be displayed for a period of time before hiding', (done) => {
  983. const infoVisibleEvent = jest.fn()
  984. const infoHiddenEvent = jest.fn()
  985. const core = new Core()
  986. core.run()
  987. core.on('info-visible', infoVisibleEvent)
  988. core.on('info-hidden', infoHiddenEvent)
  989. core.info('This is the message', 'info', 100)
  990. expect(typeof core.infoTimeoutID).toEqual('number')
  991. expect(infoHiddenEvent.mock.calls.length).toEqual(0)
  992. setTimeout(() => {
  993. expect(infoHiddenEvent.mock.calls.length).toEqual(1)
  994. expect(core.state.info).toEqual({
  995. isHidden: true,
  996. type: 'info',
  997. message: 'This is the message',
  998. details: null
  999. })
  1000. done()
  1001. }, 110)
  1002. })
  1003. it('should hide an info message', () => {
  1004. const infoVisibleEvent = jest.fn()
  1005. const infoHiddenEvent = jest.fn()
  1006. const core = new Core()
  1007. core.run()
  1008. core.on('info-visible', infoVisibleEvent)
  1009. core.on('info-hidden', infoHiddenEvent)
  1010. core.info('This is the message', 'info', 0)
  1011. expect(typeof core.infoTimeoutID).toEqual('undefined')
  1012. expect(infoHiddenEvent.mock.calls.length).toEqual(0)
  1013. core.hideInfo()
  1014. expect(infoHiddenEvent.mock.calls.length).toEqual(1)
  1015. expect(core.state.info).toEqual({
  1016. isHidden: true,
  1017. type: 'info',
  1018. message: 'This is the message',
  1019. details: null
  1020. })
  1021. })
  1022. })
  1023. describe('createUpload', () => {
  1024. it('should assign the specified files to a new upload', () => {
  1025. const core = new Core()
  1026. core.run()
  1027. return core.addFile({
  1028. source: 'jest',
  1029. name: 'foo.jpg',
  1030. type: 'image/jpeg',
  1031. data: utils.dataURItoFile(sampleImageDataURI, {})
  1032. }).then(() => {
  1033. core._createUpload(Object.keys(core.state.files))
  1034. const uploadId = Object.keys(core.state.currentUploads)[0]
  1035. const currentUploadsState = {}
  1036. currentUploadsState[uploadId] = {
  1037. fileIDs: Object.keys(core.state.files),
  1038. step: 0
  1039. }
  1040. expect(core.state.currentUploads).toEqual(currentUploadsState)
  1041. })
  1042. })
  1043. })
  1044. describe('removeUpload', () => {
  1045. it('should remove all files from the specified upload', () => {
  1046. // this uploader will run once the upload has started
  1047. const uploader = () => {
  1048. return Promise.resolve().then(() => {
  1049. const uploadId = Object.keys(core.state.currentUploads)[0]
  1050. expect(typeof core.state.currentUploads[uploadId]).toEqual('object')
  1051. expect(core.state.currentUploads[uploadId].fileIDs.length).toEqual(1)
  1052. core._removeUpload(uploadId)
  1053. expect(typeof core.state.currentUploads[uploadId]).toEqual('undefined')
  1054. })
  1055. }
  1056. const core = new Core()
  1057. core.run()
  1058. core.addUploader(uploader)
  1059. return core
  1060. .addFile({
  1061. source: 'jest',
  1062. name: 'foo.jpg',
  1063. type: 'image/jpeg',
  1064. data: utils.dataURItoFile(sampleImageDataURI, {})
  1065. })
  1066. .then(() => {
  1067. return core.upload(true)
  1068. })
  1069. })
  1070. })
  1071. })