initial commit (migrated)

This commit is contained in:
fiatcode 2025-10-20 16:43:59 +07:00
commit b594facb51
143 changed files with 11057 additions and 0 deletions

View file

@ -0,0 +1,4 @@
import 'dart:io';
String readResponse(String name) =>
File('test/_responses/$name.json').readAsStringSync();

View file

@ -0,0 +1,5 @@
{
"id": 35499,
"text": "I must have killed a lot of cows in a past life for Karma to hate me this much.",
"author": "Katie McGarry, Pushing the Limits"
}

View file

@ -0,0 +1,322 @@
[
{
"id": "hkZTIwcIvJg",
"description": "A woman walking down a street in a white dress",
"color": "#8c7373",
"blurHash": "LFCrvE~BV@wIH=aJt6xaxaadWUoz",
"url": "https://images.unsplash.com/photo-1725526750253-0abf4c3f4665?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-woman-walking-down-a-street-in-a-white-dress-hkZTIwcIvJg?utm_source=kuwot&utm_medium=referral",
"authorName": "Raymond Petrik",
"authorBio": "film photographer, music video director, travel lover, love through my pictures directly to all of you",
"authorLocation": "Europe",
"authorTotalLikes": 33,
"authorTotalPhotos": 255,
"authorIsForHire": true,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1722151106527-1b1d892ef696?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@raymondpetrik?utm_source=kuwot&utm_medium=referral"
},
{
"id": "AU7WQdbQTdU",
"description": "A black sports car parked in front of a building",
"color": "#262626",
"blurHash": "LgD]_3M{t7WV_NWBWCayWrofRjj[",
"url": "https://images.unsplash.com/photo-1721355623034-807a3251163a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-black-sports-car-parked-in-front-of-a-building-AU7WQdbQTdU?utm_source=kuwot&utm_medium=referral",
"authorName": "Erik Mclean",
"authorBio": "Donations are welcome! paypal.me/Introspectivedsgn \r\n& Feel free to reach out if you wish to purchase selling rights. Give me a follow on instagram @introspectivedsgn",
"authorLocation": "st. Johns, NL",
"authorTotalLikes": 311,
"authorTotalPhotos": 17700,
"authorIsForHire": true,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1605586339247-f9d24f56b74eimage?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@introspectivedsgn?utm_source=kuwot&utm_medium=referral"
},
{
"id": "ySZNyM6-q_Q",
"description": "A small white building with a clock tower on top of it",
"color": "#f3f3f3",
"blurHash": "LoMtHrRkt7t7_Nt7ofM{_3ofayax",
"url": "https://images.unsplash.com/photo-1723479319633-43fa297d3853?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-small-white-building-with-a-clock-tower-on-top-of-it-ySZNyM6-q_Q?utm_source=kuwot&utm_medium=referral",
"authorName": "oo verthing",
"authorBio": "SWEETHEART AROUND YOU.",
"authorLocation": "郑州 zhengzhou",
"authorTotalLikes": 6006,
"authorTotalPhotos": 798,
"authorIsForHire": true,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1671458629285-b7bfa964e570image?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@ooverthing?utm_source=kuwot&utm_medium=referral"
},
{
"id": "iUx8Ovld4UI",
"description": "A person standing on a beach near the ocean",
"color": "#595940",
"blurHash": "LmG*+pIoWBaz~VRjWBfRx]axWBj[",
"url": "https://images.unsplash.com/photo-1723619884726-f246de4dae07?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-person-standing-on-a-beach-near-the-ocean-iUx8Ovld4UI?utm_source=kuwot&utm_medium=referral",
"authorName": "Shiebi AL",
"authorBio": "Fotógrafa,México.",
"authorLocation": "México",
"authorTotalLikes": 127,
"authorTotalPhotos": 61,
"authorIsForHire": false,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1725483661358-c6fa0d2d8ad5image?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@shiebi?utm_source=kuwot&utm_medium=referral"
},
{
"id": "VS6jRcrwZhA",
"description": "The sun is setting over the water in the distance",
"color": "#595959",
"blurHash": "LMC$o.M|I;jZ~UR*Nbj@T0jZs,az",
"url": "https://images.unsplash.com/photo-1725402346958-ea04d6b7b657?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/the-sun-is-setting-over-the-water-in-the-distance-VS6jRcrwZhA?utm_source=kuwot&utm_medium=referral",
"authorName": "Phil Botha",
"authorBio": "Aotearoa, New Zealand",
"authorLocation": "New Zealand",
"authorTotalLikes": 17,
"authorTotalPhotos": 52,
"authorIsForHire": true,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1618470173660-888bde2e3181image?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@philbotha?utm_source=kuwot&utm_medium=referral"
},
{
"id": "LD0NgEgTMuQ",
"description": "A jellyfish swimming in the water at night",
"color": "#595959",
"blurHash": "L44V8rWC4mj[W?oft6ax00of?bf6",
"url": "https://images.unsplash.com/photo-1723581048670-bdd958641c2e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-jellyfish-swimming-in-the-water-at-night-LD0NgEgTMuQ?utm_source=kuwot&utm_medium=referral",
"authorName": "Zhen Yao",
"authorBio": "Try to capture the beautiful moments in my life",
"authorLocation": "Pittsburgh, PA",
"authorTotalLikes": 1105,
"authorTotalPhotos": 626,
"authorIsForHire": false,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1545600588648-05099ce74acf?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@zhenyao_photo?utm_source=kuwot&utm_medium=referral"
},
{
"id": "_O-f6C11iys",
"description": "A woman with red hair is walking in front of a black wall",
"color": "#262626",
"blurHash": "L87w$v%M4:R*0Kof-:Rj.8kDWBt7",
"url": "https://images.unsplash.com/photo-1725221904479-9b196e4b0d67?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-woman-with-red-hair-is-walking-in-front-of-a-black-wall-_O-f6C11iys?utm_source=kuwot&utm_medium=referral",
"authorName": "Pavel Moiseev",
"authorBio": "No bio",
"authorLocation": "Mülheim an der Ruhr, Germany",
"authorTotalLikes": 22,
"authorTotalPhotos": 60,
"authorIsForHire": true,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1713706650525-167e671fbd83image?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@pavelmois?utm_source=kuwot&utm_medium=referral"
},
{
"id": "uOE2ttzJVhg",
"description": "A woman with her eyes closed standing in a room",
"color": "#c0c0c0",
"blurHash": "LOKUZh00S4.8.8%g%M%M_4-:RPIU",
"url": "https://images.unsplash.com/photo-1724086575243-6796fc662673?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-woman-with-her-eyes-closed-standing-in-a-room-uOE2ttzJVhg?utm_source=kuwot&utm_medium=referral",
"authorName": "Alexander Mass",
"authorBio": "No bio",
"authorLocation": "Unknown",
"authorTotalLikes": 0,
"authorTotalPhotos": 1660,
"authorIsForHire": true,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1702352055453-324fc6a5e3cbimage?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@alexandermassph?utm_source=kuwot&utm_medium=referral"
},
{
"id": "azIP-LUvIXs",
"description": "A red building sitting on the side of a body of water",
"color": "#d9d9d9",
"blurHash": "LYG[sLDhM{kV~qRjRjWByXRjjFn%",
"url": "https://images.unsplash.com/photo-1724271361924-ad07d11d4add?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-red-building-sitting-on-the-side-of-a-body-of-water-azIP-LUvIXs?utm_source=kuwot&utm_medium=referral",
"authorName": "Daniel Seßler",
"authorBio": "Thank you for visting my profile!\r\nIf you want to support me creating more photos for Unsplash you can help me with a small donation. But a thank you is enough as well 😊",
"authorLocation": "Munich",
"authorTotalLikes": 1009,
"authorTotalPhotos": 546,
"authorIsForHire": false,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1634653553021-5ee00c501272image?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@danielsessler?utm_source=kuwot&utm_medium=referral"
},
{
"id": "RUJErP3X0fY",
"description": "An ornate doorway with a large metal door",
"color": "#c0a68c",
"blurHash": "LOIz-JxZ^%WX~VofEMj[9voeRjj?",
"url": "https://images.unsplash.com/photo-1724762115854-01798ad66178?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/an-ornate-doorway-with-a-large-metal-door-RUJErP3X0fY?utm_source=kuwot&utm_medium=referral",
"authorName": "Jimmy Woo",
"authorBio": "Culturist\r\n鍾意睇書同遠方 影低人類世",
"authorLocation": "Americas | Asia",
"authorTotalLikes": 251,
"authorTotalPhotos": 1162,
"authorIsForHire": false,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1695577181301-19005b97e484image?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@woomantsing?utm_source=kuwot&utm_medium=referral"
},
{
"id": "Msc61p_9BwA",
"description": "An electric guitar sitting on top of a table",
"color": "#26260c",
"blurHash": "LP9*P,aJD%of8^azxut7x^oLaeR*",
"url": "https://images.unsplash.com/photo-1723714807771-23b5447e2f65?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/an-electric-guitar-sitting-on-top-of-a-table-Msc61p_9BwA?utm_source=kuwot&utm_medium=referral",
"authorName": "Oleg Brovchenko",
"authorBio": "I'm Oleg Brovchenko, a music video director, videographer, based in Spain. With over 10 years of experience. Founder of the Full Drill Production. I've collaborated with top artists, creating viral multi-platinum videos across the country.",
"authorLocation": "Madrid",
"authorTotalLikes": 0,
"authorTotalPhotos": 107,
"authorIsForHire": false,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1708191223327-80acb220d68dimage?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@olegbrovchenko?utm_source=kuwot&utm_medium=referral"
},
{
"id": "qVkbq2obFyo",
"description": "A dark blue background with lots of leaves",
"color": "#0c0c26",
"blurHash": "L6003^fUfHfNfVfRfRfRfHfMfWfS",
"url": "https://images.unsplash.com/photo-1724995922268-cec4bb414b86?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-dark-blue-background-with-lots-of-leaves-qVkbq2obFyo?utm_source=kuwot&utm_medium=referral",
"authorName": "Eugene Golovesov",
"authorBio": "Hello everyone!\r\nWelcome to my profile. Here I share my photos. You can see even more of my photos on my Instagram: @eugenegolovesov. My Behance: @eugenegolovesov. Thank you for your attention! 🙏",
"authorLocation": "Unknown",
"authorTotalLikes": 47558,
"authorTotalPhotos": 1226,
"authorIsForHire": false,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1704991443592-a7f79d25ffb1image?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@eugene_golovesov?utm_source=kuwot&utm_medium=referral"
},
{
"id": "GXgqi2etim0",
"description": "A steering wheel and dashboard of a car",
"color": "#d9d9d9",
"blurHash": "LoH2ZgM_WBj[~qIUWBofM|M{M{fk",
"url": "https://images.unsplash.com/photo-1725127077038-3aa8c931645d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-steering-wheel-and-dashboard-of-a-car-GXgqi2etim0?utm_source=kuwot&utm_medium=referral",
"authorName": "Ricardo Resende",
"authorBio": "Hobbyist photographer from Portugal. \r\nLooking for Work in Aveiro, Portugal :) If you'd like to support me, you can consider a donation paypal.me/Rresenden",
"authorLocation": "Portugal",
"authorTotalLikes": 28,
"authorTotalPhotos": 763,
"authorIsForHire": true,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1627912212709-c3120c50c449image?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@rresenden?utm_source=kuwot&utm_medium=referral"
},
{
"id": "xjrv4fKWsNI",
"description": "A bottle of tic skin oil sitting on top of a tree branch",
"color": "#40260c",
"blurHash": "L79~m1%1E29u-UozNHV@10a}W=oK",
"url": "https://images.unsplash.com/photo-1723391962208-b5c92f400816?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-bottle-of-tic-skin-oil-sitting-on-top-of-a-tree-branch-xjrv4fKWsNI?utm_source=kuwot&utm_medium=referral",
"authorName": "Pavlo T",
"authorBio": "No bio",
"authorLocation": "Ukraine",
"authorTotalLikes": 312,
"authorTotalPhotos": 61,
"authorIsForHire": true,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1673344928632-5fa23ed8d94bimage?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@pavlo_talpa?utm_source=kuwot&utm_medium=referral"
},
{
"id": "pHC1igR4Cns",
"description": "A person sitting at a table with a bunch of books",
"color": "#d9c0c0",
"blurHash": "LDI}Fgx]0KI:~qEM9F9GMdxb-o-:",
"url": "https://images.unsplash.com/photo-1724963578391-dcf77410bb73?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-person-sitting-at-a-table-with-a-bunch-of-books-pHC1igR4Cns?utm_source=kuwot&utm_medium=referral",
"authorName": "Toa Heftiba",
"authorBio": "ᴘʀᴏᴅᴜᴄᴛ | ꜰᴏᴏᴅ | ʟɪꜰᴇꜱᴛʏʟᴇ ᴘʜᴏᴛᴏɢʀᴀᴘʜᴇʀ • ᴀ ꜰᴀɴ ᴏꜰ ᴏᴅᴅ ᴛʜɪɴɢꜱ ᴀɴᴅ ɢᴏᴏᴅ ʜᴜᴍᴏᴜʀ, a hopeless romantic with karma on her back🥹\r\n👉🏻ʟɪᴋ ᴛʜᴇ 'ᴄᴏʟʟᴇᴄᴛɪᴏɴꜱ' ᴛᴀʙ ʙᴇʟᴏᴡ ᴛᴏ ᴠɪᴇᴡ ᴍʏ ɪᴍᴀɢᴇꜱ ɪɴ ᴏʀɢᴀɴɪꜱᴇᴅ ꜰᴏʟᴅᴇʀꜱ. ᴡʜʏ ɴᴏᴛ ꜱᴀʏ ʜᴇʟʟᴏ ⚡️ ɪɢ: @ʜᴇꜰᴛɪʙᴀ..ᴜᴋ",
"authorLocation": "UK",
"authorTotalLikes": 2442,
"authorTotalPhotos": 3931,
"authorIsForHire": true,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1605780274397-200ea3604d6fimage?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@heftiba?utm_source=kuwot&utm_medium=referral"
},
{
"id": "nOIjHjsJrKo",
"description": "A woman standing in front of a green screen",
"color": "#0c8c26",
"blurHash": "LM9dFX8yIB-q54?bo|In+*Inw~oe",
"url": "https://images.unsplash.com/photo-1724812569788-ccac27cf530a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-woman-standing-in-front-of-a-green-screen-nOIjHjsJrKo?utm_source=kuwot&utm_medium=referral",
"authorName": "Dwayne joe",
"authorBio": "Love taking Portraits.",
"authorLocation": "Kenya",
"authorTotalLikes": 123,
"authorTotalPhotos": 458,
"authorIsForHire": true,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1651706213170-8be43fd00ae7?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@spliff_dj_joe?utm_source=kuwot&utm_medium=referral"
},
{
"id": "wpMCTmxivtk",
"description": "The sun is setting over a body of water",
"color": "#262626",
"blurHash": "LXDarc9^S3s.}rI=WWoK$%ayazWV",
"url": "https://images.unsplash.com/photo-1725827866746-21021ebabcfc?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/the-sun-is-setting-over-a-body-of-water-wpMCTmxivtk?utm_source=kuwot&utm_medium=referral",
"authorName": "Alvin Mahmudov",
"authorBio": "No bio",
"authorLocation": "Azerbaijan Baku",
"authorTotalLikes": 42,
"authorTotalPhotos": 65,
"authorIsForHire": true,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1658572107488-64663405b6f0image?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@alvinmahmudov?utm_source=kuwot&utm_medium=referral"
},
{
"id": "KjkZqlBayFc",
"description": "A living room filled with furniture and a large window",
"color": "#a6a68c",
"blurHash": "LIIEkL-;jEae~pIUoy%1MdITRkIp",
"url": "https://images.unsplash.com/photo-1724775255163-b0f5f5241642?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-living-room-filled-with-furniture-and-a-large-window-KjkZqlBayFc?utm_source=kuwot&utm_medium=referral",
"authorName": "Clay Banks",
"authorBio": "📷 Freelance Photog\r\n📍 Catskill Mountains NY 🌎 Presets & Prints 👉🏽 https://claybanks.info If you use my images and would like to say thanks, feel free to donate via the PayPal link on my profile",
"authorLocation": "New York",
"authorTotalLikes": 517,
"authorTotalPhotos": 1338,
"authorIsForHire": true,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1670236743900-356b1ee0dc42image?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@claybanks?utm_source=kuwot&utm_medium=referral"
},
{
"id": "CFvc4aFsWOM",
"description": "A view of a city with tall buildings",
"color": "#73c0d9",
"blurHash": "L:HCl;oft6a}ysoLa|fk9wayR*j[",
"url": "https://images.unsplash.com/photo-1710092489927-8f382249cc51?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-view-of-a-city-with-tall-buildings-CFvc4aFsWOM?utm_source=kuwot&utm_medium=referral",
"authorName": "Zhen Yao",
"authorBio": "Try to capture the beautiful moments in my life",
"authorLocation": "Pittsburgh, PA",
"authorTotalLikes": 1105,
"authorTotalPhotos": 626,
"authorIsForHire": false,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1545600588648-05099ce74acf?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@zhenyao_photo?utm_source=kuwot&utm_medium=referral"
},
{
"id": "kwV8FbXF7Bo",
"description": "A man standing in front of a white bus",
"color": "#d9d9d9",
"blurHash": "L#HoOGM{ofWB~qWBazjtx]ofWBj@",
"url": "https://images.unsplash.com/photo-1722218531378-db77ec2eea7f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzNTU1MTd8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MjYxMjcxMzV8&ixlib=rb-4.0.3&q=80&w=1080",
"originUrl": "https://unsplash.com/photos/a-man-standing-in-front-of-a-white-bus-kwV8FbXF7Bo?utm_source=kuwot&utm_medium=referral",
"authorName": "Jose Figueroa",
"authorBio": "No bio",
"authorLocation": "Unknown",
"authorTotalLikes": 11,
"authorTotalPhotos": 187,
"authorIsForHire": true,
"authorProfileImageUrl": "https://images.unsplash.com/profile-1675085333943-8cd12a953307?ixlib=rb-4.0.3&crop=faces&fit=crop&w=128&h=128",
"authorUrl": "https://unsplash.com/@josefigueroa_mov?utm_source=kuwot&utm_medium=referral"
}
]

