diff --git a/build/sw_plugin.js b/build/sw_plugin.js index 615c3db56..f278d55f6 100644 --- a/build/sw_plugin.js +++ b/build/sw_plugin.js @@ -106,7 +106,7 @@ export const buildSwPlugin = ({ swSrc, swDest }) => { const swBundle = await build(config) return swBundle.output[0] } catch (e) { - console.error('Error building ServiceWorker:' , e) + console.error('Error building ServiceWorker:', e) } }, }, @@ -119,7 +119,7 @@ export const buildSwPlugin = ({ swSrc, swDest }) => { try { await build(config) } catch (e) { - console.error('Error building ServiceWorker:' , e) + console.error('Error building ServiceWorker:', e) } }, }, diff --git a/src/modules/users.js b/src/modules/users.js index 4a4614e4d..471c19289 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -798,8 +798,11 @@ const users = { } if (useMergedConfigStore().mergedConfig.useStreamingApi) { - dispatch('fetchTimeline', { timeline: 'friends', since: null }) - dispatch('fetchNotifications', { since: null }) + dispatch('fetchTimeline', { + timeline: 'friends', + sinceId: null, + }) + dispatch('fetchNotifications', { sinceId: null }) dispatch('enableMastoSockets', true) .catch((error) => { console.error( diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 1aeeaa603..864123f5f 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -9,7 +9,7 @@ import { parseStatus, parseUser, } from '../entity_normalizer/entity_normalizer.service.js' -import { promisedRequest } from './helpers.js' +import { paramsString, promisedRequest } from './helpers.js' import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' @@ -46,8 +46,16 @@ const MASTODON_UNRETWEET_URL = (id) => `/api/v1/statuses/${id}/unreblog` const MASTODON_DELETE_URL = (id) => `/api/v1/statuses/${id}` const MASTODON_FOLLOW_URL = (id) => `/api/v1/accounts/${id}/follow` const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow` -const MASTODON_FOLLOWING_URL = (id) => `/api/v1/accounts/${id}/following` -const MASTODON_FOLLOWERS_URL = (id) => `/api/v1/accounts/${id}/followers` +const MASTODON_FOLLOWING_URL = ( + id, + { minId, maxId, sinceId, limit, withRelationships }, +) => + `/api/v1/accounts/${id}/following${paramsString({ minId, maxId, sinceId, limit, withRelationships })}` +const MASTODON_FOLLOWERS_URL = ( + id, + { minId, maxId, sinceId, limit, withRelationships }, +) => + `/api/v1/accounts/${id}/followers${paramsString({ minId, maxId, sinceId, limit, withRelationships })}` const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests' const MASTODON_APPROVE_USER_URL = (id) => `/api/v1/follow_requests/${id}/authorize` @@ -61,7 +69,8 @@ const MASTODON_STATUS_SOURCE_URL = (id) => `/api/v1/statuses/${id}/source` const MASTODON_STATUS_HISTORY_URL = (id) => `/api/v1/statuses/${id}/history` const MASTODON_USER_URL = '/api/v1/accounts' const MASTODON_USER_LOOKUP_URL = '/api/v1/accounts/lookup' -const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships' +const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) => + `/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}` const MASTODON_USER_TIMELINE_URL = (id) => `/api/v1/accounts/${id}/statuses` const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists` const MASTODON_LIST_URL = (id) => `/api/v1/lists/${id}` @@ -70,8 +79,20 @@ const MASTODON_LIST_ACCOUNTS_URL = (id) => `/api/v1/lists/${id}/accounts` const MASTODON_TAG_TIMELINE_URL = (tag) => `/api/v1/timelines/tag/${tag}` const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks' const AKKOMA_BUBBLE_TIMELINE_URL = '/api/v1/timelines/bubble' -const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/' -const MASTODON_USER_MUTES_URL = '/api/v1/mutes/' +const MASTODON_USER_BLOCKS_URL = ({ + maxId, + sinceId, + limit, + withRelationships, +}) => + `/api/v1/blocks/${paramsString({ maxId, sinceId, limit, withRelationships })}` +const MASTODON_USER_MUTES_URL = ({ + maxId, + sinceId, + limit, + withRelationships, +}) => + `/api/v1/mutes/${paramsString({ maxId, sinceId, limit, withRelationships })}` const MASTODON_BLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/block` const MASTODON_UNBLOCK_USER_URL = (id) => `/api/v1/accounts/${id}/unblock` const MASTODON_MUTE_USER_URL = (id) => `/api/v1/accounts/${id}/mute` @@ -113,12 +134,14 @@ const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats' const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}` -const PLEROMA_CHAT_MESSAGES_URL = (id) => `/api/v1/pleroma/chats/${id}/messages` +const PLEROMA_CHAT_MESSAGES_URL = (id, { maxId, sinceId, limit }) => + `/api/v1/pleroma/chats/${id}/messages${paramsString({ maxId, sinceId, limit })}` const PLEROMA_CHAT_READ_URL = (id) => `/api/v1/pleroma/chats/${id}/read` const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}` const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups' -const PLEROMA_SCROBBLES_URL = (id) => `/api/v1/pleroma/accounts/${id}/scrobbles` +const PLEROMA_SCROBBLES_URL = (id, { maxId, sinceId, minId, limit, offset }) => + `/api/v1/pleroma/accounts/${id}/scrobbles${paramsString({ maxId, sinceId, minId, limit, offset })}` const PLEROMA_STATUS_QUOTES_URL = (id) => `/api/v1/pleroma/statuses/${id}/quotes` const PLEROMA_USER_FAVORITES_TIMELINE_URL = (id) => @@ -128,20 +151,14 @@ const PLEROMA_BOOKMARK_FOLDER_URL = (id) => `/api/v1/pleroma/bookmark_folders/${id}` const EMOJI_PACKS_URL = (page, pageSize) => - `/api/v1/pleroma/emoji/packs?page=${page}&page_size=${pageSize}` + `/api/v1/pleroma/emoji/packs${paramsString({ page, pageSize })}` export const updateNotificationSettings = ({ credentials, settings }) => { - const form = new FormData() - - each(settings, (value, key) => { - form.append(key, value) - }) - return promisedRequest({ - url: `${NOTIFICATION_SETTINGS_URL}?${new URLSearchParams(settings)}`, + url: NOTIFICATION_SETTINGS_URL, credentials, method: 'PUT', - formData: form, + payload: settings, }) } @@ -245,19 +262,19 @@ export const getCaptcha = () => }) export const followUser = ({ id, credentials, ...options }) => { - const form = {} + const payload = {} if (options.reblogs !== undefined) { - form.reblogs = options.reblogs + payload.reblogs = options.reblogs } if (options.notify !== undefined) { - form.notify = options.notify + payload.notify = options.notify } return promisedRequest({ url: MASTODON_FOLLOW_URL(id), - formData: form, + payload, credentials, method: 'POST', }) @@ -380,35 +397,17 @@ export const fetchUserByName = ({ name, credentials }) => }) .then((id) => fetchUser({ id, credentials })) -export const fetchUserRelationship = ({ id, credentials }) => +export const fetchUserRelationship = ({ id, withSuspended, credentials }) => promisedRequest({ - url: `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}`, + url: MASTODON_USER_RELATIONSHIPS_URL({ id, withSuspended }), credentials, }) -export const fetchFriends = ({ - id, - maxId, - sinceId, - limit = 20, - credentials, -}) => { - let url = MASTODON_FOLLOWING_URL(id) - const args = [ - maxId && `max_id=${maxId}`, - sinceId && `since_id=${sinceId}`, - limit && `limit=${limit}`, - 'with_relationships=true', - ] - .filter((_) => _) - .join('&') - - url = url + (args ? '?' + args : '') - return promisedRequest({ - url, +export const fetchFriends = ({ id, maxId, sinceId, limit = 20, credentials }) => + promisedRequest({ + url: MASTODON_FOLLOWING_URL(id, { maxId, sinceId, limit }), credentials, }).then((data) => data.map(parseUser)) -} export const exportFriends = ({ id, credentials }) => { // biome-ignore lint/suspicious/noAsyncPromiseExecutor: TODO refactor this @@ -418,7 +417,12 @@ export const exportFriends = ({ id, credentials }) => { let more = true while (more) { const maxId = friends.length > 0 ? last(friends).id : undefined - const users = await fetchFriends({ id, maxId, credentials }) + const users = await fetchFriends({ + id, + maxId, + credentials, + withRelationships: true, + }) friends = concat(friends, users) if (users.length === 0) { more = false @@ -437,23 +441,16 @@ export const fetchFollowers = ({ sinceId, limit = 20, credentials, -}) => { - let url = MASTODON_FOLLOWERS_URL(id) - const args = [ - maxId && `max_id=${maxId}`, - sinceId && `since_id=${sinceId}`, - limit && `limit=${limit}`, - 'with_relationships=true', - ] - .filter((_) => _) - .join('&') - - url += args ? '?' + args : '' - return promisedRequest({ - url, +}) => + promisedRequest({ + url: MASTODON_FOLLOWERS_URL(id, { + maxId, + sinceId, + limit, + withRelationships: true, + }), credentials, }).then((data) => data.map(parseUser)) -} export const fetchFollowRequests = ({ credentials }) => promisedRequest({ @@ -567,17 +564,17 @@ export const fetchStatusHistory = ({ status, credentials }) => export const fetchTimeline = ({ timeline, credentials, - since = false, - minId = false, - until = false, - userId = false, - listId = false, - statusId = false, - tag = false, - withMuted = false, + sinceId, + minId, + maxId, + userId, + listId, + statusId, + tag, + withMuted, replyVisibility = 'all', includeTypes = [], - bookmarkFolderId = false, + bookmarkFolderId, }) => { const timelineUrls = { public: MASTODON_PUBLIC_TIMELINE, @@ -595,8 +592,14 @@ export const fetchTimeline = ({ quotes: PLEROMA_STATUS_QUOTES_URL, bubble: AKKOMA_BUBBLE_TIMELINE_URL, } + const isNotifications = timeline === 'notifications' - const params = [] + const params = { + minId, + sinceId, + maxId, + limit: 20, + } let url = timelineUrls[timeline] @@ -616,51 +619,34 @@ export const fetchTimeline = ({ url = url(statusId) } - if (minId) { - params.push(['min_id', minId]) - } - if (since) { - params.push(['since_id', since]) - } - if (until) { - params.push(['max_id', until]) - } if (tag) { url = url(tag) } + if (timeline === 'media') { - params.push(['only_media', 1]) + params.onlyMedia = 1 } if (timeline === 'public') { - params.push(['local', true]) + params.local = true } if (timeline === 'public' || timeline === 'publicAndExternal') { - params.push(['only_media', false]) + params.onlyMedia = false } if (timeline !== 'favorites' && timeline !== 'bookmarks') { - params.push(['with_muted', withMuted]) + params.withMuted = withMuted } if (replyVisibility !== 'all') { - params.push(['reply_visibility', replyVisibility]) + params.replyVisibility = replyVisibility } if (includeTypes.size > 0) { - includeTypes.forEach((type) => { - params.push(['include_types[]', type]) - }) + params.includeTypes = includeTypes } if (timeline === 'bookmarks' && bookmarkFolderId) { - params.push(['folder_id', bookmarkFolderId]) + params.folderId = bookmarkFolderId } - params.push(['limit', 20]) - - const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join( - '&', - ) - url += `?${queryString}` - return promisedRequest({ - url, + url: url + paramsString(params), credentials, }).then(async (data) => { const pagination = parseLinkHeaderPagination( @@ -1036,16 +1022,11 @@ export const generateMfaBackupCodes = ({ credentials }) => method: 'GET', }) -export const fetchMutes = ({ maxId, credentials }) => { - const query = new URLSearchParams({ with_relationships: true }) - if (maxId) { - query.append('max_id', maxId) - } - return promisedRequest({ - url: `${MASTODON_USER_MUTES_URL}?${query.toString()}`, +export const fetchMutes = ({ maxId, credentials }) => + promisedRequest({ + url: MASTODON_USER_MUTES_URL({ maxId, withRelationships: true }), credentials, }).then((users) => users.map(parseUser)) -} export const muteUser = ({ id, expiresIn, credentials }) => { const payload = {} @@ -1068,16 +1049,11 @@ export const unmuteUser = ({ id, credentials }) => method: 'POST', }) -export const fetchBlocks = ({ maxId, credentials }) => { - const query = new URLSearchParams({ with_relationships: true }) - if (maxId) { - query.append('max_id', maxId) - } - return promisedRequest({ - url: `${MASTODON_USER_BLOCKS_URL}?${query.toString()}`, +export const fetchBlocks = ({ maxId, credentials }) => + promisedRequest({ + url: MASTODON_USER_BLOCKS_URL({ maxId, withRelationships: true }), credentials, }).then((users) => users.map(parseUser)) -} export const addBackup = ({ credentials }) => promisedRequest({ @@ -1519,19 +1495,8 @@ export const chatMessages = ({ sinceId, limit = 20, }) => { - let url = PLEROMA_CHAT_MESSAGES_URL(id) - const args = [ - maxId && `max_id=${maxId}`, - sinceId && `since_id=${sinceId}`, - limit && `limit=${limit}`, - ] - .filter((_) => _) - .join('&') - - url = url + (args ? '?' + args : '') - return promisedRequest({ - url, + url: PLEROMA_CHAT_MESSAGES_URL(id, { maxId, sinceId, limit }), method: 'GET', credentials, }) @@ -1584,15 +1549,10 @@ export const deleteChatMessage = ({ chatId, messageId, credentials }) => credentials, }) -export const fetchScrobbles = ({ accountId, limit = 1 }) => { - let url = PLEROMA_SCROBBLES_URL(accountId) - const params = [['limit', limit]] - const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join( - '&', - ) - url += `?${queryString}` - return promisedRequest({ url }) -} +export const fetchScrobbles = ({ accountId, limit = 1 }) => + promisedRequest({ + url: PLEROMA_SCROBBLES_URL(accountId, { limit }), + }) export const fetchBookmarkFolders = ({ credentials }) => promisedRequest({ diff --git a/src/services/api/helpers.js b/src/services/api/helpers.js index 80012cca3..236893501 100644 --- a/src/services/api/helpers.js +++ b/src/services/api/helpers.js @@ -1,5 +1,71 @@ +import { snakeCase } from 'lodash' + import { RegistrationError, StatusCodeError } from 'src/services/errors/errors' +export const paramsString = (params = {}) => { + if (params == null || params === undefined) return '' + + if (typeof params !== 'object' || Array.isArray(params)) { + throw new Error('Params are not an object!') + } + + const entries = (() => { + if (params instanceof Map) { + return params.entries() + } else { + return Object.entries(params) + } + })() + + if (entries.length === 0) return '' + + const arrays = [] + const nonArrays = [] + + entries.forEach(([k, v]) => { + if (v == null) return // Drop nulls + if ( + (typeof v === 'object' && !Array.isArray(v)) || + typeof v === 'function' + ) { + throw new Error('Param cannot be non-primitive!') + } + if (Array.isArray(v)) { + arrays.push([k, v]) + } else { + nonArrays.push([k, v]) + } + }) + + arrays.forEach(([k, array]) => { + array.forEach((v) => { + if ( + typeof v === 'object' || + typeof v === 'function' || + typeof v === 'undefined' + ) + throw new Error('Array param cannot contain non-primitives!') + }) + }) + + return ( + '?' + + [ + ...nonArrays.map(([k, v]) => [snakeCase(k), v]), + // turning [a,[1,2,3]] into [[a[],1],[a[],2],[a[],3]] + ...arrays.reduce( + (acc, [k, arrayValue]) => [ + ...acc, + ...arrayValue.map((v) => [snakeCase(k) + '[]', v]), + ], + [], + ), + ] + .map(([k, v]) => `${k}=${window.encodeURIComponent(v)}`) + .join('&') + ) +} + export const promisedRequest = ({ method, url, @@ -60,6 +126,12 @@ export const promisedRequest = ({ ) } + if (typeof json !== 'object') { + return resolve({ + _response: response, + _value: json, + }) + } json._response = response return resolve(json) diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js index c754c7316..1a3b2395c 100644 --- a/src/services/follow_manipulate/follow_manipulate.js +++ b/src/services/follow_manipulate/follow_manipulate.js @@ -35,42 +35,33 @@ const fetchRelationship = (attempt, userId, store) => } }) -export const requestFollow = (userId, store) => - new Promise((resolve) => { - followUser({ - id: userId, - credentials: useOAuthStore().token, - }).then((updated) => { - store.commit('updateUserRelationship', [updated]) - - if (updated.following || (updated.locked && updated.requested)) { - // If we get result immediately or the account is locked, just stop. - resolve() - return - } - - // But usually we don't get result immediately, so we ask server - // for updated user profile to confirm if we are following them - // Sometimes it takes several tries. Sometimes we end up not following - // user anyway, probably because they locked themselves and we - // don't know that yet. - // Recursive Promise, it will call itself up to 3 times. - - return fetchRelationship(1, updated, store).then(() => { - resolve() - }) - }) +export const requestFollow = async (userId, store) => { + const updated = await followUser({ + id: userId, + credentials: useOAuthStore().token, }) -export const requestUnfollow = (userId, store) => - new Promise((resolve) => { - unfollowUser({ - id: userId, - credentials: useOAuthStore().token, - }).then((updated) => { - store.commit('updateUserRelationship', [updated]) - resolve({ - updated, - }) - }) + store.commit('updateUserRelationship', [updated]) + + if (updated.following || (updated.locked && updated.requested)) { + // If we get result immediately or the account is locked, just stop. + return + } + + // But usually we don't get result immediately, so we ask server + // for updated user profile to confirm if we are following them + // Sometimes it takes several tries. Sometimes we end up not following + // user anyway, probably because they locked themselves and we + // don't know that yet. + // Recursive Promise, it will call itself up to 3 times. + return await fetchRelationship(1, updated, store) +} + +export const requestUnfollow = async (userId, store) => { + const updated = await unfollowUser({ + id: userId, + credentials: useOAuthStore().token, }) + + return await store.commit('updateUserRelationship', [updated]) +} diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js index ec6c47a5d..e8fa3d16d 100644 --- a/src/services/notifications_fetcher/notifications_fetcher.service.js +++ b/src/services/notifications_fetcher/notifications_fetcher.service.js @@ -25,7 +25,7 @@ const mastoApiNotificationTypes = new Set([ 'pleroma:report', ]) -const fetchAndUpdate = ({ store, credentials, older = false, since }) => { +const fetchAndUpdate = ({ store, credentials, older = false, sinceId }) => { const args = { credentials } const rootState = store.rootState || store.state const timelineData = rootState.notifications @@ -35,24 +35,24 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => { mastoApiNotificationTypes.add('pleroma:chat_mention') } - args.includeTypes = mastoApiNotificationTypes + args.includeTypes = [...mastoApiNotificationTypes] args.withMuted = !hideMutedPosts args.timeline = 'notifications' if (older) { if (timelineData.minId !== Number.POSITIVE_INFINITY) { - args.until = timelineData.minId + args.maxId = timelineData.minId } return fetchNotifications({ store, args, older }) } else { // fetch new notifications if ( - since === undefined && + sinceId === undefined && timelineData.maxId !== Number.POSITIVE_INFINITY ) { - args.since = timelineData.maxId - } else if (since !== null) { - args.since = since + args.sinceId = timelineData.maxId + } else if (sinceId !== null) { + args.sinceId = sinceId } const result = fetchNotifications({ store, args, older }) @@ -69,7 +69,7 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => { if (readNotifsIds.length > 0 && readNotifsIds.length > 0) { const minId = Math.min(...unreadNotifsIds) // Oldest known unread notification if (minId !== Infinity) { - args.since = false // Don't use since_id since it sorta conflicts with min_id + args.sinceId = null // Don't use since_id since it sorta conflicts with min_id args.minId = minId - 1 // go beyond fetchNotifications({ store, args, older }) } diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 8e9b8a2e9..f2f9e6d41 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -41,7 +41,7 @@ const fetchAndUpdate = ({ bookmarkFolderId = false, tag = false, until, - since, + sinceId, }) => { const args = { timeline, credentials } const rootState = store.rootState || store.state @@ -53,10 +53,10 @@ const fetchAndUpdate = ({ if (older) { args.until = until || timelineData.minId } else { - if (since === undefined) { - args.since = timelineData.maxId - } else if (since !== null) { - args.since = since + if (sinceId === undefined) { + args.sinceId = timelineData.maxId + } else if (sinceId !== null) { + args.sinceId = sinceId } } diff --git a/test/unit/specs/services/api/helpers.spec.js b/test/unit/specs/services/api/helpers.spec.js new file mode 100644 index 000000000..3ffb83c70 --- /dev/null +++ b/test/unit/specs/services/api/helpers.spec.js @@ -0,0 +1,90 @@ +import { paramsString } from 'src/services/api/helpers.js' + +describe('API Helpers', () => { + describe.only('paramsString', () => { + it('should return empty string when given empty object', () => { + const string = paramsString({}) + + expect(string).to.eq('') + }) + + it('should return empty string when given null', () => { + const string = paramsString(null) + + expect(string).to.eq('') + }) + + it('should return empty string when given undefined', () => { + const string = paramsString(undefined) + + expect(string).to.eq('') + }) + + it('should return URI param string for normal object', () => { + const string = paramsString({ a: 1, b: '3' }) + + expect(string).to.eq('?a=1&b=3') + }) + + it('should encode objects correctly', () => { + const string = paramsString({ foo: true, bar: [1, 2, 3] }) + + expect(string).to.eq('?foo=true&bar[]=1&bar[]=2&bar[]=3') + }) + + it('should drop nullish params', () => { + const string = paramsString({ present: 'yes', missing: null }) + + expect(string).to.eq('?present=yes') + }) + + it('should convert camelCase keys to snake_keys objects correctly', () => { + const string = paramsString({ isActive: true, MaybeNot: false }) + + expect(string).to.eq('?is_active=true&maybe_not=false') + }) + + it('should work with maps', () => { + const string = paramsString( + new Map([ + ['key', 'yes'], + ['key2', 'also yes'], + ]), + ) + + expect(string).to.eq('?key=yes&key_2=also%20yes') + }) + + it('should escape components correctly', () => { + const string = paramsString({ gachi: '♂', muchi: 'Билли Геррингтон' }) + + expect(string).to.eq( + '?gachi=%E2%99%82&muchi=%D0%91%D0%B8%D0%BB%D0%BB%D0%B8%20%D0%93%D0%B5%D1%80%D1%80%D0%B8%D0%BD%D0%B3%D1%82%D0%BE%D0%BD', + ) + }) + + it('should throw when passed a non-object', () => { + expect(() => { + paramsString('Totally an object') + }).to.throw() + }) + + it('should throw when passed an array', () => { + expect(() => { + paramsString(['Totally an object']) + }).to.throw() + }) + + it('should throw when array param is non-primitive', () => { + expect(() => { + paramsString({ a: [() => ''] }) + }).to.throw() + }) + + it('should throw when array param is nullish', () => { + expect(() => { + paramsString({ a: [1, null, 3] }) + }).to.throw() + }) + }) +}) diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js index 43ce4da5d..a9cb3fd27 100644 --- a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js +++ b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js @@ -1,10 +1,11 @@ +import mastoapidata from '../../../../fixtures/mastoapi.json' + import { parseLinkHeaderPagination, parseNotification, parseStatus, parseUser, -} from '../../../../../src/services/entity_normalizer/entity_normalizer.service.js' -import mastoapidata from '../../../../fixtures/mastoapi.json' +} from 'src/services/entity_normalizer/entity_normalizer.service.js' const makeMockUserMasto = (overrides = {}) => { return Object.assign(