View file

@ -0,0 +1,52 @@
[
{
"id": "en",
"lang": "English",
"tableName": "quotes"
},
{
"id": "id",
"lang": "Indonesian",
"tableName": "quotes_id"
},
{
"id": "es",
"lang": "Spanish",
"tableName": "quotes_es"
},
{
"id": "de",
"lang": "Dutch",
"tableName": "quotes_de"
},
{
"id": "fr",
"lang": "French",
"tableName": "quotes_fr"
},
{
"id": "hi",
"lang": "Hindi",
"tableName": "quotes_hi"
},
{
"id": "it",
"lang": "Italian",
"tableName": "quotes_it"
},
{
"id": "ru",
"lang": "Russian",
"tableName": "quotes_ru"
},
{
"id": "zh-CN",
"lang": "Chinese (Simplified)",
"tableName": "quotes_zhcn"
},
{
"id": "ja",
"lang": "Japanese",
"tableName": "quotes_ja"
}
]

View file

@ -0,0 +1,90 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:in_app_update/in_app_update.dart';
import 'package:kuwot/core/app_updater.dart';
import 'package:kuwot/features/in_app_update/presentation/bloc/in_app_update_bloc.dart';
import 'package:mocktail/mocktail.dart';
class MockAppUpdater extends Mock implements AppUpdater {}
void main() {
late MockAppUpdater mockAppUpdater;
late InAppUpdateBloc bloc;
setUp(() {
mockAppUpdater = MockAppUpdater();
bloc = InAppUpdateBloc(appUpdater: mockAppUpdater);
});
test('initial state should be InAppUpdateInitialState', () {
// assert
expect(bloc.state, const InAppUpdateInitialState());
});
group('InAppUpdateCheck', () {
test(
'should emit [Check, UpdateAvailable] when update is available',
() async {
// arrange
final tAppUpdateInfo = AppUpdateInfo(
updateAvailability: UpdateAvailability.updateAvailable,
immediateUpdateAllowed: true,
immediateAllowedPreconditions: [],
flexibleUpdateAllowed: true,
flexibleAllowedPreconditions: [],
availableVersionCode: 1,
installStatus: InstallStatus.pending,
packageName: 'com.example.app',
clientVersionStalenessDays: 1,
updatePriority: 1,
);
when(
() => mockAppUpdater.checkForUpdate(),
).thenAnswer((_) async => tAppUpdateInfo);
// expect later
final expected = [
const InAppUpdateCheckingState(),
const InAppUpdateAvailableState(),
];
expectLater(bloc.stream, emitsInOrder(expected));
// act
bloc.add(const InAppUpdateCheckEvent());
},
);
test(
'should emit [Check, UpdateUnavailable] when update is not available',
() async {
// arrange
final tAppUpdateInfo = AppUpdateInfo(
updateAvailability: UpdateAvailability.updateNotAvailable,
immediateUpdateAllowed: true,
immediateAllowedPreconditions: [],
flexibleUpdateAllowed: true,
flexibleAllowedPreconditions: [],
availableVersionCode: 1,
installStatus: InstallStatus.pending,
packageName: 'com.example.app',
clientVersionStalenessDays: 1,
updatePriority: 1,
);
when(
() => mockAppUpdater.checkForUpdate(),
).thenAnswer((_) async => tAppUpdateInfo);
// expect later
final expected = [
const InAppUpdateCheckingState(),
const InAppUpdateUnavailableState(),
];
expectLater(bloc.stream, emitsInOrder(expected));
// act
bloc.add(const InAppUpdateCheckEvent());
},
);
});
group('InAppUpdateStart', () {});
}

View file

@ -0,0 +1,91 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:kuwot/core/env.dart';
import 'package:kuwot/core/network/network.dart';
import 'package:kuwot/features/quote/data/data_sources/remote/kuwot_api_remote_data_source.dart';
import 'package:kuwot/features/quote/data/models/image_model.dart';
import 'package:kuwot/features/quote/data/models/quote_model.dart';
import 'package:kuwot/features/quote/data/models/translation_model.dart';
import 'package:mocktail/mocktail.dart';
import '../../../../../_responses/_response.dart';
class MockEnv extends Mock implements Env {}
class MockNetwork extends Mock implements Network {}
class FakeUri extends Fake implements Uri {}
void main() {
late MockEnv mockEnv;
late MockNetwork mockNetwork;
late KuwotApiRemoteDataSource dataSource;
setUpAll(() {
registerFallbackValue(FakeUri());
});
setUp(() {
mockEnv = MockEnv();
mockNetwork = MockNetwork();
dataSource = KuwotApiRemoteApiImpl(env: mockEnv, network: mockNetwork);
// global stubs
when(() => mockEnv.authPublicKey).thenReturn('test');
when(() => mockEnv.quoteApiScheme).thenReturn('http');
when(() => mockEnv.quoteApiHost).thenReturn('10.0.2.2');
when(() => mockEnv.quoteApiPort).thenReturn(8080);
});
group('getDailyQuote', () {
test('should return random quote when response is successful', () async {
// arrange
final tResponse = readResponse('quote');
when(
() => mockNetwork.get(any(), headers: any(named: 'headers')),
).thenAnswer((_) async => tResponse);
// act
final result = await dataSource.getQuote();
// assert
expect(result, isA<QuoteModel>());
});
test(
'should return translated quote when response is successful',
() async {
// arrange
final tResponse = readResponse('quote');
when(
() => mockNetwork.get(any(), headers: any(named: 'headers')),
).thenAnswer((_) async => tResponse);
// act
final result = await dataSource.getTranslatedQuote(1);
// assert
expect(result, isA<QuoteModel>());
},
);
test('should return random images when response is successful', () async {
// arrange
final tResponse = readResponse('random_images');
when(
() => mockNetwork.get(any(), headers: any(named: 'headers')),
).thenAnswer((_) async => tResponse);
// act
final result = await dataSource.getRandomImages();
// assert
expect(result, isA<List<ImageModel>>());
});
test('should return translations when response is successful', () async {
// arrange
final tResponse = readResponse('translations');
when(
() => mockNetwork.get(any(), headers: any(named: 'headers')),
).thenAnswer((_) async => tResponse);
// act
final result = await dataSource.getTranslations();
// assert
expect(result, isA<List<TranslationModel>>());
});
});
}

View file

@ -0,0 +1,347 @@
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart';
import 'package:kuwot/core/data/local/translation_target_config.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/features/quote/data/data_sources/remote/kuwot_api_remote_data_source.dart';
import 'package:kuwot/features/quote/data/models/image_model.dart';
import 'package:kuwot/features/quote/data/models/quote_model.dart';
import 'package:kuwot/features/quote/data/models/translation_model.dart';
import 'package:kuwot/features/quote/data/repositories/quote_repository_impl.dart';
import 'package:kuwot/features/quote/domain/entities/background_image.dart';
import 'package:kuwot/features/quote/domain/entities/quote.dart';
import 'package:kuwot/features/quote/domain/entities/translation.dart';
import 'package:mocktail/mocktail.dart';
import '../../../../_responses/_response.dart';
class MockKuwotApiRemoteDataSource extends Mock
implements KuwotApiRemoteDataSource {}
void main() {
late QuoteRepositoryImpl quoteRepository;
late MockKuwotApiRemoteDataSource mockKuwotApiRemoteDataSource;
setUp(() {
mockKuwotApiRemoteDataSource = MockKuwotApiRemoteDataSource();
quoteRepository = QuoteRepositoryImpl(
quoteDataSource: mockKuwotApiRemoteDataSource,
);
});
const tQuoteModel = QuoteModel(id: 1, author: 'author', text: 'text');
const tExpectedQuote = Quote(id: 1, author: 'author', body: 'text');
const tTranslationTarget = TranslationTarget(id: 'en', name: 'English');
group('getQuote', () {
test('should return a Quote entity', () async {
// arrange
when(
() => mockKuwotApiRemoteDataSource.getQuote(query: any(named: 'query')),
).thenAnswer((_) async => tQuoteModel);
// act
final result = await quoteRepository.getQuote(tTranslationTarget);
// assert
verify(
() => mockKuwotApiRemoteDataSource.getQuote(query: any(named: 'query')),
);
result.fold(
(failure) => fail('Expected Quote, but got $failure'),
(quote) => expect(quote, tExpectedQuote),
);
verifyNoMoreInteractions(mockKuwotApiRemoteDataSource);
});
test(
'should return ClientFailure when a client exception is thrown',
() async {
// arrange
when(
() =>
mockKuwotApiRemoteDataSource.getQuote(query: any(named: 'query')),
).thenThrow(ClientException('test'));
// act
final result = await quoteRepository.getQuote(null);
// assert
verify(
() =>
mockKuwotApiRemoteDataSource.getQuote(query: any(named: 'query')),
);
result.fold(
(failure) => expect(failure, isA<ClientFailure>()),
(quote) => fail('Expected ClientFailure, but got $quote'),
);
verifyNoMoreInteractions(mockKuwotApiRemoteDataSource);
},
);
test('should return UnknownFailure when an exception is thrown', () async {
// arrange
when(
() => mockKuwotApiRemoteDataSource.getQuote(query: any(named: 'query')),
).thenThrow(Exception('test'));
// act
final result = await quoteRepository.getQuote(null);
// assert
verify(
() => mockKuwotApiRemoteDataSource.getQuote(query: any(named: 'query')),
);
result.fold(
(failure) => expect(failure, isA<UnknownFailure>()),
(quote) => fail('Expected UnknownFailure, but got $quote'),
);
verifyNoMoreInteractions(mockKuwotApiRemoteDataSource);
});
});
group('getTranslatedQuote', () {
const tQuoteId = 1;
test('should return a Quote entity', () async {
// arrange
when(
() => mockKuwotApiRemoteDataSource.getTranslatedQuote(
any(),
query: any(named: 'query'),
),
).thenAnswer((_) async => tQuoteModel);
// act
final result = await quoteRepository.getTranslatedQuote(
tQuoteId,
tTranslationTarget,
);
// assert
verify(
() => mockKuwotApiRemoteDataSource.getTranslatedQuote(
any(),
query: any(named: 'query'),
),
);
result.fold(
(failure) => fail('Expected Quote, but got $failure'),
(quote) => expect(quote, tExpectedQuote),
);
verifyNoMoreInteractions(mockKuwotApiRemoteDataSource);
});
test(
'should return ClientFailure when a client exception is thrown',
() async {
// arrange
when(
() => mockKuwotApiRemoteDataSource.getTranslatedQuote(
any(),
query: any(named: 'query'),
),
).thenThrow(ClientException('test'));
// act
final result = await quoteRepository.getTranslatedQuote(
tQuoteId,
tTranslationTarget,
);
// assert
verify(
() => mockKuwotApiRemoteDataSource.getTranslatedQuote(
any(),
query: any(named: 'query'),
),
);
result.fold(
(failure) => expect(failure, isA<ClientFailure>()),
(quote) => fail('Expected ClientFailure, but got $quote'),
);
verifyNoMoreInteractions(mockKuwotApiRemoteDataSource);
},
);
test('should return UnknownFailure when an exception is thrown', () async {
// arrange
when(
() => mockKuwotApiRemoteDataSource.getTranslatedQuote(
any(),
query: any(named: 'query'),
),
).thenThrow(Exception('test'));
// act
final result = await quoteRepository.getTranslatedQuote(
tQuoteId,
tTranslationTarget,
);
// assert
verify(
() => mockKuwotApiRemoteDataSource.getTranslatedQuote(
any(),
query: any(named: 'query'),
),
);
result.fold(
(failure) => expect(failure, isA<UnknownFailure>()),
(quote) => fail('Expected UnknownFailure, but got $quote'),
);
verifyNoMoreInteractions(mockKuwotApiRemoteDataSource);
});
});
group('getTranslations', () {
test('should return a List of Translation entity', () async {
// arrange
final tTranslationListModel =
(jsonDecode(readResponse('translations')) as List)
.map((e) => TranslationModel.fromJson(e as Map<String, dynamic>))
.toList();
final tExpectedTranslations = tTranslationListModel
.map((e) => Translation(id: e.id, language: e.lang))
.toList();
when(
() => mockKuwotApiRemoteDataSource.getTranslations(),
).thenAnswer((_) async => tTranslationListModel);
// act
final result = await quoteRepository.getTranslations();
// assert
verify(() => mockKuwotApiRemoteDataSource.getTranslations());
result.fold(
(failure) => fail('Expected Translations, but got $failure'),
(translations) => expect(translations, tExpectedTranslations),
);
verifyNoMoreInteractions(mockKuwotApiRemoteDataSource);
});
test(
'should return ClientFailure when a client exception is thrown',
() async {
// arrange
when(
() => mockKuwotApiRemoteDataSource.getTranslations(),
).thenThrow(ClientException('test'));
// act
final result = await quoteRepository.getTranslations();
// assert
verify(() => mockKuwotApiRemoteDataSource.getTranslations());
result.fold(
(failure) => expect(failure, isA<ClientFailure>()),
(translations) =>
fail('Expected ClientFailure, but got $translations'),
);
verifyNoMoreInteractions(mockKuwotApiRemoteDataSource);
},
);
test('should return UnknownFailure when an exception is thrown', () async {
// arrange
when(
() => mockKuwotApiRemoteDataSource.getTranslations(),
).thenThrow(Exception('test'));
// act
final result = await quoteRepository.getTranslations();
// assert
verify(() => mockKuwotApiRemoteDataSource.getTranslations());
result.fold(
(failure) => expect(failure, isA<UnknownFailure>()),
(translations) =>
fail('Expected UnknownFailure, but got $translations'),
);
verifyNoMoreInteractions(mockKuwotApiRemoteDataSource);
});
});
group('getBackgroundImages', () {
test('should return a List of BackgroundImage entity', () async {
// arrange
final tImageListModel =
(jsonDecode(readResponse('random_images')) as List)
.map((e) => ImageModel.fromJson(e as Map<String, dynamic>))
.toList();
final tExpectedImages = tImageListModel.map(
(e) => BackgroundImage(
id: e.id,
description: e.description,
color: e.color,
blurHash: e.blurHash,
url: e.url,
originUrl: e.originUrl,
authorName: e.authorName,
authorProfileImageUrl: e.authorProfileImageUrl,
authorUrl: e.authorUrl,
authorBio: e.authorBio,
authorLocation: e.authorLocation,
authorTotalLikes: e.authorTotalLikes,
authorTotalPhotos: e.authorTotalPhotos,
authorIsForHire: e.authorIsForHire,
),
);
when(
() => mockKuwotApiRemoteDataSource.getRandomImages(),
).thenAnswer((_) async => tImageListModel);
// act
final result = await quoteRepository.getBackgroundImages();
// assert
verify(() => mockKuwotApiRemoteDataSource.getRandomImages());
result.fold(
(failure) => fail('Expected BackgroundImages, but got $failure'),
(photos) => expect(photos, tExpectedImages),
);
verifyNoMoreInteractions(mockKuwotApiRemoteDataSource);
});
test(
'should return ClientFailure when a client exception is thrown',
() async {
// arrange
when(
() => mockKuwotApiRemoteDataSource.getRandomImages(),
).thenThrow(ClientException('test'));
// act
final result = await quoteRepository.getBackgroundImages();
// assert
verify(() => mockKuwotApiRemoteDataSource.getRandomImages());
result.fold(
(failure) => expect(failure, isA<ClientFailure>()),
(images) => fail('Expected ClientFailure, but got $images'),
);
verifyNoMoreInteractions(mockKuwotApiRemoteDataSource);
},
);
test('should return UnknownFailure when an exception is thrown', () async {
// arrange
when(
() => mockKuwotApiRemoteDataSource.getRandomImages(),
).thenThrow(Exception('test'));
// act
final result = await quoteRepository.getBackgroundImages();
// assert
verify(() => mockKuwotApiRemoteDataSource.getRandomImages());
result.fold(
(failure) => expect(failure, isA<UnknownFailure>()),
(images) => fail('Expected UnknownFailure, but got $images'),
);
verifyNoMoreInteractions(mockKuwotApiRemoteDataSource);
});
});
}

View file

@ -0,0 +1,35 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:fpdart/fpdart.dart';
import 'package:kuwot/core/domain/no_params.dart';
import 'package:kuwot/features/quote/domain/entities/background_image.dart';
import 'package:kuwot/features/quote/domain/repositories/quote_repository.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_background_images.dart';
import 'package:mocktail/mocktail.dart';
class MockQuoteRepository extends Mock implements QuoteRepository {}
void main() {
late MockQuoteRepository mockQuoteRepository;
late GetBackgroundImages useCase;
setUp(() {
mockQuoteRepository = MockQuoteRepository();
useCase = GetBackgroundImages(mockQuoteRepository);
});
test('should get background photos', () async {
// arrange
const tImages = <BackgroundImage>[];
when(
() => mockQuoteRepository.getBackgroundImages(),
).thenAnswer((_) async => right(tImages));
// act
final result = await useCase(const NoParams());
// assert
expect(result, right(tImages));
verify(() => mockQuoteRepository.getBackgroundImages());
verifyNoMoreInteractions(mockQuoteRepository);
});
}

View file

@ -0,0 +1,34 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:fpdart/fpdart.dart';
import 'package:kuwot/features/quote/domain/entities/quote.dart';
import 'package:kuwot/features/quote/domain/repositories/quote_repository.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_quote.dart';
import 'package:mocktail/mocktail.dart';
class MockQuoteRepository extends Mock implements QuoteRepository {}
void main() {
late MockQuoteRepository mockQuoteRepository;
late GetQuote useCase;
setUp(() {
mockQuoteRepository = MockQuoteRepository();
useCase = GetQuote(mockQuoteRepository);
});
test('should get quote', () async {
// arrange
const tQuote = Quote(id: 1, author: 'author', body: 'text');
when(
() => mockQuoteRepository.getQuote(any()),
).thenAnswer((_) async => right(tQuote));
// act
final result = await useCase(const GetQuoteParams(null));
// assert
expect(result, right(tQuote));
verify(() => mockQuoteRepository.getQuote(any()));
verifyNoMoreInteractions(mockQuoteRepository);
});
}

View file

@ -0,0 +1,44 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:fpdart/fpdart.dart';
import 'package:kuwot/core/data/local/translation_target_config.dart';
import 'package:kuwot/features/quote/domain/entities/quote.dart';
import 'package:kuwot/features/quote/domain/repositories/quote_repository.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_translated_quote.dart';
import 'package:mocktail/mocktail.dart';
class MockQuoteRepository extends Mock implements QuoteRepository {}
class FakeTranslationTarget extends Fake implements TranslationTarget {}
void main() {
late MockQuoteRepository mockQuoteRepository;
late GetTranslatedQuote useCase;
setUpAll(() {
registerFallbackValue(FakeTranslationTarget());
});
setUp(() {
mockQuoteRepository = MockQuoteRepository();
useCase = GetTranslatedQuote(mockQuoteRepository);
});
test('should get translated quote', () async {
// arrange
const tQuote = Quote(id: 1, author: 'author', body: 'text');
const tTarget = TranslationTarget(id: 'en', name: 'English');
when(
() => mockQuoteRepository.getTranslatedQuote(any(), any()),
).thenAnswer((_) async => right(tQuote));
// act
final result = await useCase(
const GetTranslatedQuoteParams(id: 1, translationTarget: tTarget),
);
// assert
expect(result, right(tQuote));
verify(() => mockQuoteRepository.getTranslatedQuote(tQuote.id, tTarget));
verifyNoMoreInteractions(mockQuoteRepository);
});
}

View file

@ -0,0 +1,35 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:fpdart/fpdart.dart';
import 'package:kuwot/core/domain/no_params.dart';
import 'package:kuwot/features/quote/domain/entities/translation.dart';
import 'package:kuwot/features/quote/domain/repositories/quote_repository.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_translations.dart';
import 'package:mocktail/mocktail.dart';
class MockQuoteRepository extends Mock implements QuoteRepository {}
void main() {
late MockQuoteRepository mockQuoteRepository;
late GetTranslations useCase;
setUp(() {
mockQuoteRepository = MockQuoteRepository();
useCase = GetTranslations(mockQuoteRepository);
});
test('should get translations', () async {
// arrange
final tExpected = <Translation>[];
when(
() => mockQuoteRepository.getTranslations(),
).thenAnswer((_) async => right(tExpected));
// act
final result = await useCase(const NoParams());
// assert
expect(result, right(tExpected));
verify(() => mockQuoteRepository.getTranslations());
verifyNoMoreInteractions(mockQuoteRepository);
});
}

View file

@ -0,0 +1,95 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:fpdart/fpdart.dart';
import 'package:kuwot/core/domain/no_params.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/features/quote/domain/entities/background_image.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_background_images.dart';
import 'package:kuwot/features/quote/presentation/bloc/background_images_bloc.dart';
import 'package:mocktail/mocktail.dart';
class MockGetBackgroundImages extends Mock implements GetBackgroundImages {}
class FakeNoParams extends Fake implements NoParams {}
void main() {
late MockGetBackgroundImages mockGetBackgroundImages;
late BackgroundImagesBloc backgroundImagesBloc;
setUpAll(() {
registerFallbackValue(FakeNoParams());
});
setUp(() {
mockGetBackgroundImages = MockGetBackgroundImages();
backgroundImagesBloc = BackgroundImagesBloc(
getBackgroundImages: mockGetBackgroundImages,
);
});
test('initial state is BackgroundImagesInitial', () {
// assert
expect(backgroundImagesBloc.state, const BackgroundImagesInitialState());
});
group('GetBackgroundImages', () {
test(
'should get background photos from GetBackgroundImages use case',
() async {
// arrange
when(
() => mockGetBackgroundImages(any()),
).thenAnswer((_) async => right(const <BackgroundImage>[]));
// act
backgroundImagesBloc.add(const GetBackgroundImagesEvent());
await untilCalled(() => mockGetBackgroundImages(any()));
// assert
verify(() => mockGetBackgroundImages(any())).called(1);
verifyNoMoreInteractions(mockGetBackgroundImages);
},
);
test(
'should emit [BackgroundImagesLoading, BackgroundImagesLoaded] when data is gotten successfully',
() async {
// arrange
const tBackgroundImages = <BackgroundImage>[];
when(
() => mockGetBackgroundImages(any()),
).thenAnswer((_) async => right(tBackgroundImages));
// assert later
final expected = [
const BackgroundImagesLoadingState(),
const BackgroundImagesLoadedState(tBackgroundImages),
];
expectLater(backgroundImagesBloc.stream, emitsInOrder(expected));
// act
backgroundImagesBloc.add(const GetBackgroundImagesEvent());
},
);
test(
'should emit [BackgroundImagesLoading, BackgroundImagesError] when getting data fails',
() async {
// arrange
const tFailure = UnknownFailure(message: 'Unknown Failure');
when(
() => mockGetBackgroundImages(any()),
).thenAnswer((_) async => left(tFailure));
// assert later
final expected = [
const BackgroundImagesLoadingState(),
BackgroundImagesErrorState(message: tFailure.message),
];
expectLater(backgroundImagesBloc.stream, emitsInOrder(expected));
// act
backgroundImagesBloc.add(const GetBackgroundImagesEvent());
},
);
});
}

View file

@ -0,0 +1,180 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:fpdart/fpdart.dart';
import 'package:kuwot/core/data/local/config.dart';
import 'package:kuwot/core/data/local/translation_target_config.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/features/quote/domain/entities/quote.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_quote.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_translated_quote.dart';
import 'package:kuwot/features/quote/presentation/bloc/quote_bloc.dart';
import 'package:mocktail/mocktail.dart';
class MockGetQuote extends Mock implements GetQuote {}
class MockGetTranslatedQuote extends Mock implements GetTranslatedQuote {}
class MockTranslationTargetConfig extends Mock
implements Config<TranslationTarget> {}
class FakeQuoteParams extends Fake implements GetQuoteParams {}
class FakeTranslatedQuoteParams extends Fake
implements GetTranslatedQuoteParams {}
void main() {
late MockGetQuote mockGetQuote;
late MockGetTranslatedQuote mockGetTranslatedQuote;
late MockTranslationTargetConfig mockTranslationTargetConfig;
late QuoteBloc quoteBloc;
setUpAll(() {
registerFallbackValue(FakeQuoteParams());
registerFallbackValue(FakeTranslatedQuoteParams());
});
setUp(() {
mockGetQuote = MockGetQuote();
mockGetTranslatedQuote = MockGetTranslatedQuote();
mockTranslationTargetConfig = MockTranslationTargetConfig();
quoteBloc = QuoteBloc(
getQuote: mockGetQuote,
getTranslatedQuote: mockGetTranslatedQuote,
translationTargetConfig: mockTranslationTargetConfig,
);
});
const tTranslationTarget = TranslationTarget(id: 'en', name: 'English');
const tQuote = Quote(id: 1, author: 'author', body: 'text');
const tFailure = UnknownFailure(message: 'Unknown Failure');
test('initial state is QuoteInitial', () {
// assert
expect(quoteBloc.state, const QuoteInitialState());
});
group('GetQuote', () {
test('should get quote from GetQuote use case', () async {
// arrange
when(() => mockGetQuote(any())).thenAnswer((_) async => right(tQuote));
when(
() => mockTranslationTargetConfig.get(),
).thenAnswer((_) async => tTranslationTarget);
// act
quoteBloc.add(const GetQuoteEvent());
await untilCalled(() => mockTranslationTargetConfig.get());
await untilCalled(() => mockGetQuote(any()));
// assert
verify(() => mockGetQuote(any()));
verifyNoMoreInteractions(mockGetQuote);
});
test(
'should emit [QuoteLoading, QuoteLoaded] when data is gotten successfully',
() async {
// arrange
when(() => mockGetQuote(any())).thenAnswer((_) async => right(tQuote));
when(
() => mockTranslationTargetConfig.get(),
).thenAnswer((_) async => tTranslationTarget);
// assert later
const expected = [QuoteLoadingState(), QuoteLoadedState(quote: tQuote)];
expectLater(quoteBloc.stream, emitsInOrder(expected));
// act
quoteBloc.add(const GetQuoteEvent());
},
);
test(
'should emit [QuoteLoading, QuoteError] when getting data fails',
() async {
// arrange
when(() => mockGetQuote(any())).thenAnswer((_) async => left(tFailure));
when(
() => mockTranslationTargetConfig.get(),
).thenAnswer((_) async => tTranslationTarget);
// assert later
final expected = [
const QuoteLoadingState(),
QuoteErrorState(message: tFailure.message),
];
expectLater(quoteBloc.stream, emitsInOrder(expected));
// act
quoteBloc.add(const GetQuoteEvent());
},
);
});
group('GetTranslatedQuote', () {
test(
'should get translated quote from GetTranslatedQuote use case',
() async {
// arrange
when(
() => mockGetTranslatedQuote(any()),
).thenAnswer((_) async => right(tQuote));
when(
() => mockTranslationTargetConfig.get(),
).thenAnswer((_) async => tTranslationTarget);
// act
quoteBloc.add(const GetTranslatedQuoteEvent(tTranslationTarget));
await untilCalled(() => mockTranslationTargetConfig.get());
await untilCalled(() => mockGetTranslatedQuote(any()));
// assert
verify(() => mockGetTranslatedQuote(any()));
verifyNoMoreInteractions(mockGetTranslatedQuote);
},
);
test(
'should emit [QuoteLoading, QuoteLoaded] when data is gotten successfully',
() async {
// arrange
when(
() => mockGetTranslatedQuote(any()),
).thenAnswer((_) async => right(tQuote));
when(
() => mockTranslationTargetConfig.get(),
).thenAnswer((_) async => tTranslationTarget);
// assert later
const expected = [QuoteLoadingState(), QuoteLoadedState(quote: tQuote)];
expectLater(quoteBloc.stream, emitsInOrder(expected));
// act
quoteBloc.add(const GetTranslatedQuoteEvent(tTranslationTarget));
},
);
test(
'should emit [QuoteLoading, QuoteError] when getting data fails',
() async {
// arrange
when(
() => mockGetTranslatedQuote(any()),
).thenAnswer((_) async => left(tFailure));
when(
() => mockTranslationTargetConfig.get(),
).thenAnswer((_) async => tTranslationTarget);
// assert later
final expected = [
const QuoteLoadingState(),
QuoteErrorState(message: tFailure.message),
];
expectLater(quoteBloc.stream, emitsInOrder(expected));
// act
quoteBloc.add(const GetTranslatedQuoteEvent(tTranslationTarget));
},
);
});
}

View file

@ -0,0 +1,72 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:fpdart/fpdart.dart';
import 'package:kuwot/core/domain/no_params.dart';
import 'package:kuwot/core/error/failure.dart';
import 'package:kuwot/features/quote/domain/use_cases/get_translations.dart';
import 'package:kuwot/features/quote/presentation/bloc/translations_bloc.dart';
import 'package:mocktail/mocktail.dart';
class MockGetTranslations extends Mock implements GetTranslations {}
class FakeNoParams extends Fake implements NoParams {}
void main() {
late MockGetTranslations mockGetTranslations;
late TranslationsBloc translationsBloc;
setUpAll(() {
registerFallbackValue(FakeNoParams());
});
setUp(() {
mockGetTranslations = MockGetTranslations();
translationsBloc = TranslationsBloc(getTranslations: mockGetTranslations);
});
group('GetTranslations', () {
test('should get translations from GetTranslations use case', () async {
// arrange
when(() => mockGetTranslations(any())).thenAnswer((_) async => right([]));
// act
translationsBloc.add(const GetTranslationsEvent());
await untilCalled(() => mockGetTranslations(any()));
// assert
verify(() => mockGetTranslations(any()));
verifyNoMoreInteractions(mockGetTranslations);
});
test('should emit [Loading, Loaded] when successful', () async {
// arrange
when(() => mockGetTranslations(any())).thenAnswer((_) async => right([]));
// assert later
final expected = [
const TranslationsLoadingState(),
const TranslationsLoadedState(translations: []),
];
expectLater(translationsBloc.stream, emitsInOrder(expected));
// act
translationsBloc.add(const GetTranslationsEvent());
});
test('should emit [Loading, Error] when unsuccessful', () async {
// arrange
when(() => mockGetTranslations(any())).thenAnswer(
(_) async => left(const UnknownFailure(message: 'Unknown Failure')),
);
// assert later
final expected = [
const TranslationsLoadingState(),
const TranslationsErrorState(message: 'Unknown Failure'),
];
expectLater(translationsBloc.stream, emitsInOrder(expected));
// act
translationsBloc.add(const GetTranslationsEvent());
});
});
}